React Performance Challenges
React applications often suffer performance issues: large bundle sizes, unnecessary re-renders, janky animations. Performance directly impacts user satisfaction and conversion rates.
Code Splitting and Lazy Loading
Bundle everything in one file? Users download unnecessary code. Code splitting breaks bundles into chunks loaded on demand.
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Admin = lazy(() => import('./Admin'));
export function App() {
return (
<Routes>
<Route
path="/dashboard"
element={
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
}
/>
</Routes>
);
}
Users visiting dashboard load Dashboard chunk. Admin users load Admin chunk. Initial bundle shrinks 30-50%.
Route-based splitting for page-level components. Component-based splitting for heavy components (modals, datepickers).
Memoization and Preventing Re-renders
By default, React re-renders on every state change. Components receiving props re-render even if props unchanged.
// Without memo - re-renders on every parent render
function UserCard({ user }) {
return <div>{user.name}</div>;
}
// With memo - re-renders only if user prop changes
const UserCard = memo(function UserCard({ user }) {
return <div>{user.name}</div>;
}, (prev, next) => prev.user.id === next.user.id);
Memoization prevents re-renders but costs performance (comparison checks). Memo complex components rendering often. Skip memo on simple components.
useMemo prevents expensive computations:
const expensiveValue = useMemo(() => {
return fibonacci(50); // Expensive
}, [dependency]);
useCallback prevents function recreation:
const handleClick = useCallback(() => {
dispatch(action);
}, [dispatch]);
Virtual Scrolling for Large Lists
Rendering 10,000 list items creates 10,000 DOM nodes. Browsers struggle. Virtual scrolling renders only visible items.
import { FixedSizeList } from 'react-window';
export function LargeList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
}
10,000 items, 40 visible. Only 40 DOM nodes. Scrolling stays smooth.
Image Optimization
Images are often the largest assets. Optimize aggressively:
import Image from 'next/image';
export function Hero() {
return (
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
quality={75}
placeholder="blur"
blurDataURL={blurredBase64}
/>
);
}
Next.js Image component:
- Serves optimized formats (WebP)
- Lazy loads below fold
- Responsive sizing
- Built-in blur placeholder
Bundle Size Analysis
Identify large dependencies:
npm install --save-dev webpack-bundle-analyzer
Analyze output. Replace large dependencies:
- moment → date-fns (100kb → 10kb)
- lodash → lodash-es (70kb → 4kb tree-shaken)
- axios → fetch (reducing http client)
Core Web Vitals Optimization
Google ranks sites on Core Web Vitals:
LCP (Largest Contentful Paint): Load time for largest visible element. Target <2.5s.
- Optimize images
- Defer non-critical CSS
- Preload critical fonts
FID (First Input Delay): Time to respond to user input. Target <100ms.
- Reduce main thread blocking work
- Break up long tasks with
setTimeout
CLS (Cumulative Layout Shift): Unexpected layout changes. Target <0.1.
- Reserve space for images
- Avoid inserting content above existing
// Prevent layout shift
<div className="card" style={{ aspectRatio: '16/9' }}>
<Image
src={...}
fill
sizes="100%"
/>
</div>
Profiling and Measurement
Use React DevTools Profiler:
- Record component renders
- Identify slow components
- Check if memoization helps
Use Lighthouse in Chrome:
- Audit performance
- Get specific recommendations
- Track improvements over time
Frequently asked questions
When should we use code splitting vs keeping code in main bundle?
Split routes and heavy modals. Keep utility libraries in main bundle (always needed). Rule: if feature used by <30% of users or loads >50kb, split it. Main bundle target: <100kb gzip.
How much overhead does memoization add?
Memo comparison ~0.1-1ms per component. Benefits appear when render time >5ms or renders frequent (hundreds/second). Don't memo simple components. Profile first, optimize based on data.
How do we handle dynamic data with virtual scrolling?
Virtual scrolling works with pagination or windowed loading. Load next 100 items as user scrolls near end. Infinite scroll with placeholders while loading. react-window supports dynamic sizing per item.