The Agent Ledger

← All fixes

Fix: "Hydration failed because the server rendered text didn't match the client" from dates in your AI-built Next.js app

By The Agent Ledger. Drafted with AI assistance and reviewed by the publisher. Tested 2026-10-20.

Status: Reproduced and fixed on a synthetic app, checked in a headless browser. We reproduced the error and verified the fix on 2026-10-20 with a tiny invented Next.js app on one Linux machine (Node 20.19.2, next 16.4.2, react 19.3.1), server running in UTC, and headless Google Chrome (driven by playwright-core 1.64.1) set to German locale and Berlin time. Before the fix the browser threw the hydration error and replaced the date; after the fix there was no hydration error and the date matched the server. We did NOT test other browsers, real users' devices, other Next.js versions, the Pages Router, Vercel/Netlify, or code generated by Cursor, Lovable, Bolt or v0. No real systems or private data were used.

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() or toLocaleString() 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

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).

  1. Both builds pass: npx next build exited 0 for before and after (before-build.log, after-build.log). 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 (before-browser.log).
  3. Before, dev (next dev): the browser threw the full "Hydration failed ..." message with the + 14.1.2026 / - 1/13/2026 diff shown above (before-dev-browser.log).
  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 (after-browser.log, after-dev-browser.log).
  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.

Files: see the downloads below.

Not verified

Troubleshooting

Security notes

Downloads

Synthetic example files from our test. No real keys or data.

Get The Agent Ledger every Tuesday

Free. One useful AI workflow or a tested fix for an AI-built app each week, in plain language, with what it costs and what stays manual.

Free to subscribe. One email a week, on Tuesdays. Unsubscribe any time. We use your email only to send the newsletter. Privacy