Skip to content
Damien Mercier

React performance in practice: a working checklist

Damien Mercier · · 3 min read

ReactPerformanceFrontend

Most React performance advice starts with solutions: memoise this, virtualise that. In practice, an application is usually slow for a reason nobody has measured yet. So this checklist starts where the work actually starts: finding out what is slow, then applying the fix that matches the cause, in that order.

1. Measure before touching anything

Open the React DevTools Profiler, record the interaction that feels slow, and look at two things: which components rendered, and why. Then check the browser's Performance panel for the same interaction, because sometimes the cost isn't React at all, but layout thrash, oversized images or a third-party script.

Two rules worth keeping:

  • Profile in a production build. Development mode is dramatically slower and lies about where time goes.
  • Profile with realistic data. Ten demo rows hide the problem a thousand real rows will reveal, and the gap is widest on data-heavy screens like the survey dashboards on gPulse.

2. Fix re-renders at the source

The most common finding: an interaction re-renders far more of the tree than it needs to. Before reaching for memo, restructure:

  • Push state down. State that lives high in the tree re-renders everything under it. A hover state, an input value, an open/closed flag: move each to the smallest component that cares.
  • Pass children instead of rendering inside. A component that takes children doesn't re-render them when its own state changes. This one pattern removes entire classes of wasted renders, free.
  • Then memoise what remains. memo, useMemo and useCallback are for the hot paths the profiler actually shows: expensive subtrees receiving stable props. Sprinkled everywhere "just in case", they add noise and hide intent.

3. Derive data once, not per render

Transforming API data (filtering, grouping, computing display series) inside the render path is a silent tax on every interaction. Derive once per data change (useMemo keyed on the source), and keep formatting cheap at the leaf level. In chart-heavy UIs this is regularly worth more than any rendering optimisation.

4. Virtualise long lists, but only long ones

A list rendering thousands of DOM nodes will be slow no matter how well memoised. Windowing (rendering only the visible slice) fixes it. The threshold in practice: under a few hundred simple rows, don't bother. The added complexity of virtualisation (scroll restoration, accessibility, variable heights) isn't free.

5. Check what you ship, not just what you render

Runtime performance and loading performance are different problems; users feel both.

  • Run a bundle analyzer and look at the top ten modules. There is almost always a surprise: a charting library imported whole for one chart, a date library where Intl would do.
  • Code-split routes and heavy widgets with dynamic imports.
  • Let the framework help: in Next.js, Server Components keep entire dependency trees off the client, and next/image handles the media side. Loading performance overlaps heavily with SEO; I've written about that in SEO with the Next.js App Router.

6. Re-measure, and write the number down

After each fix, profile the same interaction again. Two reasons: confirming the fix did what you believed (they don't always), and building the habit of attaching numbers to claims. "The dashboard renders in 80ms now, down from 600ms" survives scrutiny; "it feels faster" doesn't.

The uncomfortable summary

React performance work is rarely about knowing exotic APIs. It is measuring, restructuring state, doing less work per render, and shipping less code, in that order, stopping when the numbers say you can. If your application needs this treatment and you'd rather someone did it with you, performance optimisation is part of what I do, so get in touch.