"Text content does not match server-rendered HTML" means React 18 found different text on the server and in the browser's first render. Check dates, formatted numbers and random values first. Make the initial text the same on both sides, then update changing values after hydration.
The error
In development, React 18 shows the server and client values before throwing:
Warning: Text content did not match. Server: "12:04:31" Client: "12:04:33"
Uncaught Error: Text content does not match server-rendered HTML.
Uncaught Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.In production, the message is reduced to a code:
Minified React error #425; visit https://reactjs.org/docs/error-decoder.html?invariant=425 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.React 19 uses a different message for the same problem:
Hydration failed because the server rendered text didn't match the client.See the React 19 error for its diff.
What it means
React 18 checks text as it hydrates. Its warning puts the server value first
and the browser value second. If they differ, React re-renders that part of
the page, up to the nearest <Suspense> boundary or the whole root.
Keep these limitations in mind:
- React 18 logs the warning only for the first mismatch on a page. Fix it, reload, and the next one appears.
- The error itself carries no values. In production you get #425 and nothing else, so users' consoles do not tell you which text it was.
Text content does not match server-rendered HTML (Next.js)
Next.js 13 and 14 show this React 18 error in the development overlay, often alongside "Hydration failed because the initial UI does not match…". The fix is the same as in React elsewhere. In Next.js, also check:
- Static pages keep the build's text. A page pre-rendered at build time keeps that moment's date, and every visitor's browser renders a new one.
- Server Components are not the problem. Text a Server Component renders
is never re-rendered in the browser. The mismatch is in a Client Component
(
"use client"), which renders on the server and again in the browser.
On Next.js 15 and later you get the React 19 wording instead.
Common causes
| The text is | Why it differs | Fix guide |
|---|---|---|
| A time or "3 minutes ago" | The clock moves between the two renders | Time-dependent values |
| A date or time of day | The server formats in UTC, the browser in the user's timezone | Timezone |
| A number, price or date format | toLocaleString() uses the server's locale, then the browser's | Locale |
| An id, a shuffled item, a random greeting | Math.random() returns a new value | Random values |
| A name, count or cart total | Read from localStorage or fetched again | Storage, data |
| Translated or corrected text | A browser extension edited it | Browser extensions |
How to fix it
-
Find the text. Use the values in the development warning. If you only have a production error code, run a development build or test the page with hydration-proof.
-
Share the server's value. This component reads the clock separately on each side and lets each runtime choose its own timezone:
components/last-updated.tsx "use client"; export function LastUpdated() { return <p>Updated at {new Date().toLocaleTimeString()}</p>; }Pass a timestamp from the server and set the locale and timezone explicitly:
components/last-updated.tsx "use client"; export function LastUpdated({ updatedAt }: { updatedAt: number }) { const time = new Date(updatedAt).toLocaleTimeString("en-US", { timeZone: "UTC", }); return <p>Updated at {time}</p>; } -
Update live values after hydration. Start a clock or browser-only value from a stable placeholder, then fill it in from an effect. Render the time after hydration shows the pattern.
-
Suppress only what is meant to differ. For a timestamp whose first value does not matter,
suppressHydrationWarningon that one element keeps the server text without an error. Read when suppressHydrationWarning is safe first.
Find every instance
hydration-proof checks text across your routes and reports differences as HP1001. The finding shows both values and a possible cause:
npx hydration-proof test --probe ✖ /blog 912ms 1 error
HP1001 Text differs between server and client · components/last-updated.tsx:4
server "Updated at 12:04:31" → client "Updated at 12:04:33"
Possible cause: Time-dependent value. Fix: Pass the timestamp the server used as a prop, or render the time after mount (useEffect).--probe changes one factor per reload, such as the clock or locale, to
check the cause. See probes.
The ESLint rules catch the source in the editor:
no-date-in-render,
no-random-in-render,
no-locale-without-explicit-locale
and no-timezone-without-explicit-timezone.