React Performance Patterns I Actually Use in Production
After 8 years of building React apps, these are the practical optimization techniques that have made a real difference — not just benchmark improvements.
Performance optimization in React has a lot of folklore. 'Never use inline arrow functions', 'always memoize', 'useCallback everywhere'. Most of it is cargo-culted advice that doesn't reflect how modern React actually works. Here's what I've found genuinely useful after shipping production apps for 8 years.
1. Profile First — Always
The biggest mistake I see is developers adding useMemo and useCallback all over the codebase without ever measuring. React DevTools Profiler tells you exactly which components are re-rendering and why. Start there. You'll often discover the real bottleneck isn't what you expected.
2. Colocate State, Don't Lift Unnecessarily
A form with 20 fields doesn't need its state in a top-level context. Every keystroke would re-render the entire subtree. Keep form state local with React Hook Form's uncontrolled mode, and only lift state when multiple distant components truly need to share it.
3. React.memo Is a Contract, Not a Toggle
React.memo only helps if the props are referentially stable. Wrapping a component with memo while passing a new object literal every render does nothing. Use memo when you know the parent re-renders often but the child's props genuinely don't change — and use the second argument to customize the comparison if needed.
// Only useful if 'items' reference is stable (e.g., from useMemo or state)
const ItemList = React.memo(({ items }: { items: string[] }) => (
<ul>{items.map(i => <li key={i}>{i}</li>)}</ul>
));4. Virtualize Long Lists
Rendering 1,000 DOM nodes is expensive. TanStack Virtual (formerly react-virtual) makes it trivial to only render the rows in the viewport. I've seen this reduce render time from 2 seconds to 50ms on dashboard tables. The setup is about 30 lines of code.
5. Suspense Boundaries as Loading UX Strategy
In Next.js App Router, wrapping individual sections in Suspense lets you stream HTML progressively. Critical content appears instantly while slower data-fetching sections load in behind it. Your users perceive a much faster page even when the total data fetch time is the same.
The Rule I Follow
Measure, identify the actual bottleneck, fix only that. Every optimization has a maintenance cost. The best-performing code is still the code that's easiest to read and change six months from now.