Hydration Proof

Search documentation

Find a page or section

Fix server/client data mismatch hydration errors

The client fetched again, and the answer had changed.

A server/client data mismatch hydration error means the server and first browser render used different data. The browser may have fetched again, started with an empty cache, or made a different feature-flag decision. Send the server's data with the page and use it for the first browser render.

Symptoms

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 and added or missing elements as HP1007, HP1008, or HP1009. A changed counter or a fetch near the element can point to data as the cause.

Why a server/client data mismatch hydration error happens

The first browser render must use the same snapshot as the server. These patterns break that agreement:

  • 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 Next.js App Router, fetch in a Server Component and pass the result to the Client Component:

app/stats/page.tsx
import { Visits } from "./visits";
 
export default async function Page() {
  const stats = await getStats();
  return <Visits initialCount={stats.count} />;
}
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>;
}

If the value must stay current, refresh it after hydration. Only the first render has to use the server snapshot.

Hydrate the query cache

With TanStack Query, prefetch on the server and pass the cache through dehydrate and HydrationBoundary. The browser's query starts with that data:

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>
  );
}

Consider a nonzero staleTime so the browser does not refetch immediately (TanStack Query SSR guide). For one query, a server prop passed as initialData can also work.

Use the framework's loader data

In React Router or Remix, read serialized loader data with useLoaderData(). Do not fetch the same value again for the first browser render.

Decide flags once, on the server

Choose a feature flag or experiment bucket once on the server. Pass the decision to the browser through a prop, context, or cookie.

Catch it with ESLint

A linter cannot know whether two requests return the same result. Check the running page with hydration-proof test.

Catch it in CI

With --probe, hydration-proof tries an identical reload. If the value still changes, inspect changing server or API data. See probes.

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

--repeat shows whether the problem comes and goes. To control browser requests in a test, use a scenario's mocks. Server requests are not changed by those mocks. See scenarios.