Hydration Proof

Search documentation

Find a page or section

Hydration failed because the server rendered HTML didn't match the client

React 19's hydration error, and how to read the diff under it.

"Hydration failed because the server rendered HTML didn't match the client" means React 19 found different text or elements in the first browser render. Read the diff under the message to find the difference. Then make the server and browser start with the same output.

The error

In development, React 19 shows possible causes and a diff:

Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
 
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
 
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
 
https://react.dev/link/hydration-mismatch
 
  <Clock>
    <p>
+     12:04:33
-     12:04:31

For text-only differences, the message may say "text" instead of "HTML":

Hydration failed because the server rendered text didn't match the client. As a result this tree will be regenerated on the client.

Production may show only error #418, without the diff.

Why hydration failed because the server rendered HTML didn't match the client

Hydration attaches React to existing server HTML. The first browser render must match that HTML for React to reuse it.

When an element or text node differs, React may render the affected Suspense boundary—or the whole root—again in the browser. This can cause problems:

  • The server's work is wasted and the page renders twice.
  • Content can flash while it is replaced.
  • Text a user typed or the focused element can be lost.

This message is for text or structure. A React 19 hydration mismatch in an attribute such as className, style, or href has another warning: "A tree hydrated but some attributes… didn't match".

If you search for "Next.js 15 hydration failed because the server rendered HTML didn't match the client" or "React 19 hydration error," this is the message to start with. The Next.js development overlay can show the React diff.

How to read the diff

Read the diff from the browser's point of view:

  • Lines starting with + are what the browser rendered.
  • Lines starting with - are what the server HTML contained.
  • Unmarked lines are the path from the component down to the difference.

A +/- pair shows a changed value. A line on only one side may show an added or missing element. Fix the first difference, then reload to check for others.

Common causes

Match the diff to a likely cause:

CauseTypical diffFix guide
A typeof window check, window or navigator read during render+ <canvas> against - <div>Browser-only APIs
localStorage, theme or screen size read during renderA different element or classStorage, theme, media queries
Date.now(), new Date() or relative times+ 12:04:33 against - 12:04:31Time-dependent values
Math.random() or countersTwo different ids or keysRandom values, unstable ids
Dates and numbers formatted in the user's locale or timezone+ 18/09/2026 against - 9/18/2026Locale, timezone
Data fetched again in the browserTwo different prices or listsServer and client data
Invalid HTML nesting, such as <div> in <p>An element in an unexpected parentInvalid HTML
A browser extension, a third-party script or a CDN that edits the HTMLNodes nobody renderedExtensions, scripts, CDNs

How to fix it

  1. Read the diff and find the component above the changed value.

  2. Trace the value to its source: time, formatting, storage, or another input.

  3. Make the first browser render match. Pass a server value down or show a stable initial value and read the browser later:

    components/greeting.tsx
    "use client";
     
    // Before: the server renders "guest", the browser renders the stored name.
    export function Greeting() {
      const name =
        typeof window !== "undefined" ? localStorage.getItem("name") : null;
      return <p>Hello, {name ?? "guest"}</p>;
    }
    components/greeting.tsx
    "use client";
     
    import { useEffect, useState } from "react";
     
    // After: both renders say "guest", then the effect loads the stored name.
    export function Greeting() {
      const [name, setName] = useState<string | null>(null);
     
      useEffect(() => {
        setName(localStorage.getItem("name"));
      }, []);
     
      return <p>Hello, {name ?? "guest"}</p>;
    }

    See two-pass rendering. If the component cannot run on the server, use a client-only component.

  4. Reload and check again. Another difference may now be visible.

  5. Use Suspense where appropriate to limit a future failure; it does not fix this mismatch.

suppressHydrationWarning cannot hide structural differences. See when it is safe before using it.

Find every instance

The console covers the page you opened. Check your routes with hydration-proof:

npx hydration-proof test --mode both

The report identifies changed text as HP1001, changed elements as HP1007–HP1009, and re-rendered trees as HP1010 or HP1011. If React reports the error but the comparison cannot place it, see HP2001.

In the editor, the ESLint plugin catches the code patterns React lists: no-window-render-branch, no-date-in-render, no-random-in-render, no-locale-without-explicit-locale and no-invalid-interactive-nesting.