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

Source: https://hydration.jscrate.dev/docs/issues/hp1001
Last updated: 2026-09-18

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.

| | |
| --- | --- |
| Code | `HP1001` |
| Name | `text-mismatch` |
| Default severity | Error |
| Group | DOM mismatches |
| What it means | The 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](https://hydration.jscrate.dev/docs/issues/hp1010) is
reported. If only whitespace differs, the code is
[HP1015](https://hydration.jscrate.dev/docs/issues/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:

```text
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](https://hydration.jscrate.dev/docs/errors/text-content-does-not-match-server-rendered-html)
covers the React 18 wording, and
[the server rendered HTML didn't match the client](https://hydration.jscrate.dev/docs/errors/hydration-failed-server-rendered-html-didnt-match-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:

| Cause                                              | What differs                                                |
| -------------------------------------------------- | ----------------------------------------------------------- |
| [Time-dependent value](https://hydration.jscrate.dev/docs/causes/time)          | `Date.now()` or `new Date()` read during render             |
| [Timezone difference](https://hydration.jscrate.dev/docs/causes/timezone)       | A date formatted in the server's and the browser's timezone |
| [Locale-dependent formatting](https://hydration.jscrate.dev/docs/causes/locale) | `toLocaleString()` with the runtime's default locale        |
| [Random value](https://hydration.jscrate.dev/docs/causes/random)                | `Math.random()` or `crypto.randomUUID()` in render          |
| [Browser storage](https://hydration.jscrate.dev/docs/causes/storage)            | `localStorage` read behind a `typeof window` check          |
| [Media query](https://hydration.jscrate.dev/docs/causes/media-query)            | `matchMedia` or the window size read during render          |
| [Browser-only API](https://hydration.jscrate.dev/docs/causes/browser-api)       | `navigator.userAgent` and other browser globals             |
| [Different data](https://hydration.jscrate.dev/docs/causes/data)                | The 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:

```tsx title="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:

```tsx title="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](https://hydration.jscrate.dev/docs/probes).

## Prevent it with ESLint

The ESLint plugin reports the usual sources in render code:
[`no-date-in-render`](https://hydration.jscrate.dev/docs/rules/no-date-in-render),
[`no-random-in-render`](https://hydration.jscrate.dev/docs/rules/no-random-in-render),
[`no-locale-without-explicit-locale`](https://hydration.jscrate.dev/docs/rules/no-locale-without-explicit-locale),
[`no-timezone-without-explicit-timezone`](https://hydration.jscrate.dev/docs/rules/no-timezone-without-explicit-timezone),
[`no-storage-in-initial-render`](https://hydration.jscrate.dev/docs/rules/no-storage-in-initial-render) and
[`no-window-render-branch`](https://hydration.jscrate.dev/docs/rules/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](https://hydration.jscrate.dev/docs/issues/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](https://hydration.jscrate.dev/docs/ignoring).

## Example

```text
  ✖ /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.

## Related

- [Fix date and time hydration errors](https://hydration.jscrate.dev/docs/causes/time)
- [HP1002: an attribute differs](https://hydration.jscrate.dev/docs/issues/hp1002)
- [HP1015: only whitespace differs](https://hydration.jscrate.dev/docs/issues/hp1015)
- [When suppressHydrationWarning is safe](https://hydration.jscrate.dev/docs/guides/suppresshydrationwarning)
- [All issue codes](https://hydration.jscrate.dev/docs/issues)
