Hydration Proof

Search documentation

Find a page or section

Hydration errors only in production

Minified codes, no diff, and differences your laptop never has.

A React hydration error only in production often comes from a difference between your local setup and the deployed page. The server may use another timezone, serve cached HTML, or send HTML that a CDN changes. Production React messages also contain less detail, so the same mismatch can be harder to find.

Why you get a React hydration error only in production

If you see a random React hydration error 418 only on prod, whether on Vercel or another host, check these differences first:

CauseWhy production differsFix guide
The server's timezoneServers usually run in UTC; your laptop runs in your timezoneTimezone
The server's localeNumbers and dates formatted with the server's default localeLocale
Cached or prerendered HTMLA statically generated page was rendered at build time; "today" or "3 minutes ago" is days old when it hydratesTime
HTML rewritten by a CDNMinification or email obfuscation changes the HTML after it leaves your serverCDN
Scripts that only run in productionAnalytics, tag managers or A/B testing tools edit the DOM before React hydratesThird-party scripts
Different dataFeature flags, personalization or a second fetch on the client return other valuesData
Your users' browsersExtensions, translation tools and in-app browsers that you do not useExtensions

These problems can happen locally too. They may go unnoticed if you test only one timezone or browser. React 19 also warns about attribute mismatches in development but may leave them unreported in production.

What production builds show you

Production React uses shorter error messages:

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: The server HTML did not match the first browser render. React may render the affected tree again in the browser.
  • #423 and #425: React 18 codes for switching a root to client rendering and for text that did not match.

The code does not tell you which element changed. See minified React error codes for the other messages you may encounter.

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

  1. Run your production build locally. This tells you whether the build itself causes the mismatch:

    npm run build
    npm run start
  2. Match the server's timezone and locale. For example, on Linux or macOS:

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

    If the error appears, inspect timezone and locale formatting in render.

  3. Enable browser source maps if you need to trace minified code. In Next.js:

    next.config.ts
    import type { NextConfig } from "next";
     
    const nextConfig: NextConfig = {
      productionBrowserSourceMaps: true,
    };
     
    export default nextConfig;
  4. Compare the origin and public responses. If the HTML differs, inspect your CDN or any service between them:

    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 cached or prerendered routes fail, look for time-dependent values in render. Their HTML may have been generated earlier.

  6. Test development and production. The comparison below helps show which findings are specific to the production build.

Test production builds before you deploy

hydration-proof test checks a production build by default. Use --mode both to check the development server and production build in one run:

npx hydration-proof test --mode both

The check also compares attributes, including differences React 19 may not report in production. To check a deployed preview, pass its URL:

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

If the problem depends on location or language, add scenarios for the environments you support:

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 and the environment matrix for more combinations, and CI to run this on every pull request.