Hydration Proof

Search documentation

Find a page or section

Text content does not match server-rendered HTML

React 18's error for a text node that differs between the two renders.

"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 isWhy it differsFix guide
A time or "3 minutes ago"The clock moves between the two rendersTime-dependent values
A date or time of dayThe server formats in UTC, the browser in the user's timezoneTimezone
A number, price or date formattoLocaleString() uses the server's locale, then the browser'sLocale
An id, a shuffled item, a random greetingMath.random() returns a new valueRandom values
A name, count or cart totalRead from localStorage or fetched againStorage, data
Translated or corrected textA browser extension edited itBrowser extensions

How to fix it

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

  2. 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>;
    }
  3. 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.

  4. Suppress only what is meant to differ. For a timestamp whose first value does not matter, suppressHydrationWarning on 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.