DEV Community

The Agent Ledger
The Agent Ledger

Posted on Originally published at theagentledger.ai

Fixing "Hydration failed because the server rendered text didn't match the client" from dates in Next.js (tested repro, Next 16 + React 19.3)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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" });
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.

  1. Both builds pass: npx next build exited 0 for before and after. The bug does not show at build time.
  2. Before, production (next start): the browser threw Minified React error #418 ... args[]=text. Server HTML had 1/13/2026; after hydration the page showed 14.1.2026.
  3. Before, dev (next dev): the browser threw the full "Hydration failed ..." message with the + 14.1.2026 / - 1/13/2026 diff shown above.
  4. After, production and dev: no hydration error or React error in the console; server HTML and the hydrated page both showed 1/13/2026.
  5. 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 dev and 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.50 vs 1.234,50: same cause; pass a locale to toLocaleString / 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)