Most React tutorials cover the basics of performance: don't mutate state, use keys in lists, and avoid inline functions in render. But real production apps require deeper strategies. When your component tree grows to hundreds of nodes and your state updates fire cascading re-renders, you need a more systematic approach to performance.
React.memo prevents re-renders when props haven't changed, but it's not free — it adds a shallow comparison on every render. Use it on components that receive stable props but sit inside frequently re-rendering parents. Pair it with useMemo for expensive computed values anduseCallback for function references passed as props. The key rule: don't memoize everything blindly. Profile first, then optimize the components that actually cause jank.
Rendering thousands of DOM nodes kills scroll performance. Libraries like react-window andreact-virtuoso solve this by only rendering the items currently visible in the viewport, plus a small overscan buffer. I've seen this single optimization take a dashboard from 3-second render times to under 50ms. If your list has more than 100 items, virtualize it.
Use React.lazy with Suspense to split heavy components into separate bundles. This is especially powerful for modals, charts, and admin panels that most users never see. In Next.js, use next/dynamic with ssr: false for components that depend on browser APIs. Watch your bundle size with tools like @next/bundle-analyzer — it often reveals surprising culprits like date libraries or icon packs.
The React DevTools Profiler shows you which components re-render and why. Enable the "Highlight updates when components render" option to visually spot unnecessary re-renders. For production, use the Profiler API to collect timing data from real users. Combine this with Web Vitals metrics — specifically Interaction to Next Paint (INP) — to understand how your React app performs on actual devices, not just your M3 MacBook.
The biggest performance killer in React is often state architecture. Lifting state too high causes entire subtrees to re-render. Consider colocating state closer to where it's used, using context selectors (via use-context-selector), or adopting atomic state managers like Jotai or Zustand that allow granular subscriptions. The goal is simple: minimize the number of components that re-render when state changes.