# Debugging hydration errors

> How to debug a hydration error: read React's diff, find the component, rule out browser extensions, and trace production-only mismatches to their cause.

Source: https://hydration.jscrate.dev/docs/guides/debug-hydration-errors
Last updated: 2026-09-18

To debug a hydration error, find the element that differs, compare the server's value with the client's, and let that difference tell you the cause. React 19's development build prints a diff that does most of this. For production-only errors and silent attribute mismatches, you need the server HTML and a tool that compares it for you.

## How to debug a hydration error, step by step

A typical React debug hydration error session takes five steps:

1. **Reproduce it in a clean browser profile.** Open the page in a guest or private window with extensions turned off. If the error disappears, an extension caused it (see [browser extensions](#rule-out-browser-extensions)).
2. **Run a development build.** Production builds only print minified codes. Development builds print the component stack and, in React 19, a diff.
3. **Read the diff.** Lines starting with `+` are what the client rendered, lines starting with `-` are what the server sent.
4. **Find the component.** Follow the component stack under the diff to your file.
5. **Name the cause from the two values,** then fix it and check the page again.

## Read the diff React prints

React 19 logs a single error with a diff of the mismatch and the tree around it. This is the example from the [React 19 release post](https://react.dev/blog/2024/12/05/react-19):

```text
Uncaught Error: Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if an SSR-ed Client Component used:
...
https://react.dev/link/hydration-mismatch

  <App>
    <span>
+    Client
-    Server
```

React 18 printed separate warnings instead, for example `Warning: Text content did not match. Server: "Server" Client: "Client"`, followed by "Hydration failed because the initial UI does not match what was rendered on the server."

The two values usually give the cause away:

| The values look like                            | Likely cause                                |
| ----------------------------------------------- | ------------------------------------------- |
| Two timestamps a few hundred milliseconds apart | [The clock](https://hydration.jscrate.dev/docs/causes/time)              |
| The same time, hours apart                      | [Timezone](https://hydration.jscrate.dev/docs/causes/timezone)           |
| `1,234.5` and `1.234,5`                         | [Locale](https://hydration.jscrate.dev/docs/causes/locale)               |
| Two random-looking tokens                       | [Random values or ids](https://hydration.jscrate.dev/docs/causes/random) |
| `light` and `dark`                              | [Theme](https://hydration.jscrate.dev/docs/causes/theme)                 |
| Desktop and mobile markup                       | [Media queries](https://hydration.jscrate.dev/docs/causes/media-query)   |
| Different records or counts                     | [Data](https://hydration.jscrate.dev/docs/causes/data)                   |
| Class names like `sc-a1b2` in a different order | [CSS-in-JS](https://hydration.jscrate.dev/docs/causes/css-in-js)         |

## How to find which component causes a hydration error

When the diff is not enough, narrow it down:

1. **Follow the component stack.** The first component in it that you wrote is usually the one to look at.
2. **Compare the server HTML with the DOM.** "View page source" shows the HTML the server sent. The Elements panel shows the DOM after React changed it. Search both for the text around the error.
3. **Turn JavaScript off.** In DevTools, disable JavaScript and reload: what you see is the server's version of the page.
4. **Bisect.** Comment out half of the page's children, or return `null` from them, and reload. Keep the half that still errors and repeat. A mismatch in a layout shows up on every page, one in a page component only there.
5. **Check shared components.** A header or footer that reads the clock, a cookie banner or a theme toggle breaks every route at once.

## Rule out browser extensions

Extensions such as password managers and grammar checkers add attributes to `<body>` or `<input>` before React hydrates. The diff then shows attributes you never wrote, for example `cz-shortcut-listen="true"` or `data-new-gr-c-s-check-loaded`. Nothing in your code is wrong: test in a clean profile, and read [browser extension hydration errors](https://hydration.jscrate.dev/docs/causes/extension) for the list of attributes.

## Debug hydration errors in Next.js

When you debug a hydration error, Next.js adds its own tools on top of React's:

- **The dev overlay.** Since Next.js 15, hydration errors show the source code of the error with suggestions. Since Next.js 16.2, the overlay labels the diff with a `+ Client` / `- Server` legend ([Next.js 16.2 release notes](https://nextjs.org/blog/next-16-2)).
- **The error page.** Next.js links the message to [its hydration error page](https://nextjs.org/docs/messages/react-hydration-error), which lists the usual causes.

The overlay shows one page at a time, while you browse, in development. [Next.js dev overlay compared](https://hydration.jscrate.dev/docs/compare/nextjs-dev-overlay) covers what it does not see.

## Debug hydration mismatch reports in production

Production builds print `Minified React error #418` with no element and no diff, and React 19 production builds do not report attribute mismatches at all. To trace them:

1. Build and start the app locally in production mode, with the server's timezone and locale.
2. Turn on browser source maps (`productionBrowserSourceMaps: true` in Next.js) to map minified code to your files.
3. Test development and production builds side by side and compare what each reports.

[React hydration errors only in production](https://hydration.jscrate.dev/docs/guides/hydration-error-only-in-production) walks through each step.

## Let hydration-proof do the search

hydration-proof loads every route, compares the server HTML with what React renders, and prints the element, both values, the component, the source line and the likely cause:

```bash
npx hydration-proof test
npx hydration-proof test --mode both   # development and production builds
npx hydration-proof test --probe       # prove the cause
```

With `--probe`, each page with a value mismatch is loaded again with one thing changed at a time: the clock, the random seed, the locale, the timezone, the theme, the viewport or storage. The factor that changes the value is the cause ([probes](https://hydration.jscrate.dev/docs/probes)).

While you work on a page, `npx hydration-proof dev` opens the app with an overlay that shows the findings of every page you open, highlights the element and opens the source line in your editor ([dev overlay](https://hydration.jscrate.dev/docs/dev-overlay)).

## Related

- [React hydration errors: messages and causes](https://hydration.jscrate.dev/docs/guides/react-hydration-error)
- [Minified React error #418](https://hydration.jscrate.dev/docs/errors/minified-react-error-418)
- [Common causes of hydration errors](https://hydration.jscrate.dev/docs/causes)
- [The hydration-proof CLI](https://hydration.jscrate.dev/docs/cli)
- [Reading hydration-proof reports](https://hydration.jscrate.dev/docs/reports)
