# Hydration errors only in production

> A React hydration error only in production usually means different data, timezone or HTML rewrites on the server. How to reproduce it, read #418 and fix it.

Source: https://hydration.jscrate.dev/docs/guides/hydration-error-only-in-production
Last updated: 2026-09-18

A React hydration error only in production usually means the production server renders something your machine does not: another timezone or locale, cached HTML, different data, or HTML that a CDN rewrites on the way. Production builds also say less, with minified codes, no diff and, in React 19, no report at all for attribute mismatches.

## Why you get a React hydration error only in production

A random React hydration error 418 that shows up only on prod (on Vercel or any other host) usually has one of these causes:

| Cause                               | Why production differs                                                                                          | Fix guide                                              |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| The server's timezone               | Servers usually run in UTC; your laptop runs in your timezone                                                   | [Timezone](https://hydration.jscrate.dev/docs/causes/timezone)                      |
| The server's locale                 | Numbers and dates formatted with the server's default locale                                                    | [Locale](https://hydration.jscrate.dev/docs/causes/locale)                          |
| Cached or prerendered HTML          | A statically generated page was rendered at build time; "today" or "3 minutes ago" is days old when it hydrates | [Time](https://hydration.jscrate.dev/docs/causes/time)                              |
| HTML rewritten by a CDN             | Minification or email obfuscation changes the HTML after it leaves your server                                  | [CDN](https://hydration.jscrate.dev/docs/causes/cdn)                                |
| Scripts that only run in production | Analytics, tag managers or A/B testing tools edit the DOM before React hydrates                                 | [Third-party scripts](https://hydration.jscrate.dev/docs/causes/third-party-script) |
| Different data                      | Feature flags, personalization or a second fetch on the client return other values                              | [Data](https://hydration.jscrate.dev/docs/causes/data)                              |
| Your users' browsers                | Extensions, translation tools and in-app browsers that you do not use                                           | [Extensions](https://hydration.jscrate.dev/docs/causes/extension)                   |

Some of these also happen in development but go unnoticed there. React 19 development builds warn about an attribute mismatch; production builds keep the server's attribute silently and never log it.

## What production builds show you

React's production build replaces messages with codes:

```text
Minified React error #418; visit https://react.dev/errors/418
Minified React error #423; visit https://react.dev/errors/423
Minified React error #425; visit https://react.dev/errors/425
```

- [#418](https://hydration.jscrate.dev/docs/errors/minified-react-error-418): hydration failed. In React 19 it means "Hydration failed because the server rendered HTML didn't match the client", and React renders the tree again on the client.
- [#423](https://hydration.jscrate.dev/docs/errors/minified-react-error-423) and [#425](https://hydration.jscrate.dev/docs/errors/minified-react-error-425): React 18's messages for a root that switched to client rendering and for text that did not match.

None of them names the element. [Minified React error codes](https://hydration.jscrate.dev/docs/errors/minified-react-error-codes) lists the others you may see alongside them.

## How to debug a Next.js hydration error that only shows up in production

1. **Build and run production locally.** Most of these errors reproduce with a production build on your machine:

   ```sh
   npm run build
   npm run start
   ```

2. **Use the server's timezone and locale.** Start the server the way your host does. On Linux and macOS:

   ```sh
   TZ=UTC LANG=en_US.UTF-8 npm run start
   ```

   If the error appears now, the cause is [timezone](https://hydration.jscrate.dev/docs/causes/timezone) or [locale](https://hydration.jscrate.dev/docs/causes/locale) formatting.

3. **Turn on browser source maps.** They map minified code back to your files:

   ```ts title="next.config.ts"
   import type { NextConfig } from "next";

   const nextConfig: NextConfig = {
     productionBrowserSourceMaps: true,
   };

   export default nextConfig;
   ```

4. **Compare what the origin sends with what the browser gets.** If they differ, something between them rewrites the HTML:

   ```sh
   curl -s https://origin.example.com/pricing > origin.html
   curl -s https://www.example.com/pricing > edge.html
   diff origin.html edge.html
   ```

5. **Check cached pages.** If only prerendered or cached pages fail, look for the clock in render: the HTML was made at build time.
6. **Test both builds with hydration-proof** (below). Findings that appear only in the production build are marked as such.

## Test production builds before you deploy

`hydration-proof test` tests the production build by default. `--mode both` starts a production build and the development server and tests every route in each, so you get React's full development messages and the production behavior in one run:

```bash
npx hydration-proof test --mode both
```

It compares every attribute with the props React renders on the client, so it also finds the attribute mismatches React 19 never reports in production. To test a deployed preview instead, point it at the URL:

```bash
npx hydration-proof test --url https://preview.example.com --sitemap
```

Production servers rarely share your timezone. Add scenarios for the environments your users and your servers have:

```ts title="hydration-proof.config.ts"
import { defineConfig } from "hydration-proof";

export default defineConfig({
  scenarios: [
    { name: "default" },
    { name: "utc", timezoneId: "UTC", locale: "en-US" },
    { name: "berlin", timezoneId: "Europe/Berlin", locale: "de-DE" },
  ],
});
```

See [scenarios](https://hydration.jscrate.dev/docs/scenarios) and [the environment matrix](https://hydration.jscrate.dev/docs/environment-matrix) for more combinations, and [CI](https://hydration.jscrate.dev/docs/ci) to run this on every pull request.

## Related

- [Debugging hydration errors](https://hydration.jscrate.dev/docs/guides/debug-hydration-errors)
- [Minified React error #418](https://hydration.jscrate.dev/docs/errors/minified-react-error-418)
- [Cloudflare Auto Minify and other CDN rewrites](https://hydration.jscrate.dev/docs/causes/cdn)
- [Timezone hydration mismatches](https://hydration.jscrate.dev/docs/causes/timezone)
- [Detect hydration errors in CI](https://hydration.jscrate.dev/docs/ci)
