Technology · React
React Memo and Lazy Loading
Optimize component rendering with React.memo, lazy loading, and code splitting strategies.
TL;DR
- 01Use React.memo to skip re-renders when props haven't changed.
- 02Use lazy loading to split code and load components on demand.
- 03Combine both for significant performance improvements.
React.memo
- Memoize a component to skip re-renders when props are the same.
const UserCard = React.memo(({ user }) => ( <div>{user.name}</div> )); - React.memo compares new and old props using shallow equality.
- Only skip re-renders if props haven't changed.
// Parent re-renders but UserCard doesn't if user prop is the same const Parent = () => { const [count, setCount] = useState(0); const user = { id: 1, name: "Alice" }; return ( <> <UserCard user={user} /> <button onClick={() => setCount(count + 1)}> Count: {count} </button> </> ); }; - Use custom comparison for complex props.
const UserCard = React.memo( ({ user }) => <div>{user.name}</div>, (prevProps, nextProps) => { return prevProps.user.id === nextProps.user.id; } ); - Memoization has overhead, so only use when re-renders are expensive.
Lazy Loading
- Use React.lazy to split code and load components on demand.
import { lazy, Suspense } from "react"; const HeavyComponent = lazy(() => import("./HeavyComponent")); export default function App() { return ( <Suspense fallback={<p>Loading...</p>}> <HeavyComponent /> </Suspense> ); } - The component code is not included in the main bundle.
- Only loads when the component is about to be rendered.
- Wrap lazy components in Suspense for a fallback UI.
<Suspense fallback={<div>Loading page...</div>}> <HeavyPageComponent /> </Suspense> - Works great for route-based code splitting.
Route-Based Code Splitting
- Split code by route to load pages on demand in Next.js or React Router.
import { lazy } from "react"; const Dashboard = lazy(() => import("./pages/Dashboard")); const Settings = lazy(() => import("./pages/Settings")); const Profile = lazy(() => import("./pages/Profile")); - Each route is a separate chunk loaded when the user navigates.
- Reduces the initial bundle size significantly.
<Routes> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/settings" element={<Settings />} /> <Route path="/profile" element={<Profile />} /> </Routes> - Most effective optimization for large applications.
- Combine with Suspense for loading states while pages load.
Component Splitting
- Split large components into smaller memoized pieces.
const Header = React.memo(() => <header>...</header>); const Content = React.memo(({ data }) => <main>{data}</main>); const Footer = React.memo(() => <footer>...</footer>); export default function Page({ data }) { return ( <> <Header /> <Content data={data} /> <Footer /> </> ); } - Memoized children don't re-render when parent re-renders.
- Only effective if children receive stable props.
- Combine with useCallback to keep function props stable.
const handleClick = useCallback(() => { // Handle click }, []); <Button onClick={handleClick} />
Performance Monitoring
- Use Profiler API to measure component rendering time.
import { Profiler } from "react"; <Profiler id="dashboard" onRender={onRender}> <Dashboard /> </Profiler> function onRender(id, phase, actualDuration) { console.log(`${id} (${phase}) took ${actualDuration}ms`); } - Use React DevTools Profiler to identify slow components.
- Measure before and after optimization to confirm improvements.
- Only optimize the slow components, not everything.
// Profile to find slow components first // Then apply memo, lazy, or splitting strategically - Avoid premature optimization — measure real performance first.
Tip: Use lazy loading for large components and code splitting for routes to significantly reduce initial bundle size and improve page load time.
Warning: React.memo only does shallow comparison, so passing new objects or arrays as props defeats memoization — use useMemo or useCallback to keep props stable.