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:
| Cause | Why production differs | Fix guide |
|---|---|---|
| The server's timezone | Servers usually run in UTC; your laptop runs in your timezone | Timezone |
| The server's locale | Numbers and dates formatted with the server's default locale | 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 |
| HTML rewritten by a CDN | Minification or email obfuscation changes the HTML after it leaves your server | CDN |
| Scripts that only run in production | Analytics, tag managers or A/B testing tools edit the DOM before React hydrates | Third-party scripts |
| Different data | Feature flags, personalization or a second fetch on the client return other values | Data |
| Your users' browsers | Extensions, translation tools and in-app browsers that you do not use | Extensions |
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
-
Run your production build locally. This tells you whether the build itself causes the mismatch:
npm run build npm run start -
Match the server's timezone and locale. For example, on Linux or macOS:
TZ=UTC LANG=en_US.UTF-8 npm run startIf the error appears, inspect timezone and locale formatting in render.
-
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; -
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 -
Check cached pages. If only cached or prerendered routes fail, look for time-dependent values in render. Their HTML may have been generated earlier.
-
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 bothThe 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 --sitemapIf the problem depends on location or language, add scenarios for the environments you support:
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.