Fix: "Hydration failed because the server rendered text didn't match the client" from dates in your AI-built Next.js app
Short answer: Your page shows a date (or number, or price) that is formatted differently on the server than in your visitor's browser, usually because of
toLocaleDateString()ortoLocaleString()with no locale or time zone. React sees the two versions disagree and throws a hydration error. The smallest fix is to give the formatter an explicit locale and time zone so both sides print the same text. Verified on a synthetic app only.
Prerequisites
- A Next.js app using the App Router (an
app/folder). - The error appears in the browser console (or the Next.js dev overlay), not during
next build. Builds still pass. - Terminal access, or your AI tool's code view.
- No keys or accounts are needed for this fix.
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.
Copyable 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
Run on 2026-10-20 in fix-03/before and fix-03/after. Server started with TZ=UTC; headless Chrome set to locale de-DE and time zone Europe/Berlin (script: pw/check.js).
- Both builds pass:
npx next buildexited 0 for before and after (before-build.log,after-build.log). 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-browser.log). - Before, dev (
next dev): the browser threw the full "Hydration failed ..." message with the+ 14.1.2026 / - 1/13/2026diff shown above (before-dev-browser.log). - After, production and dev: no hydration error or React error in the console; server HTML and the hydrated page both showed
1/13/2026(after-browser.log,after-dev-browser.log). - 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.
Files: see the downloads below.
Not verified
- 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.
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 notes
- No secrets are involved. Don't "fix" this by disabling server rendering for whole pages without understanding the SEO and performance cost.
Downloads
Synthetic example files from our test. No real keys or data.
- orderrow.diff: Before/after diff of the date component
- before-OrderRow.js: Before: component that causes the hydration error
- after-OrderRow.js: After: component with a fixed locale and time zone
- before-build.log.txt: Build log before the fix (passes; the build does not catch this)
- after-build.log.txt: Build log after the fix
- before-browser.log.txt: Headless Chrome check before the fix (hydration error #418)
- after-browser.log.txt: Headless Chrome check after the fix (no error, dates match)
- check.js: Playwright script used for the browser check