If your AI-built (or hand-built) Next.js app throws Hydration failed because the server rendered text didn't match the client in the browser, a common culprit is a date formatted with toLocaleDateString() or toLocaleString() and no locale or time zone. The server and your visitor's browser print different text, and React refuses to hydrate.
We reproduced it on a small synthetic app, applied the smallest fix, and checked it in headless Chrome set to a different locale and time zone than the server. Below: the exact error, the cause, the fix, how we verified it, and what we did not test.
The exact error
In development (next dev), from our headless-browser run:
Hydration failed because the server rendered text didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
...
<time id="placed" dateTime="2026-01-13T23:30:00Z">
+ 14.1.2026
- 1/13/2026
The + line is what the browser rendered; the - line is what the server sent.
In production (next build + next start) the same problem shows only as:
Minified React error #418; visit https://react.dev/errors/418?args[]=text&args[]= for the full message
Older React/Next versions word this differently, for example "Text content does not match server-rendered HTML" (the title of the official Next.js page). We did not run those versions.
Versions we used
| Item | Version |
|---|---|
| Node.js | 20.19.2 |
| next | 16.4.2 (Turbopack) |
| react / react-dom | 19.3.1 |
| Browser | Google Chrome, headless, via playwright-core 1.64.1 |
| Server time zone | UTC |
| Browser locale / time zone | de-DE / Europe/Berlin |
| OS | Linux, single machine |
Cause
Even a "use client" component is rendered twice: once on the server to make the HTML, and again in the browser to make it interactive ("hydration"). React requires the two results to match.
new Date(x).toLocaleDateString() with no arguments uses whatever locale and time zone the current machine has. Our server was in UTC with US English, so it printed 1/13/2026. The browser was in Berlin with German, where the same moment (2026-01-13T23:30:00Z) is already 14 January, so it printed 14.1.2026. Same code, two answers, hydration error.
The same thing happens with Date.now(), new Date(), Math.random(), or typeof window checks used during render. We reproduced only the locale/time-zone date case.
Smallest working fix
Pass an explicit locale and time zone, so the server and every browser format the date identically.
Before (hydration error): app/OrderRow.js
"use client";
import { useState } from "react";
export default function OrderRow({ id, createdAt }) {
const [open, setOpen] = useState(false);
return (
<div>
<span>Order {id} placed </span>
<time id="placed" dateTime={createdAt}>{new Date(createdAt).toLocaleDateString()}</time>
<button onClick={() => setOpen(!open)}>{open ? "Hide" : "Details"}</button>
{open && <p id="details">Synthetic details</p>}
</div>
);
}
After (no hydration error)
Change the <time> line to call formatDate(createdAt) and add this at the bottom of the file:
// Same locale and time zone on the server and in every browser,
// so the first browser render matches the server HTML.
function formatDate(iso) {
return new Date(iso).toLocaleDateString("en-US", { timeZone: "UTC" });
}
Pick the locale and zone that fit your business (for example "en-US" and "America/Phoenix"). The trade-off: every visitor sees that same zone's date. If you need each visitor's own local date, the usual pattern is to render a neutral value first and switch to local formatting in useEffect after hydration. That is documented on the Next.js hydration page, but we did not test that variant.
Prompt for your AI coding tool
My Next.js App Router app shows "Hydration failed because the server rendered text didn't match the client" in the browser console. Find every place that formats dates, times, numbers or prices during render using toLocaleString, toLocaleDateString, toLocaleTimeString or Intl.DateTimeFormat/Intl.NumberFormat without an explicit locale and timeZone, and every place that uses Date.now(), new Date() with no argument, or Math.random() during render. For the formatting cases, pass an explicit locale and timeZone (use "en-US" and "UTC" unless I tell you otherwise). For the clock/random cases, compute the value on the server and pass it as a prop, or set it in useEffect. Do not change anything else, and show me the diff.
Review the diff yourself. We did not test this prompt in any AI tool.
How we verified
All runs on 2026-10-20 on a small synthetic app. Server started with TZ=UTC; headless Chrome set to locale de-DE and time zone Europe/Berlin.
-
Both builds pass:
npx next buildexited 0 for before and after. The bug does not show at build time. -
Before, production (
next start): the browser threwMinified React error #418 ... args[]=text. Server HTML had1/13/2026; after hydration the page showed14.1.2026. -
Before, dev (
next dev): the browser threw the full "Hydration failed ..." message with the+ 14.1.2026 / - 1/13/2026diff shown above. -
After, production and dev: no hydration error or React error in the console; server HTML and the hydrated page both showed
1/13/2026. - Button still works: in every run, clicking "Details" showed the details paragraph.
Both runs also logged one unrelated 404 resource error, most likely the missing favicon; we did not confirm which file it was.
Not verified
To be upfront about the limits:
- Other browsers (Firefox, Safari), real phones, and real visitors' settings; we simulated one locale/time zone in headless Chrome.
- The
useEffect"show local time after load" variant. - The other causes in the error message (
Date.now(),Math.random(),typeof window, invalid HTML nesting, browser extensions). Same family, not reproduced. - Other Next.js/React/Node versions, including older "Text content does not match" wording, and the Pages Router.
- Vercel, Netlify, Cloudflare or other hosts (host server time zones may differ from ours).
- Code generated by Cursor, Lovable, Bolt or v0; our app is hand-written and synthetic. We did not measure how often AI tools produce this pattern.
- Whether the AI prompt produces a correct diff in any tool.
Quick troubleshooting
-
Only see "Minified React error #418": run
npm run devand open the page; the dev overlay shows the full message with the+/-diff that points to the exact text. - Error goes away on your machine but users report it: your computer and server may share a time zone. Test with a browser set to a different time zone or language.
- The diff points to an attribute, not text, or you have a password manager/translate extension: try a private window with extensions off; extensions can cause this too (not tested by us).
-
Prices like
1,234.50vs1.234,50: same cause; pass a locale totoLocaleString/Intl.NumberFormat.
Security: no secrets are involved. Don't "fix" this by disabling server rendering for whole pages without understanding the SEO and performance cost.
The original guide, with the before/after files, is on The Agent Ledger: Hydration mismatch from date locale: Next.js fix.
AI disclosure: This article was written with the help of AI. The reproduction and the before/after browser runs described above were actually performed and checked against the logs before publishing.
Top comments (0)