Ever stared at React DevTools’ Profiler flamegraph and wondered, “How does React actually measure these timings? Why do some commits show weird durations? And what exactly is React timing anyway?”
I’ve been there. You want to trust the Profiler data but the numbers sometimes feel off or confusing. So I grabbed the React source code, traced the Profiler integration, and here’s what I figured out.
Why profiling React rendering isn’t as simple as a stopwatch
Profiling React isn’t just about recording when a component starts rendering and when it ends. React apps are complex. A render can be split, interrupted, and resumed. React Fiber’s reconciliation is asynchronous and can pause work to handle urgent updates.
So measuring render time means carefully hooking into React’s internal phases and summing up the right pieces without double counting.
How React Profiler hooks into the render and commit phases
React’s Profiler API revolves around two things:
- Tracking render phase durations.
- Tracking commit phase durations.
When you wrap some components with <Profiler onRender={callback} />, React records timings for every render and commit that affects those components.
Inside React Fiber: the timing flow
Here’s roughly what happens under the hood:
- Render phase starts: React kicks off reconciliation for a root. It marks the start time.
- Work is done: React walks the Fiber tree, calling render methods and hooks.
- Render phase ends: React notes the end time and calculates render duration.
- Commit phase starts: React applies changes to the DOM.
- Commit phase ends: React records commit duration.
- Profiler callback fired: For each Profiler node, React calls your callback with timings and metadata.
This split is crucial because your app’s render phase is often the CPU-heavy part, while the commit phase is about DOM mutations, layout, and side effects.
What exactly does React measure?
React collects these timings per Profiler tree:
- Actual render duration: Time spent rendering React components during reconciliation.
- Base render duration: The render time without interruptions or pauses (used internally by React Scheduler).
- Commit duration: Time spent applying changes to the DOM.
- Start and end times: Timestamps for when the render started and committed.
These numbers come from calls to performance.now(), which has sub-millisecond precision in browsers.
Why do some commits show strange or inflated durations?
React’s render can be interrupted and restarted, especially in Concurrent Mode. The Profiler tries to report the final render duration, but if React abandons a render midway and retries, some of those partial timings might leak into the final report.
Also, the commit phase can include more than just DOM updates. Effects and lifecycle methods run here and can add to the commit duration.
If you see unusually long commit durations, check for expensive side effects or synchronous DOM reads/writes triggered in useEffect or lifecycle hooks.
How React associates profiler data with components
React tracks Profiler nodes as Fiber nodes with a special tag. When a Profiler component renders, it creates a Fiber node that stores timing data.
Because React builds and commits Fiber trees incrementally, it accumulates timings per Profiler node and attributes them to the wrapped subtree.
Your Profiler callback receives:
id: the id you gave the Profiler.
phase: "mount" or "update".
actualDuration: time spent rendering this update.
baseDuration: estimated time to render the subtree without interruptions.
startTime and commitTime: timestamps.
interactions: a set of interactions traced via React’s scheduler tracing.
This lets you correlate renders with user interactions or other events.
Using profiler data to find bottlenecks
The Profiler’s flamegraph shows you which components take the most render time. But the raw numbers aren’t always obvious:
- Look for large actualDuration spikes.
- Compare baseDuration to actualDuration: a big gap might mean React interrupted work.
- Check commitDuration spikes to identify expensive DOM updates or effects.
One useful trick is to profile with and without Suspense or memoization to see how different features affect render times.
Debugging strategies with React Profiler
- Start broad: Profile entire pages or major routes to spot hotspots.
- Drill down: Wrap suspicious components in their own Profiler to isolate timings.
- Analyze commit phase: If commit times spike, audit effects and DOM mutations.
- Use React DevTools Profiler flamegraph: Hover over components to see durations, then cross-reference with your code.
- Leverage
interactions tracing: Correlate slow renders with user events to optimize UX responsiveness.
What the Profiler doesn’t show you
The Profiler times React’s rendering and commit work but doesn’t measure:
- Browser layout and paint times after commit.
- JavaScript work outside React (e.g., event handlers not triggering renders).
- Network latency or data fetching durations.
You’ll often need to combine Profiler data with browser performance tools like Chrome DevTools Performance tab or lighthouse for a fuller picture.
Wrapping up
React Profiler is more than a fancy stopwatch. It hooks deeply into the Fiber reconciler’s phases, measures both render and commit durations, and reports data tied to your component trees and interactions.
When you understand how it works under the hood, the profiler data becomes much clearer and more actionable. You can spot real bottlenecks, identify expensive commits, and debug performance issues with confidence.
Next time you profile a sluggish React app, remember: React’s timing is the story of a complex dance between render interruptions, commit work, and scheduling priorities , and the Profiler is your window into that dance.