React Performance Optimization
React Performance Optimization
Performance isn't about premature optimization. It's about building systems that stay fast as they grow.
When to Optimize
Don't optimize everything. Optimize when:
- Users report slowness
- Metrics show issues (Core Web Vitals)
- Profiling reveals bottlenecks
- You're building data-heavy features
Measure first, optimize second.
Profiling Tools
Use the right tools:
- React DevTools Profiler - See component render times
- Chrome Performance - Analyze full page performance
- Web Vitals - Track real user metrics
- Lighthouse - Automated audits
Common Performance Issues
1. Unnecessary Re-renders
Problem: Component re-renders when props haven't changed.
// Bad: Creates new object every render function Parent() { return <Child config={{ theme: "dark" }} />; } // Good: Stable reference const CONFIG = { theme: "dark" }; function Parent() { return <Child config={CONFIG} />; } // Or use useMemo for dynamic values function Parent() { const config = useMemo(() => ({ theme: "dark" }), []); return <Child config={config} />; }
Use React.memo wisely:
const ExpensiveComponent = React.memo(({ data }) => { // Heavy rendering logic return <div>{/* ... */}</div>; });
2. Large Lists Without Virtualization
Problem: Rendering 10,000 rows kills performance.
Solution: Use virtualization.
import { useVirtualizer } from "@tanstack/react-virtual"; function VirtualList({ items }) { const parentRef = useRef<HTMLDivElement>(null); const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 50, }); return ( <div ref={parentRef} style={{ height: "500px", overflow: "auto" }}> <div style={{ height: `${virtualizer.getTotalSize()}px`, position: "relative", }} > {virtualizer.getVirtualItems().map((virtualRow) => ( <div key={virtualRow.index} style={{ position: "absolute", top: 0, left: 0, width: "100%", height: `${virtualRow.size}px`, transform: `translateY(${virtualRow.start}px)`, }} > {items[virtualRow.index].name} </div> ))} </div> </div> ); }
Only renders visible items. Huge performance win.
3. Heavy Computations in Render
Problem: Expensive calculations on every render.
// Bad: Recalculates every render function DataTable({ data }) { const sorted = data.sort((a, b) => a.value - b.value); const filtered = sorted.filter(item => item.active); return <Table data={filtered} />; } // Good: Memoized function DataTable({ data }) { const processed = useMemo(() => { const sorted = [...data].sort((a, b) => a.value - b.value); return sorted.filter(item => item.active); }, [data]); return <Table data={processed} />; }
4. Inline Functions as Props
Problem: New function every render = child re-renders.
// Bad function Parent() { return ( <Child onClick={() => console.log("clicked")} /> ); } // Good function Parent() { const handleClick = useCallback(() => { console.log("clicked"); }, []); return <Child onClick={handleClick} />; }
5. Context Provider Re-renders
Problem: Context value changes = all consumers re-render.
// Bad: New object every render function ThemeProvider({ children }) { const [theme, setTheme] = useState("light"); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); } // Good: Memoized value function ThemeProvider({ children }) { const [theme, setTheme] = useState("light"); const value = useMemo(() => ({ theme, setTheme }), [theme]); return ( <ThemeContext.Provider value={value}> {children} </ThemeContext.Provider> ); }
Code Splitting
Load code only when needed:
import { lazy, Suspense } from "react"; const Dashboard = lazy(() => import("./Dashboard")); const Settings = lazy(() => import("./Settings")); function App() { return ( <Suspense fallback={<Loading />}> <Routes> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/settings" element={<Settings />} /> </Routes> </Suspense> ); }
Image Optimization
Images are usually the biggest assets:
// Use modern formats <img src="hero.webp" srcSet="hero-sm.webp 400w, hero-md.webp 800w, hero-lg.webp 1200w" sizes="(max-width: 640px) 400px, (max-width: 1024px) 800px, 1200px" alt="Hero" loading="lazy" />
Use loading="lazy" for below-the-fold images.
State Management Performance
Use Zustand for Better Performance
import { create } from "zustand"; const useStore = create((set) => ({ todos: [], addTodo: (todo) => set((state) => ({ todos: [...state.todos, todo] })), })); // Only re-renders when todos change function TodoList() { const todos = useStore((state) => state.todos); return <div>{/* ... */}</div>; } // Never re-renders function AddTodo() { const addTodo = useStore((state) => state.addTodo); return <button onClick={() => addTodo({ text: "New" })}>Add</button>; }
TanStack Query for Server State
const { data } = useQuery({ queryKey: ["users"], queryFn: fetchUsers, staleTime: 5 * 60 * 1000, // 5 minutes });
Automatic caching, deduplication, background refetching.
Bundle Size Optimization
Tree Shaking
// Bad: Imports entire library import _ from "lodash"; // Good: Import only what you need import debounce from "lodash/debounce";
Analyze Bundle
npm install --save-dev vite-plugin-bundle-analyzer
Find and remove large dependencies.
React Compiler (Coming Soon)
React 19+ will have automatic memoization. But until then, manual optimization is needed.
Performance Checklist
✅ Profile before optimizing ✅ Use React.memo for expensive components ✅ Virtualize large lists ✅ Code split routes ✅ Lazy load images ✅ Optimize images (WebP, sizing) ✅ Memoize expensive computations ✅ Use proper state management ✅ Analyze bundle size ✅ Monitor Core Web Vitals
Conclusion
Performance is a feature. Build fast from the start:
- Measure with tools
- Optimize bottlenecks
- Use proper patterns
- Monitor in production
Fast apps = happy users = business success.
Need help with React performance? Let's talk.