React
Profiler: finding the slow component
Record one session, sort by render time and read the first component in the ranking. The Profiler measures React, not layout or paint.
Key points
- The Profiler measures React render and commit time, not layout or paint time.
- The react-dom 18.3.1 profiling build costs 9,427 bytes more than the production build.
- One session with a single interaction isolates the component; ten interactions produce noise.
Short answer: open React DevTools, record one session with the Profiler, reproduce the slow interaction and sort components by render time. The first entry is the suspect, not the proof.
What it measures and what it does not
The Profiler measures React work: render time for a subtree, commit time and the reason for a new render. It does not measure layout, paint or time spent inside an event handler. Those come from the browser Performance panel, and only there do you see the total.
The minimal wrapper
import { Profiler } from "react";
type Row = { id: string; price: number };
function onRender(
id: string,
phase: "mount" | "update" | "nested-update",
actualDuration: number,
baseDuration: number,
) {
if (actualDuration }) {
return (
);
}
The onRender callback only arrives with the profiling build: actualDuration is the time spent in that render, baseDuration is what it would need without memoisation. The gap between the two tells you whether memo and useMemo are working or just adding code.
The loop that works
- One interaction only: a click, a filter, a tab change. A session with ten interactions isolates nothing.
- The ranking, not the bars: the ranking sorts by time spent, the bars show the order of renders.
- The reason: open the component and read why it rendered again, among new props, changed state and context.
- The repeat: run the same interaction after the fix, otherwise you have no comparison.
Without DevTools
For a repeatable measure, wrap the suspect subtree in Profiler and log only the cases above a threshold. In Next.js the profiling build is enabled with the reactProductionProfiling option; in Vite projects you alias react-dom to the profiling variant.
The command
npx react-devtools
Live records in the catalogue
The interfaces you measure get built from existing records: the shadcn library for the primitives, the dashboard blocks for a page with tables and charts, and progress-indicator as a small component to profile.
The limitation
The Profiler sees React work only: a component that renders in 2 ms but forces a 40 ms synchronous layout stays almost invisible. The time the reader perceives is measured in the browser and is called INP, described in INP and responsiveness.
Where to continue
Rendering and its cost are explained in what React is; what changes in version 19 is in React 19. To set a threshold in CI read Lighthouse thresholds in CI, and for the comparison between libraries the measured ranking.
Frequently asked questions
Does the Profiler work in production?
Yes, but only with the profiling build of react-dom: the standard build never calls onRender. The profiling build costs 9,427 more bytes in the local react-dom 18.3.1 install.
Why do actualDuration and baseDuration differ?
actualDuration is the time of the render just done, baseDuration is what it would take if every child rendered without memoisation. If actualDuration stays high while baseDuration is low, memoisation is not working.
Should I profile every component?
No. Wrap only the subtree you suspect, with a recognisable id, and log the cases above a threshold. A wrapper around the whole tree produces logs nobody reads.
Does the Profiler find layout problems too?
No. It measures React render and commit, not browser layout and paint. Those need the Performance panel or a field measurement of INP.
Can I use the Profiler in a test?
Yes, wrap the component in the test and collect onRender calls in an array. It is useful to prove that an update does not re-render a branch that should stay still.