Hydration Proof

Search documentation

Find a page or section

HP1001: Text differs between server and client

HP1001 (text mismatch) means the server HTML has different text than React rendered in the browser. The likely causes, the fix for each, and an example.

HP1001 (text-mismatch) means the text in the server HTML differs from the text React rendered in the browser's first render. The finding gives both values, the element and, in development builds, the line that rendered it. Find what that component reads that differs between the two renders (the clock, the locale, browser storage) and make it the same.

CodeHP1001
Nametext-mismatch
Default severityError
GroupDOM mismatches
What it meansThe server HTML contains different text than the browser rendered during hydration.

What HP1001 (text-mismatch) means

React expects the first client render to produce exactly the text the server sent. When a text node differs, React discards the server HTML up to the nearest Suspense boundary and renders that part again on the client, which is slower and resets state.

hydration-proof finds the difference in two ways:

  • It compares the DOM right before and inside the hydration commit, which shows the text React replaced.
  • It compares the server text of every element React reused with the text React renders for it, which also catches text React left in place.

When the text sits inside a branch React re-rendered, the finding says so in its evidence ("React discarded the server HTML of <section> and rendered it again on the client"), and no separate HP1010 is reported. If only whitespace differs, the code is HP1015 instead.

The React error it matches

In a development build, React usually reports the same problem, and hydration-proof attaches React's message to the finding as evidence. These are the messages for a text difference:

Hydration failed because the server rendered text didn't match the client.
Text content does not match server-rendered HTML.
Warning: Text content did not match. Server: "5:00 AM" Client: "10:00 AM"
Minified React error #418; visit https://react.dev/errors/418

Text content does not match server-rendered HTML covers the React 18 wording, and the server rendered HTML didn't match the client the React 19 one.

Likely causes

Each finding names its most likely cause with a confidence score. These are the causes behind HP1001 in the package's own test pages:

CauseWhat differs
Time-dependent valueDate.now() or new Date() read during render
Timezone differenceA date formatted in the server's and the browser's timezone
Locale-dependent formattingtoLocaleString() with the runtime's default locale
Random valueMath.random() or crypto.randomUUID() in render
Browser storagelocalStorage read behind a typeof window check
Media querymatchMedia or the window size read during render
Browser-only APInavigator.userAgent and other browser globals
Different dataThe client fetched the data again and got a new result

How to fix it

  • Render exactly the same text on the server and in the first client render.
  • Move browser-only or time-dependent values into useEffect, or compute them on the server and pass them down as props.

For example, a greeting that reads localStorage renders "guest" on the server and the stored name in the browser:

welcome.tsx
"use client";
 
export function Welcome() {
  const name =
    typeof window === "undefined"
      ? "guest"
      : (localStorage.getItem("name") ?? "guest");
 
  return <p>Welcome back, {name}</p>;
}

Render the value both sides agree on first, and read storage after hydration:

welcome.tsx
"use client";
 
import { useEffect, useState } from "react";
 
export function Welcome() {
  const [name, setName] = useState("guest");
 
  useEffect(() => {
    setName(localStorage.getItem("name") ?? "guest");
  }, []);
 
  return <p>Welcome back, {name}</p>;
}

The cause page for your finding has the fix that fits it: pass the server's time as a prop, pass an explicit locale and timeZone, or send the data the server rendered with. hydration-proof test --probe reloads the page with one factor changed at a time (clock, random seed, locale, timezone, theme, viewport, storage) and turns the likely cause into a proven one; see probes.

Prevent it with ESLint

The ESLint plugin reports the usual sources in render code: no-date-in-render, no-random-in-render, no-locale-without-explicit-locale, no-timezone-without-explicit-timezone, no-storage-in-initial-render and no-window-render-branch.

When the difference is intentional

For a value that is meant to differ, such as a live clock, put suppressHydrationWarning on the element that holds it: React keeps the server text, and hydration-proof lists the difference as HP6001 (info) instead. ignore.textPatterns ignores a text difference when both values are equal after removing the patterns, for example /\d{2}:\d{2}/ for times. See ignoring findings.

Example

  ✖ /status 842ms  1 error
    HP1001 Text differs between server and client  (time-dependent value, 97%)
      #rendered-at  in RenderedAt
      server: "Rendered at 1767225600000"
      client: "Rendered at 1767225600412"
      app/status/rendered-at.tsx:4:10
      → The server and the browser render at different moments. Pass the timestamp the server used as a prop, or render the time after mount (useEffect).

The last line is the fix for the likely cause. The HTML report adds the code around that line, React's message and a screenshot with the element outlined.