---
# === IDENTITY ===
id: software/debugging/react-hydration-mismatch/2026
canonical_question: "How do I fix React/Next.js hydration mismatch errors?"
aliases:
  - "React hydration mismatch"
  - "Next.js hydration error"
  - "Hydration failed because the server rendered HTML didn't match the client"
  - "Text content does not match server-rendered HTML"
  - "React server client mismatch"
  - "SSR hydration error React"
  - "NextJS hydration warning"
  - "Hydration failed because the initial UI does not match"
entity_type: software_reference
domain: software > debugging > react_hydration_mismatch
region: global
jurisdiction: global
temporal_scope: 2022-2026

# === VERIFICATION ===
last_verified: 2026-05-17
confidence: 0.93
version: 2.1
first_published: 2026-02-17

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "React 19 (2024-12) — improved hydration error diffs, tolerates third-party DOM modifications"
  next_review: 2026-11-13
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "Never suppress hydration warnings globally on <html> or <body> — this hides real bugs and silently degrades to client-side rendering in production"
  - "React 18+ requires hydrateRoot() — legacy ReactDOM.hydrate() was removed in React 19"
  - "suppressHydrationWarning only works one level deep — it does not suppress warnings for child elements"
  - "In production mode React silently falls back to full client-side re-render on mismatch — the error is hidden but the performance penalty (doubled rendering) is real"
  - "jQuery and React must never manage the same DOM node — browser extensions that modify DOM trigger the same class of error"
  - "next/dynamic with ssr:false eliminates hydration risk but also eliminates SSR SEO benefits for that component"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Component renders wrong data but no hydration error appears in console"
    use_instead: "Debug React state management or prop drilling — not a hydration issue"
  - condition: "White screen with no console output (component doesn't render at all)"
    use_instead: "software/debugging/react-white-screen/2026"
  - condition: "Error message is 'Too many re-renders' (render loop, not hydration)"
    use_instead: "software/debugging/react-too-many-rerenders/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: react_version
    question: "Which React version is the project using?"
    type: choice
    options: ["React 19 (latest)", "React 18", "React 17 or older"]
  - key: framework
    question: "Which framework or meta-framework is in use?"
    type: choice
    options: ["Next.js App Router", "Next.js Pages Router", "Remix", "Gatsby", "Plain React SSR"]
  - key: error_message
    question: "What is the exact hydration error message in the browser console?"
    type: text

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/debugging/react-hydration-mismatch/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-17)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/debugging/react-white-screen/2026"
      label: "React White Screen of Death"
    - id: "software/debugging/nextjs-build-failures/2026"
      label: "Next.js Build Failures"
  solves:
    - id: "software/debugging/react-too-many-rerenders/2026"
      label: "React Too Many Re-Renders"
  often_confused_with:
    - id: "software/debugging/react-too-many-rerenders/2026"
      label: "React Too Many Re-Renders (render loop, not hydration)"
  depends_on:
    - id: "software/migrations/cra-to-nextjs/2026"
      label: "Create React App to Next.js Migration"

# === SOURCES (8 authoritative sources) ===
sources:
  - id: src1
    title: "React — hydrateRoot API Reference"
    author: React.dev
    url: https://react.dev/reference/react-dom/client/hydrateRoot
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src2
    title: "Next.js — React Hydration Error"
    author: Vercel
    url: https://nextjs.org/docs/messages/react-hydration-error
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src3
    title: "Sentry — Hydration Errors in React"
    author: Sentry
    url: https://sentry.io/answers/hydration-errors-in-react/
    type: community_resource
    published: 2025-06-01
    reliability: high
  - id: src4
    title: "The Perils of Rehydration — An Eye-Opening Exploration"
    author: Josh W. Comeau
    url: https://www.joshwcomeau.com/react/the-perils-of-rehydration/
    type: technical_blog
    published: 2024-08-01
    reliability: high
  - id: src5
    title: "React — suppressHydrationWarning"
    author: React.dev
    url: https://react.dev/reference/react-dom/components/common#suppressing-unavoidable-hydration-mismatch-errors
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src6
    title: "Next.js 15 — Improved Hydration Mismatch Errors"
    author: Vercel
    url: https://nextjs.org/blog/next-15
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src7
    title: "LogRocket — Resolving Hydration Mismatch Errors in Next.js"
    author: LogRocket
    url: https://blog.logrocket.com/resolving-hydration-mismatch-errors-next-js/
    type: technical_blog
    published: 2025-04-01
    reliability: moderate_high
  - id: src8
    title: "React v19 Release Blog Post"
    author: React.dev
    url: https://react.dev/blog/2024/12/05/react-19
    type: official_docs
    published: 2024-12-05
    reliability: authoritative
---

# How to Fix React/Next.js Hydration Mismatch Errors

## TL;DR

- **Bottom line**: Hydration mismatch occurs when server-rendered HTML differs from what React generates on the client during hydration. The 5 most common causes: (1) browser-only APIs like `window`/`localStorage` in render, (2) non-deterministic values (`Date.now()`, `Math.random()`), (3) invalid HTML nesting (`<div>` inside `<p>`), (4) browser extensions modifying DOM, (5) conditional rendering based on client-only state.
- **Key tool/command**: Wrap client-only code in `useEffect` + `useState` to defer it past hydration, or use `next/dynamic` with `{ ssr: false }`.
- **Watch out for**: Accessing `typeof window !== 'undefined'` in render — it returns `false` on server and `true` on client, causing mismatch. Use `useEffect` instead.
- **Works with**: React 18+, React 19, Next.js 13/14/15 (App Router and Pages Router), Gatsby, Remix.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Never suppress hydration warnings globally on `<html>` or `<body>` — this hides real bugs and silently degrades to client-side rendering in production. [src5]
- React 18+ requires `hydrateRoot()` — legacy `ReactDOM.hydrate()` was removed in React 19. [src1, src8]
- `suppressHydrationWarning` only works one level deep — it does not suppress warnings for child elements, only the element's own attributes and text content. [src5]
- In production mode, React silently falls back to full client-side re-render on mismatch — the error is hidden but the performance penalty (doubled rendering, lost SSR benefits) is real. [src1, src6]
- `next/dynamic` with `ssr: false` eliminates hydration risk but also eliminates SSR SEO benefits for that component — only use for truly client-only content (maps, charts, editors). [src2, src7]
- jQuery, third-party scripts, and browser extensions that modify the DOM during or before hydration trigger the same class of error — React 19 tolerates third-party `<script>`, `<link>`, and `<style>` tags, but older React versions do not. [src8]

## Quick Reference

| # | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | Browser API in render (`window`, `localStorage`) | ~30% of cases | `Text content does not match server-rendered HTML` | Wrap in `useEffect` + `useState` [src1, src4] |
| 2 | Non-deterministic values (`Date.now()`, `Math.random()`) | ~20% of cases | `Expected server HTML to contain a matching text` | Render placeholder on server, set real value in `useEffect` [src1, src3] |
| 3 | Invalid HTML nesting (`<div>` inside `<p>`, nested `<a>`) | ~15% of cases | `In HTML, <div> cannot be a descendant of <p>` | Fix HTML structure — use `<span>` or restructure [src2, src3] |
| 4 | Browser extensions / CDN modifying DOM | ~10% of cases | Mismatch on elements you didn't write | Test in incognito mode; disable Cloudflare Auto Minify [src2, src7] |
| 5 | Conditional rendering from client-only state | ~10% of cases | Component renders differently on server vs client | Use `useEffect` to toggle visibility after mount [src4, src8] |
| 6 | Third-party scripts injecting DOM nodes | ~5% of cases | Extra `<script>` or `<style>` tags in head/body | Move scripts to `useEffect` or use `next/script` [src2, src6] |
| 7 | CSS-in-JS style extraction mismatch | ~4% of cases | `className` differs between server and client | Configure SSR style extraction (e.g., `styled-components` SSR setup) [src7] |
| 8 | Stale cached HTML served with new JS bundle | ~3% of cases | Mismatch after deployment | Purge CDN cache; set proper `Cache-Control` headers [src7] |
| 9 | Whitespace or text node differences | ~2% of cases | `Text content does not match` on whitespace | Remove extra whitespace in JSX; check template literals [src3] |
| 10 | Date/number formatting with locale differences | ~1% of cases | Number or date formatted differently | Use `Intl` APIs with explicit locale or defer to `useEffect` [src4] |

## Decision Tree

> Full script: [decision-tree.txt](scripts/decision-tree.txt) (25 lines)

```
START
├── Error message contains "cannot be a descendant of"?
│   ├── YES → Invalid HTML nesting. Fix: restructure JSX (no <div> in <p>, no nested <a>) [src2]
│   └── NO ↓
├── Error mentions specific text content mismatch?
│   ├── YES → Check if mismatched value is dynamic (date, random, locale)
│   │   ├── Dynamic value → Wrap in useEffect + useState [src1, src4]
│   │   └── Static value → Check for browser API usage (window, localStorage) ↓
│   └── NO ↓
├── Mismatch on elements you didn't write (extra tags)?
│   ├── YES → Browser extension or third-party script. Test in incognito [src2, src7]
│   └── NO ↓
├── Using `typeof window !== 'undefined'` in render logic?
│   ├── YES → Move to useEffect — this check differs server vs client [src4]
│   └── NO ↓
├── Using CSS-in-JS (styled-components, Emotion)?
│   ├── YES → Configure SSR style extraction. Check className mismatch [src7]
│   └── NO ↓
├── Error only appears after deployment (not in dev)?
│   ├── YES → Stale cache. Purge CDN, rebuild, verify Cache-Control [src7]
│   └── NO ↓
├── Using Next.js App Router?
│   ├── YES → Ensure client-only components have "use client" directive [src2, src6]
│   └── NO ↓
└── DEFAULT → Use React DevTools to compare server HTML (View Source) vs client DOM [src6]
```

## Step-by-Step Guide

### 1. Identify the mismatched content

Open browser DevTools Console. React 19 and Next.js 15 show a detailed diff of server vs client HTML with source code locations and fix suggestions. For older versions, the error message points to the mismatched text or element. [src6, src8]

```bash
# View the raw server HTML (before hydration) in browser
# Right-click → View Page Source (NOT Inspect Element)
# Compare this HTML with what React renders in the DOM
```

**Verify**: Check if the error appears in the console → look for `Hydration failed` or `Text content does not match`.

### 2. Check for browser-only API usage in render

Search your component for any usage of `window`, `document`, `localStorage`, `sessionStorage`, `navigator`, or other browser-only globals outside of `useEffect`. [src1, src4]

```jsx
// ❌ WRONG — window doesn't exist on the server
function ThemeToggle() {
  const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  return <button>{isDark ? '🌙' : '☀️'}</button>;
}

// ✅ CORRECT — defer to useEffect
function ThemeToggle() {
  const [isDark, setIsDark] = useState(false); // same on server + client initially
  useEffect(() => {
    setIsDark(window.matchMedia('(prefers-color-scheme: dark)').matches);
  }, []);
  return <button>{isDark ? '🌙' : '☀️'}</button>;
}
```

**Verify**: Component renders without hydration error → same content on server and client initial render.

### 3. Fix invalid HTML nesting

Check JSX for elements that violate HTML content model rules. Common violations: `<div>` inside `<p>`, `<p>` inside `<p>`, nested `<a>` tags, block elements inside inline elements. [src2, src3]

```jsx
// ❌ WRONG — <div> cannot be a descendant of <p>
<p>Hello <div className="badge">World</div></p>

// ✅ CORRECT — use <span> for inline or restructure
<p>Hello <span className="badge">World</span></p>

// ❌ WRONG — nested anchor tags
<a href="/page"><a href="/other">Link</a></a>

// ✅ CORRECT — single anchor
<a href="/page">Link</a>
```

**Verify**: Run `npx next build` → no hydration warnings in build output.

### 4. Handle non-deterministic values

Values like dates, random numbers, and IDs change between server and client render. Render a consistent placeholder first, then update client-side. [src1, src3]

```jsx
// ❌ WRONG — different time on server vs client
function Timestamp() {
  return <span>{new Date().toLocaleTimeString()}</span>;
}

// ✅ CORRECT — show placeholder until client mounts
function Timestamp() {
  const [time, setTime] = useState('');
  useEffect(() => {
    setTime(new Date().toLocaleTimeString());
    const timer = setInterval(() => setTime(new Date().toLocaleTimeString()), 1000);
    return () => clearInterval(timer);
  }, []);
  return <span>{time || 'Loading...'}</span>;
}
```

**Verify**: `View Page Source` shows `Loading...` → after hydration shows the actual time.

### 5. Use dynamic imports for client-only components

For components that simply cannot work on the server (maps, charts, editors), use Next.js dynamic import with SSR disabled. [src2, src7]

```jsx
import dynamic from 'next/dynamic';

// Component only loads on the client — no hydration mismatch possible
const MapView = dynamic(() => import('./MapView'), {
  ssr: false,
  loading: () => <div className="map-placeholder">Loading map…</div>,
});

export default function Page() {
  return <MapView lat={51.5} lng={-0.1} />;
}
```

**Verify**: No hydration error → map renders only on client after JavaScript loads.

### 6. Create a reusable ClientOnly wrapper

Build a generic wrapper component for any client-only content. [src4, src8]

```jsx
'use client';
import { useState, useEffect } from 'react';

function ClientOnly({ children, fallback = null }) {
  const [mounted, setMounted] = useState(false);
  useEffect(() => setMounted(true), []);
  return mounted ? children : fallback;
}

// Usage
<ClientOnly fallback={<span>--</span>}>
  <span>{window.innerWidth}px</span>
</ClientOnly>
```

**Verify**: Server HTML shows fallback → client renders the real content after hydration.

## Code Examples

### Next.js App Router: Fixing localStorage-based theme

> Full script: [next-js-app-router-fixing-localstorage-based-theme.jsx](scripts/next-js-app-router-fixing-localstorage-based-theme.jsx) (31 lines)

```jsx
// Input:  Component reading user's theme preference from localStorage
// Output: No hydration mismatch — consistent server/client render
'use client';
import { useState, useEffect } from 'react';
export default function ThemeProvider({ children }) {
# ... (see full script)
```

### Next.js Pages Router: Fixing window-dependent rendering

> Full script: [next-js-pages-router-fixing-window-dependent-rende.jsx](scripts/next-js-pages-router-fixing-window-dependent-rende.jsx) (28 lines)

```jsx
// Input:  Component that conditionally renders based on viewport size
// Output: No hydration mismatch — deferred client measurement
import { useState, useEffect } from 'react';
function useWindowSize() {
  const [size, setSize] = useState({ width: 0, height: 0 });
# ... (see full script)
```

### React + Remix: Handling user agent detection

```jsx
// Input:  Component detecting mobile vs desktop from user agent
// Output: Hydration-safe implementation using server-side detection

import { useLoaderData } from '@remix-run/react';
import { json } from '@remix-run/node';

// Server: detect device from request headers
export async function loader({ request }) {
  const ua = request.headers.get('user-agent') || '';
  const isMobile = /iPhone|iPad|Android|Mobile/i.test(ua);
  return json({ isMobile });
}

// Client: use the same value the server used — no mismatch
export default function Layout() {
  const { isMobile } = useLoaderData();
  return (
    <div className={isMobile ? 'mobile-layout' : 'desktop-layout'}>
      {isMobile ? <MobileNav /> : <DesktopNav />}
    </div>
  );
}
```

### React 19: Using the improved error diagnostics

```jsx
// Input:  React 19 app with hydration mismatch
// Output: Detailed error diff in console with source code location

// React 19 automatically provides:
// 1. A single consolidated error (not multiple)
// 2. Diff showing exact server vs client HTML
// 3. Source code location of the mismatched component
// 4. Suggested fix in the error message

// To enable maximum diagnostic detail in development:
import { hydrateRoot } from 'react-dom/client';

const root = hydrateRoot(document.getElementById('root'), <App />, {
  onRecoverableError(error, errorInfo) {
    // Log hydration recovery errors to your monitoring service
    console.error('Hydration recovery:', error);
    console.error('Component stack:', errorInfo.componentStack);
  },
});
```

## Anti-Patterns

### Wrong: Using typeof window check in render

```jsx
// ❌ BAD — this returns false on server and true on client [src4]
function MyComponent() {
  if (typeof window !== 'undefined') {
    return <div>{window.innerWidth}px wide</div>;
  }
  return <div>Loading</div>;
}
// Server renders "Loading", client renders "1024px wide" → MISMATCH
```

### Correct: Defer client-only content with useEffect

```jsx
// ✅ GOOD — both server and client render the same initial HTML [src4, src8]
function MyComponent() {
  const [width, setWidth] = useState(null);
  useEffect(() => {
    setWidth(window.innerWidth);
  }, []);
  return <div>{width !== null ? `${width}px wide` : 'Loading'}</div>;
}
// Server renders "Loading", client initially renders "Loading" too → NO MISMATCH
// After useEffect: updates to "1024px wide"
```

### Wrong: Rendering dates directly

```jsx
// ❌ BAD — different timestamps on server vs client [src1, src3]
function Footer() {
  return <p>Page rendered at {new Date().toISOString()}</p>;
}
```

### Correct: Use suppressHydrationWarning or useEffect

```jsx
// ✅ GOOD (option 1) — suppress for intentionally different timestamps [src5]
function Footer() {
  return <p suppressHydrationWarning>Page rendered at {new Date().toISOString()}</p>;
}

// ✅ GOOD (option 2) — defer to client [src8]
function Footer() {
  const [time, setTime] = useState('');
  useEffect(() => setTime(new Date().toISOString()), []);
  return <p>Page rendered at {time}</p>;
}
```

### Wrong: Invalid HTML nesting in JSX

```jsx
// ❌ BAD — browser auto-corrects this, React complains [src2, src3]
function Card() {
  return (
    <p>
      <div className="card-body">  {/* div inside p is invalid HTML */}
        <h2>Title</h2>
      </div>
    </p>
  );
}
```

### Correct: Use valid HTML structure

```jsx
// ✅ GOOD — proper semantic HTML [src2]
function Card() {
  return (
    <div>
      <div className="card-body">
        <h2>Title</h2>
      </div>
    </div>
  );
}
```

### Wrong: Suppressing all hydration warnings globally

```jsx
// ❌ BAD — hides real bugs, not a fix [src5]
// Some developers wrap entire app with suppressHydrationWarning
<html suppressHydrationWarning>
  <body suppressHydrationWarning>
    <App />
  </body>
</html>
```

### Correct: Fix the root cause, suppress only specific intentional mismatches

```jsx
// ✅ GOOD — only suppress on the specific element with known, intentional mismatch [src5]
<time suppressHydrationWarning dateTime={new Date().toISOString()}>
  {new Date().toLocaleDateString()}
</time>
// Use sparingly — only for truly unavoidable cases like live timestamps
```

## Common Pitfalls

- **`typeof window` guard in render logic**: Returns `false` on server / `true` on client — the guard itself causes the mismatch. Fix: use `useEffect` + `useState`, not conditional rendering. [src4]
- **Third-party component libraries without SSR support**: Libraries like some rich text editors, maps, or charting tools access `window` internally. Fix: wrap in `next/dynamic` with `ssr: false`. [src2, src7]
- **Cloudflare Auto Minify**: Strips whitespace from server HTML, causing text node mismatches. Fix: disable HTML Auto Minify in Cloudflare dashboard → Speed → Optimization. [src7]
- **Browser extensions injecting DOM nodes**: Extensions like Grammarly, ad blockers, and password managers add elements to the DOM. Fix: test in incognito; these are not your bugs — React 19 handles third-party `<script>` and `<link>` tags gracefully. [src2, src8]
- **Styled-components without SSR config**: Generates different class names on server vs client without proper `ServerStyleSheet` setup. Fix: follow the [styled-components SSR guide](https://styled-components.com/docs/advanced#server-side-rendering). [src7]
- **Using `useId()` incorrectly across server/client trees**: React's `useId()` generates deterministic IDs, but only if the component tree is identical. Conditional rendering that changes tree shape breaks it. Fix: keep the component tree consistent between server and client. [src1]
- **next-themes without mounted guard**: The popular `next-themes` library causes hydration mismatch if you render theme-dependent content without checking `mounted` state first. Fix: use the `mounted` check from `useTheme()` before rendering theme-specific UI. [src7]
- **iOS Safari auto-detection of phone numbers, dates, and emails**: iOS wraps detected patterns in `<a>` tags after the page loads, creating extra anchor elements not present in server HTML. Fix: add `<meta name="format-detection" content="telephone=no, date=no, email=no, address=no">` to your document head. [src2]

## Diagnostic Commands

```bash
# View raw server-rendered HTML (before hydration)
# In browser: Ctrl+U (View Source) or curl the page
curl -s https://localhost:3000/your-page | head -100

# Build and test in production mode (some mismatches only appear in prod)
npx next build && npx next start

# Search codebase for common hydration mismatch causes
grep -rn "typeof window" --include="*.tsx" --include="*.jsx" src/
grep -rn "localStorage\|sessionStorage" --include="*.tsx" --include="*.jsx" src/
grep -rn "document\." --include="*.tsx" --include="*.jsx" src/
grep -rn "navigator\." --include="*.tsx" --include="*.jsx" src/
grep -rn "Date.now\|new Date()" --include="*.tsx" --include="*.jsx" src/
grep -rn "Math.random" --include="*.tsx" --include="*.jsx" src/

# Check for invalid HTML nesting (install html-validate)
npx html-validate --ext .html,.tsx,.jsx src/

# Test in incognito mode to rule out browser extensions
# Chrome: Ctrl+Shift+N → navigate to page → check console
```

## Version History & Compatibility

| Version | Hydration Behavior | Key Changes |
|---|---|---|
| React 19 + Next.js 15 | Current | Improved error diffs with source code + fix suggestions, tolerates third-party DOM modifications, selective hydration, `onRecoverableError` callback, single consolidated error message [src6, src8] |
| React 18 + Next.js 14 | Supported | `hydrateRoot` API, streaming SSR, automatic batching, Suspense for SSR [src1] |
| React 18 + Next.js 13 | Supported | App Router introduced, Server Components reduce hydration scope [src2] |
| React 17 + Next.js 12 | Legacy | `ReactDOM.hydrate()`, less descriptive error messages |
| React 16.x | EOL | `ReactDOM.hydrate()` — warnings only, no enforcement |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Console shows "Hydration failed" or "Text content does not match" | Component renders wrong data (no hydration error) | Debug state management / props |
| Error mentions "cannot be a descendant of" | Component doesn't render at all (white screen) | See React white screen guide |
| App works in dev but breaks in prod build | Error is "Too many re-renders" | See re-renders guide |
| Server HTML (View Source) differs from client DOM | Server returns 500 error (no HTML at all) | Debug server-side error handling |

## Important Caveats

- React 19 improved hydration resilience: it now gracefully handles third-party `<script>`, `<link>`, and `<style>` tags injected by browser extensions. Older React versions throw hard errors on these. If React 19 needs to re-render the entire document due to an unrelated mismatch, it leaves third-party stylesheets in place. [src8]
- `suppressHydrationWarning` only works one level deep — it suppresses the warning for that element's attributes and text content, not for its children. [src5]
- In development mode, React 18+ logs hydration mismatches as errors (not warnings). In production, React silently falls back to client-side rendering, which hurts performance — the mismatch is hidden but the penalty is real. [src1]
- Next.js App Router (`app/`) uses React Server Components by default. Only components with `"use client"` directive are hydrated. Moving logic to Server Components eliminates hydration risk entirely for those components. [src2, src6]
- The `useEffect` pattern introduces a flash of default content (FODC). For theme switching, consider using a blocking `<script>` in `<head>` to set the `data-theme` attribute before React hydrates. [src4]
- React 19 introduces `onRecoverableError` callback on `hydrateRoot` — use this to log hydration recovery events to monitoring services without crashing the app. [src8]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [React White Screen of Death](/software/debugging/react-white-screen/2026)
- [React Too Many Re-Renders](/software/debugging/react-too-many-rerenders/2026)
- [Next.js Build Failures](/software/debugging/nextjs-build-failures/2026)
- [Create React App to Next.js Migration](/software/migrations/cra-to-nextjs/2026)
