React Performance Patterns That Actually Matter
A practical guide to finding and fixing real rendering bottlenecks without over-optimizing.
Measure Before Optimizing
Profile with React DevTools before adding memo everywhere. Identify components with high render count × duration. Browser Performance panel reveals long tasks blocking input.
Real user monitoring (Vercel Speed Insights, web-vitals) surfaces production issues dev profiles miss — slow devices, poor networks, and data shapes unlike fixtures.
- Profiler for render count × time
- RUM for production truth
- Optimize user-perceived latency
State Placement And Render Scope
Colocate state with consumers. A search input filtering a list should not live in a parent that also wraps unrelated chrome — typing rerenders everything. Extract filter state to the list subtree or use deferred value for filter text.
Split large components so state changes rerender minimal subtrees. Provider splitting prevents auth context updates from rerendering static layout chrome.
- Narrowest state ownership
- Split components to isolate rerenders
- Split contexts by update frequency
Memoization With Intent
Apply React.memo when profiling shows expensive pure components receiving stable props. useMemo for O(n) computations on large datasets, not trivial string concatenation.
useCallback stabilizes handlers passed to memoized list items. Without memo on the child, useCallback alone wastes memory. Memoization stacks — each layer must justify its cost.
- Memo after profiling confirms benefit
- useMemo for expensive compute
- useCallback pairs with React.memo children
List and Table Performance
Virtualize lists over ~100 visible rows with tanstack-virtual or react-window. Paginate server data rather than rendering thousands of DOM nodes. Stable keys from entity IDs, never index on sortable lists.
Expensive cell renderers in tables benefit from memoized row components. Column definitions should be stable references — recreate columns in useMemo when dependencies change.
- Virtualize large lists
- Stable keys from entity IDs
- Memoize row/cell components
Avoiding Accidental Regression
Performance budgets in CI catch bundle size growth. Lighthouse CI on key routes tracks regressions. Code review checklist: new dependencies sized, new context providers scoped, no fetch in render.
Document known hotspots so future contributors do not "fix" memoization thinking it is unnecessary complexity. Link profiler screenshots in PR descriptions when adding intentional optimization.
- CI bundle budgets
- Lighthouse on critical routes
- Document intentional optimizations