modern-react-spa

Chapter 34

Debugging Strategies

Debugging React 19.2 SPAs — React DevTools (Components + Profiler), TanStack Query Devtools, state-store devtools, production source maps, and Sentry / Datadog RUM.

Published 2026-05-23

🎯 Chapter Goal — After this chapter you can drive React DevTools (Components + Profiler), TanStack Query Devtools, Zustand/Jotai/Redux devtools, and the production observability stack (Sentry / Datadog RUM) with the same fluency you have for console.log.

🧭 Prerequisites — Ch 2 (Compiler), Ch 13 (state), Ch 14 (Query).


🔹 34.1 React DevTools — Components and Profiler

The single most useful tool in your debugging toolkit. Install the browser extension; it auto-detects React.

Components tab

  • Tree view of the rendered component tree.
  • For each component: props, state, hooks, source.
  • “Highlight updates when components render” (settings cog) — flashes a border around every component on render. Invaluable for finding render-storm sources.
  • ”✨ Memo” badge — Ch 2 §2.1 — confirms the compiler is doing its job.
  • Right-click a component → “Find DOM node” — jumps to the rendered element in the Elements panel.

Profiler tab

Covered in Ch 32.1. The key insights:

  • Record an interaction; read the flame chart.
  • “Ranked” view shows commits ordered by duration.
  • Hover any bar — “why did this render?” lists the changed props / state / hooks.
┌─ React DevTools → Profiler ────────────────────────────┐
│ ▶ Record  ⏸  ↺  ↤ ↦  Ranked / Flame                    │
│                                                        │
│ Commit at 1.2 s  ████████████████████  85 ms           │
│   <Dashboard>  ████████████████  62 ms                  │
│     <InvoiceTable>  ████████████  35 ms                 │
│       (Why? props.invoices changed; identity differs)   │
└────────────────────────────────────────────────────────┘

The “Why did this render?” annotation alone saves hours.


🔹 34.2 TanStack Query Devtools

Cross-link Ch 14.5. The panel shows:

  • Every active query, keyed by queryKey.
  • State: fresh / stale / fetching / inactive.
  • Last fetched timestamp.
  • Cached data preview.
  • Per-query refetch button.
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

<QueryClientProvider client={qc}>
  <App />
  <ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>

Use during development on any non-trivial data-fetching screen. The “why is this still loading?” / “why didn’t this refetch?” questions have visible answers here.


🔹 34.3 Zustand / Jotai / Redux devtools

Zustand — via the devtools middleware

import { devtools } from 'zustand/middleware';
const useStore = create<State>()(devtools((set) => ({ /* … */ }), { name: 'acme' }));

Now your Zustand store shows up in the Redux DevTools extension. Action log, state diff, time-travel.

Jotai — useAtomsDevtools

import { useAtomsDevtools } from 'jotai-devtools';

const AtomsDevtools = () => { useAtomsDevtools('acme'); return null; };

<JotaiProvider><AtomsDevtools /><App /></JotaiProvider>

Atoms appear in Redux DevTools. Slightly less rich than Zustand’s integration but functional.

Redux Toolkit — Redux DevTools out of the box

configureStore enables it by default. Just install the browser extension.


🔹 34.4 Source maps in production (safely)

The dilemma: source maps make production stack traces readable. But they expose your source code to anyone who opens DevTools.

The 2026 standard practice:

  • Generate source maps. (build.sourcemap: 'hidden' in Vite — Ch 17 §17.2.)
  • Don’t deploy them to the public web.
  • Upload them to your error-reporting tool (Sentry / Datadog / Rollbar) — the tool uses them server-side to demangle stack traces.
// vite.config.ts
export default defineConfig({
  build: { sourcemap: 'hidden' },   // emits .map files, no // # sourceMappingURL=
});
# CI step
- name: Upload sourcemaps to Sentry
  run: |
    npx sentry-cli sourcemaps inject ./dist
    npx sentry-cli sourcemaps upload ./dist --release "$GIT_SHA"
- name: Remove sourcemaps from deploy
  run: rm -f dist/**/*.map

Users see hashed minified code. You see readable stack traces in your error dashboard. Best of both.

🔹 34.5 Sentry / Datadog RUM integration

Sentry

import * as Sentry from '@sentry/react';

Sentry.init({
  dsn: env.VITE_SENTRY_DSN,
  release: env.VITE_GIT_SHA,
  integrations: [
    Sentry.browserTracingIntegration(),
    Sentry.replayIntegration({ maskAllText: false, maskAllInputs: true }),
  ],
  tracesSampleRate: 0.1,           // 10% of transactions
  replaysSessionSampleRate: 0.01,  // 1% session replay
  replaysOnErrorSampleRate: 1.0,   // 100% on error
});

// In <App>:
<Sentry.ErrorBoundary fallback={<ErrorPage />}>
  <Routes />
</Sentry.ErrorBoundary>

The Replay feature is the killer addition — Sentry records DOM mutations and replays them as video around an error. You see exactly what the user did.

Datadog RUM

import { datadogRum } from '@datadog/browser-rum';

datadogRum.init({
  applicationId: env.VITE_DATADOG_APP_ID,
  clientToken: env.VITE_DATADOG_TOKEN,
  site: 'datadoghq.com',
  service: 'acme-web',
  env: env.VITE_ENV,
  version: env.VITE_GIT_SHA,
  sampleRate: 100,
  sessionReplaySampleRate: 20,
  trackInteractions: true,
});

Pairs naturally with Datadog APM if your backend is also on Datadog — end-to-end traces across the boundary.

Pick one

ToolStrengthsBest for
SentryError-first; replay; SaaS or self-hostedTeams prioritising error visibility
DatadogUnified frontend + backend; expensiveTeams already on Datadog APM
RollbarOlder; still solidLegacy installs
OpenTelemetry browser SDKVendor-agnostic; less polishedAvoiding lock-in

Don’t run two. The double instrumentation is wasted.


🪤 Common Pitfalls

  1. Source maps publicly accessible → competitive intel handed out.
  2. Sentry without releases → stack traces don’t match the right source.
  3. 100 % sampling → expensive; sample sensibly.
  4. Replay without input masking → PII leaks into your error tool.
  5. Forgetting to filter known-noisy errors (browser extensions throwing) → real bugs lost in noise.
  6. Logging giant objects to console → hangs the dev tools.

✅ Recap

  • React DevTools is the most useful tool you’ll use; learn the Profiler.
  • TanStack Query Devtools for data; Zustand/Jotai/Redux devtools for state.
  • Source maps hidden + uploaded to error tool; never deployed publicly.
  • Pick one observability stack — Sentry default; Datadog if you’re already there.

🔗 Further Reading

In the book — not on the site

Each topic has an 🧠 Under-the-hood subsection — the algorithm, the data structures, what React DevTools surfaces, debugging recipes. Plus a 🧪 hands-on lab per chapter with a starter repo. Reserved for the book.

Topics in this chapter (5)
  1. 34.1 React DevTools — Components and Profiler
  2. 34.2 TanStack Query Devtools
  3. 34.3 Zustand / Jotai / Redux devtools
  4. 34.4 Source maps in production (safely)
  5. 34.5 Sentry / Datadog RUM integration