Frontend Engineering

Profile first, optimise second — a practical guide to memoisation, code splitting, virtualisation, and measuring what actually matters in production.

ZE
Zyden EditorialApril 10, 2026 · 10 min read
Colorful programming code on a monitor screen

A React DevTools Profiler flame chart before and after applying React.memo and useCallback. Yellow bars indicate components that committed a render in that frame.

Why React Performance Problems Happen

React's component model is designed for developer ergonomics — and it achieves that goal brilliantly. But the same reconciliation mechanism that makes React intuitive becomes a source of invisible performance debt as applications grow. Most performance problems in React boil down to one root cause: too many components re-rendering too often.

Understanding why React re-renders and when to intervene is the foundation of all optimisation work. Profile first, optimise second — never guess your bottleneck.

The Re-render Decision Tree

A React component re-renders whenever any of these three things change:

  • State — any useState or useReducer dispatch triggers a re-render of that component and all its descendants
  • Props — a new object or array reference, even with identical values, is treated as a changed prop by the reconciler
  • Context — every consumer of a context re-renders when the context value changes, regardless of which part of the value they actually use

Memoisation Techniques

React provides three built-in memoisation primitives. Used correctly, they are powerful. Used indiscriminately, they add memory pressure and make code harder to read. Apply them only after profiling confirms a genuine bottleneck.

  1. React.memo — wraps a component and skips re-renders when all props are shallowly equal to the previous render
  2. useMemo — caches the result of an expensive computation between renders, recomputing only when dependencies change
  3. useCallback — caches a function reference so it does not cause child re-renders when passed as a prop

Premature optimisation is the root of all evil. But ignoring performance until your users complain is the root of all rewrites.

— Engineering folklore, confirmed by experience

Code Splitting and Lazy Loading

Bundle size is the hidden performance tax of every React application. Use React.lazy combined with Suspense to defer loading non-critical components until they are needed. This technique — route-level code splitting — can reduce your initial JavaScript payload by 40–60% in large applications.

Bundle Size Before vs. After Code SplittingNetwork Waterfall
Network waterfall showing load-time improvement after implementing route-level code splitting. Initial JS payload reduced from 820 KB to 310 KB gzipped — a 62% reduction.

Virtualisation for Long Lists

Rendering 10,000 DOM nodes at once is not a React problem — it is a browser problem. Virtualisation (also called windowing) renders only the nodes currently visible in the viewport, maintaining a small, constant DOM footprint regardless of list length.

🚀

For lists with more than 100 items, always benchmark a virtualised approach. Libraries like react-window and @tanstack/react-virtual routinely deliver 10× rendering performance improvements on large datasets without changing a single line of business logic.


Measuring What Matters

The React DevTools Profiler is your best friend for identifying which components re-render unnecessarily. For production monitoring, track Core Web Vitals — specifically Largest Contentful Paint (LCP) and Interaction to Next Paint (INP) — as the ground truth of user-perceived performance in your deployed application.

#react#performance#frontend#optimization#core web vitals
Whatsapp