skip to content

Your team wants to ship the `actualDuration` values from React's `<Profiler>` to analytics from real users' browsers. What does a standard React production build do with those measurements, and what does it take to get real numbers?

level: seniorimportance: should knowfreq 30%

answer

  1. measuring costs something on every commit
  2. default production build leaves it out
  3. there is a build made for this
  4. a separate react-dom entry point
  5. flag it, sample it, turn it off

basics

~20 s

React disables profiling in its standard production build because the timing instrumentation costs time and memory on every commit. Real production numbers require building against React's profiling-enabled build, which reintroduces that overhead for every user you ship it to.

solid answer

~50 s

Profiling is not free — React has to time each unit of work and carry the bookkeeping through render and commit — so the standard production build leaves it out, and a `<Profiler>` you leave in shipped code will not hand you trustworthy timings there. To measure real traffic you build against React's profiling-enabled build; `react-dom` exposes it as a separate `react-dom/profiling` entry point, and most bundlers and frameworks expose a build flag that aliases it in for you. The tradeoff is real: a slightly larger, slightly slower React for whoever receives that bundle. So treat it as a deliberate, reversible decision — put it behind a build flag, prefer a canary or a sampled slice of traffic over the whole user base, and turn it off when the investigation is finished. Development numbers are not a substitute: dev builds are slower and StrictMode double-renders.

go deeper

for a junior

Know that React turns profiling off in the standard production build, and that the numbers you see while developing are inflated and only useful for comparison.

for a middle

Explain why the instrumentation is stripped — it times every unit of work inside React's render loop — and name the profiling-enabled build as the supported way to get production measurements.

for a senior

Show the operational judgment: flag-gate the profiling build, roll it to a canary or sampled slice rather than everyone, keep the callback buffered and cheap, and define when it gets turned off again.

for a principal

Weigh the whole programme: what decision the data unlocks, the cost of an instrumented React across the user base, the observer effect on the numbers you will quote, and whether reproducible local profiling answers it for free.

## Why the standard production build says nothing Measuring render work is itself work. To produce `actualDuration` and `baseDuration`, React must take a timestamp around each unit of work in the profiled subtree, accumulate the results as it walks back up, and keep per-component timing state so it can estimate the no-memoization cost later. That instrumentation is threaded through the hot path of rendering, which is exactly the code React optimises hardest. So React ships it out of the default production build. The consequence for your plan is blunt: leaving a `<Profiler>` in production code and expecting numbers to flow into analytics does not work with a normal build. You are not going to accidentally slow every user down with a full timing pass — but you are also not going to learn anything. ## The profiling-enabled build React publishes a production build **with** profiling enabled for exactly this case. `react-dom` exposes it as the `react-dom/profiling` entry point, and build tools and frameworks generally offer a flag that swaps it in so you do not have to rewrite imports. It is production React — minified, without development warnings — plus the timing instrumentation. That is the whole tradeoff, and it should be stated plainly to whoever is asking for the data: - **Runtime cost.** Every commit in the app pays for the measurement, not only the profiled subtrees, because the instrumentation lives in React's own work loop. - **Bundle cost.** The build is somewhat larger than the plain production build. - **Observer effect.** The numbers you collect are timings of an instrumented React. They are excellent for comparison — this route versus that one, before a change versus after — and misleading if quoted as "what users experience" on the shipped build. ## How to ship it responsibly Treat it as an experiment with an end date, not a permanent property of the app: 1. **Put it behind a build flag.** One environment variable that selects the profiling build, so returning to normal is a rebuild, not a refactor. 2. **Limit the blast radius.** A canary deployment, an internal build, or a small sampled percentage of traffic answers most questions. Whole-fleet profiling is rarely necessary to find out that one subtree costs 40 ms. 3. **Keep the callback cheap.** `onRender` runs after every commit of the profiled subtree — many times per interaction on a busy page. Push a small record into a buffer and flush it on an idle callback or on page hide; never do a network request per commit, and never do heavy formatting inside the callback, because that work lands inside the very path you are measuring. 4. **Know what decision the data drives.** "We suspect the dashboard subtree is slow on low-end devices and want the distribution across real hardware" is a reason. "It would be nice to have render times in the dashboard" usually is not, given the cost. 5. **Have a stop condition.** When the question is answered, drop back to the standard build. ## What to reach for first Before shipping an instrumented React to users, ask whether a local session answers the question. React DevTools can profile locally with no production impact at all, and most render-cost problems — a context value rebuilt each render, a list re-rendering wholesale, state held too high — reproduce on a developer machine. The genuine case for production profiling is the one you cannot reproduce: real data volumes, real device classes, real network conditions, a long tail that only shows up across thousands of sessions. ## Numbers that do not transfer Two tempting substitutes are worse than useless: - **Development-build numbers.** Development React is slower by design and includes warnings and checks that production strips. On top of that, StrictMode deliberately renders components twice in development, inflating `actualDuration`. A dev measurement is a comparison tool, not a production estimate. - **Shipping a development build to production** to "get the timings". This is the anti-pattern the question is really probing. It exposes development-only behaviour, ships a much larger and slower bundle, and produces numbers further from reality than the profiling build would. The profiling build exists precisely so nobody has to do this. The summary a senior engineer gives: profiling in production is possible, deliberately not free, and worth doing only for a question you cannot answer any other way — behind a flag, on a slice of traffic, with a cheap callback and a plan to turn it off.

  • Someone proposes shipping the development build to production instead, since the timings work there. What is wrong with that?
    Development React is much larger and slower, includes warnings and internal checks meant for authors, and enables development-only behaviour such as StrictMode's double render — which inflates the very durations you are collecting. You would degrade real users' experience to gather numbers that are further from reality than the profiling build's. The profiling build exists to avoid exactly this.
  • How should the onRender callback behave when it is running on real user traffic?
    It must be near-free. It fires after every commit of the profiled subtree, so it can run many times per interaction, and its own work is executed on the path you are measuring. Append a compact record to an in-memory buffer, aggregate there, and flush on an idle callback or on page hide. Never send a request per commit.
  • When is a local React DevTools profiling session the better answer than production profiling?
    Whenever the problem reproduces on a developer machine — a context value rebuilt every render, a list re-rendering wholesale, state held too high. That is most render-cost bugs, and it costs users nothing. Reserve production profiling for what you cannot reproduce: real data volumes, low-end devices, and long-tail sessions.

saying these in an interview costs you the question

  • Assumes Profiler timings just work in a normal production build
  • Suggests shipping a development build to get real timings
  • Ignores that the profiling build slows down every user who receives it
  • Posts an analytics request from inside onRender on every commit
  • Quotes development milliseconds as the production number

context