React is fast by default — until it isn't. As applications grow, rendering bottlenecks creep in. This guide covers the techniques for diagnosing and fixing performance issues in production React applications.
Rule Zero: Measure First
Use React DevTools Profiler to find the actual bottleneck. Open the Profiler tab, record an interaction, and examine which components re-rendered and why.
Preventing Unnecessary Re-renders
React.memo
const ExpensiveChart = memo(function ExpensiveChart({
data, width, height
}: ChartProps) {
// Only re-renders when props actually change
return <canvas />
})useMemo for Expensive Computations
const summary = useMemo(() => ({
total: transactions.reduce((sum, t) => sum + t.amount, 0),
byCategory: groupBy(transactions, "category"),
}), [transactions])React Compiler (React 19)
React 19 introduces automatic memoization at build time, making manual memo/useMemo/useCallback largely unnecessary for new code. Enable with experimental: { reactCompiler: true }.
Code Splitting
const DataViz = lazy(() => import("./DataVisualization"))
function Dashboard() {
return (
<Suspense fallback={<Skeleton />}>
<DataViz />
</Suspense>
)
}Virtualization for Long Lists
Render only visible items. Use @tanstack/react-virtual for lists over 100 items. The overhead of the virtualizer is negligible compared to rendering thousands of DOM nodes.
Colocate State
The most impactful technique: put state as close as possible to where it's used. Global state that changes frequently causes the entire tree to re-render.
Performance Checklist
- Profile first — don't guess
- Colocate state — move it down
- Virtualize long lists (100+ items)
- Code split heavy routes
- Optimize images (next/image or lazy loading)
- Defer non-urgent updates (useDeferredValue)
- Enable React Compiler in React 19
Wrapping Up
Focus on architecture (where state lives, how data flows) rather than micro-optimizations. A well-structured app is fast by default.