Skip to the chapter
←ReactGGroundwork⚛React
React/Advanced/A19
52 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛A19 · Part 3 · Advanced

Debugging in production

The bug is on someone else's device, on a network you cannot reproduce.

4 min read8 sections2 exercises
52 of 57

In this chapter

  • Why local debugging stops working
  • Errors: make them arrive with context
  • Group by release, not by message
  • Memory leaks
  • Field data, not lab data

and 3 more

about 4 min
08On this page
  1. Why local debugging stops working
  2. Errors: make them arrive with context
  3. Group by release, not by message
  4. Memory leaks
  5. Field data, not lab data
  6. Reproducing what you cannot see
  7. Hydration mismatches, which only happen in production
  8. The order to work in

Why local debugging stops working

Your machine is fast, your connection is good, your cache is warm, and you have one tab open with the state you just created. None of that is true for the person filing the bug. The skill at this level is getting information back from a session you were not in.

Errors: make them arrive with context

<ErrorBoundary
  onError={(error, info) => {
    reportError(error, {
      componentStack: info.componentStack,   // which component threw
      route: location.pathname,
      release: __APP_VERSION__,              // which deploy
      userId: session?.id,
    });
  }}
/>

A stack trace from a minified bundle is unreadable, so upload source maps from CI and never ship them to the browser. Without them you are reading a.b is not a function at line 1, column 48000.

The componentStack is what makes a React error tractable — the JavaScript stack tells you which function threw, the component stack tells you where in the tree it was. React 19 also exposes captureOwnerStack in development for the component that created the element.

Group by release, not by message

The single most useful thing an error tracker gives you is "this started at 14:20 on Tuesday, which is when release 4.12 went out". Tag every report with a release identifier and a deploy timestamp. Most production bugs are answered by the diff, not by the stack.

Memory leaks

React leaks look the same every time: something outlived the component that created it, and it closed over a large object.

LeakCause
Listener on windowEffect with no cleanup
Interval or timeoutSame
Subscription to a store or socketSame
A cache that only growsA module-level Map keyed by something unbounded
Detached DOM nodesA ref to a node held after unmount

Finding one: open the Memory panel, take a heap snapshot, navigate away and back five times, take another, and compare. If retained size climbs per navigation, sort by detached nodes and look at what is holding them. The retainer path names the closure, and the closure names the effect.

Strict Mode's double mount is a leak detector: anything that leaks per mount leaks twice as fast in development, which is where you should be finding it.

Field data, not lab data

Lab (Lighthouse, your machine)Field (real users)
One device, one network, cold cacheEvery device, every network, mixed caches
Good for comparing before and afterGood for knowing what is actually happening
ReproducibleThe truth
import { onLCP, onINP, onCLS } from "web-vitals";
onINP((metric) => send({ name: metric.name, value: metric.value, route, device }));

Look at the 75th percentile, not the mean. An average hides the quarter of users on a mid-range Android phone, and they are the ones abandoning. A metric that is fine on average and terrible at p75 is the most common performance situation there is.

Reproducing what you cannot see

  • Throttle honestly. 4× CPU slowdown and Fast 3G is closer to a real user than your laptop on wifi.
  • Session replay for interaction bugs — and treat it as sensitive data: mask inputs, and know what your privacy policy says.
  • Breadcrumbs beat replays for most bugs. The last twenty actions before the error usually contain the cause.
  • Feature flags so you can turn the suspect off without a deploy, which converts an incident into an investigation.

Hydration mismatches, which only happen in production

Warning: Text content did not match. Server: "12:04" Client: "12:05"

Locally the server and client run on one machine, one clock, one locale, one timezone. In production they do not. Dates, relative times, locale formatting, random ids, and anything read from localStorage are the usual causes — and the symptom is a subtree silently re-rendering on the client, undoing the benefit of server rendering exactly where it happened.

The order to work in

  1. When did it start? Check the release timeline before reading any code.
  2. Who is affected? One browser, one country, one device class narrows it enormously.
  3. Can you turn it off? Flag or roll back first, investigate second.
  4. Then reproduce, with the throttling and the data that the reports point at.
Interview answer, one sentence

"Production debugging is about getting information back: source maps and component stacks so the error is readable, releases tagged so you can ask when it started, and field vitals at p75 rather than lab numbers. React's own recurring bug is the leak — something that outlived its component because an effect had no cleanup — and a heap snapshot before and after five navigations finds it."

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 4 testsCatch it, show something safe, and report itadvanced
  • 7 testsAssemble what actually gets sent to the trackeradvanced
All 54 for this level →
A19

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. A18Previous · A18Migrating and upgrading
  2. A19you are here
  3. Next · A20Reviewing React, and knowing when not to use itAt this level you are judged on what you notice in someone else's code, and on what you argue against.6 min read

01On this page

8 sections · 0%

  1. 01Why local debugging stops working
  2. 02Errors: make them arrive with context
  3. 03Group by release, not by message
  4. 04Memory leaks
  5. 05Field data, not lab data
  6. 06Reproducing what you cannot see
  7. 07Hydration mismatches, which only happen in production
  8. 08The order to work in

[] chapters · t top