# Fix server/client data mismatch hydration errors

> A server/client data mismatch hydration error means the client rendered different data than the server. Send the server's data along; don't refetch it.

Source: https://hydration.jscrate.dev/docs/causes/data
Last updated: 2026-09-18

A server/client data mismatch hydration error happens when the first client
render uses different data than the server did: the client fetched again and
got a newer answer, read a different cache, or evaluated a flag on its own.
Send the exact data the server rendered with to the client, and hydrate from
it before fetching anything new.

## Symptoms

```text
Hydration failed because the server rendered text didn't match the client.
Warning: Text content did not match. Server: "Visits: 41" Client: "Visits: 42"
Warning: Expected server HTML to contain a matching <li> in <ul>.
Warning: Did not expect server HTML to contain a <li> in <ul>.
```

React 19's own message names this cause: "External changing data without
sending a snapshot of it along with the HTML."

hydration-proof reports changed text as [HP1001](https://hydration.jscrate.dev/docs/issues/hp1001), and a
list that is longer or shorter on one side as
[HP1007](https://hydration.jscrate.dev/docs/issues/hp1007), [HP1008](https://hydration.jscrate.dev/docs/issues/hp1008) or
[HP1009](https://hydration.jscrate.dev/docs/issues/hp1009), with the cause **Server and client used different
data**. This is the hardest cause to read off the values (68% in the package's
test suite): a counter one higher on the client and a `fetch` near the element
are the clues. A probe gives the proof.

## Why a server/client data mismatch hydration error happens

The server renders with the data it had at that moment. If the client does not
receive that exact data, its first render works from something else:

- **A second fetch.** A Client Component fetches during render (with a
  suspense-enabled hook or a cache that was never filled on the client) and the
  data changed in between: a view counter, a stock level, a feed.
- **A cache that was not sent.** The server filled a query cache, but the client
  starts with an empty one and renders loading state or refetched data.
- **Decisions made twice.** A feature flag, an A/B test bucket or a
  personalization rule is evaluated on the server and again in the browser.
- **Environment values.** In Next.js, environment variables without the
  `NEXT_PUBLIC_` prefix are only available on the server, so a Client
  Component that renders `process.env.REGION` gets a value on the server and
  nothing in the browser.

## How to fix it

### Pass the server's data as props

In the App Router, fetch in a Server Component and pass the result to the
Client Component. React serializes the props into the page, so the client
renders the same data:

```tsx title="app/stats/page.tsx"
import { Visits } from "./visits";

export default async function Page() {
  const stats = await getStats();
  return <Visits initialCount={stats.count} />;
}
```

```tsx title="app/stats/visits.tsx"
"use client";

import { useState } from "react";

export function Visits({ initialCount }: { initialCount: number }) {
  // Before: const { count } = use(fetchStats()) fetched again on the client
  const [count] = useState(initialCount);
  return <p>Visits: {count}</p>;
}
```

Refresh the value after hydration (polling, a subscription, an effect) if it
must stay live. The first render uses the snapshot; later renders can use newer
data.

### Hydrate the query cache

With TanStack Query, prefetch on the server and send the cache along with
`dehydrate` and `HydrationBoundary`. The client's `useQuery` then starts with
the server's data instead of fetching:

```tsx title="app/posts/page.tsx"
import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from "@tanstack/react-query";
import { Posts } from "./posts";

export default async function PostsPage() {
  const queryClient = new QueryClient();
  await queryClient.prefetchQuery({ queryKey: ["posts"], queryFn: getPosts });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Posts />
    </HydrationBoundary>
  );
}
```

Set a default `staleTime` above 0, as the
[TanStack Query SSR guide](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr)
recommends, so the client does not refetch as soon as it hydrates. For a
single query, `initialData` from a server prop does the same job.

### Use the framework's loader data

React Router and Remix serialize loader data into the page. Read it with
`useLoaderData()` in the component instead of fetching again in an effect or
during render, and both sides render from the same snapshot.

### Decide flags once, on the server

Evaluate feature flags and experiment buckets on the server, and pass the
result down (a prop, a context value, a cookie the server set). The client
reads the decision; it never makes its own.

## Catch it with ESLint

No lint rule can tell whether two fetches return the same data, so there is no
rule for this cause. `hydration-proof test` finds it in the running app.

## Catch it in CI

`hydration-proof test` reports the differing text or elements. With `--probe`,
it reloads the page with nothing changed at all: if that identical reload
renders a different value, the page depends on server data, not on the clock,
the locale or anything else in the browser. See [probes](https://hydration.jscrate.dev/docs/probes).

```bash
npx hydration-proof test --probe --repeat 3
```

`--repeat` loads each page several times and marks findings that come and go
as flaky, which data mismatches often are. To make the client's requests
predictable in a test, a scenario's `mocks` answer browser requests; requests
the server makes are not affected. See [scenarios](https://hydration.jscrate.dev/docs/scenarios).

## Related

- [HP1001: text differs between server and client](https://hydration.jscrate.dev/docs/issues/hp1001)
- [Expected server HTML to contain a matching element](https://hydration.jscrate.dev/docs/errors/expected-server-html-to-contain-a-matching)
- [Server Action form state](https://hydration.jscrate.dev/docs/causes/form-state)
- [Hydration errors only in production](https://hydration.jscrate.dev/docs/guides/hydration-error-only-in-production)
- [All causes of hydration errors](https://hydration.jscrate.dev/docs/causes)
