Hydration Proof

Search documentation

Find a page or section

Every message, every cause, and the fix for each.

A React hydration error means the HTML the server sent and the first render in the browser are different. React logs an error and, in React 19, throws that part of the server HTML away and renders it again on the client. The fix is to make both renders produce the same output.

Hydration error meaning: what React is telling you

React renders your components twice: once on the server to produce HTML, and once in the browser during hydration, where it expects the exact same output. A hydration error in React JS apps is the report that the two did not match.

A React hydration mismatch comes in three kinds:

  • Text: the server wrote "5:00 AM", the browser rendered "10:00 AM".
  • Attributes: a className, style, href or data-* value differs.
  • Structure: one side rendered an element the other did not, or a different one.

Every hydration problem in React comes from one of two places: code that renders different output on the two sides, or something that changed the HTML between the server and React (the browser's parser, an extension, a script or a CDN).

The messages you see

The wording depends on the React version and on the build. These are the ones people search for:

Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client.
A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up.
Hydration failed because the initial UI does not match what was rendered on the server.
Text content does not match server-rendered HTML.
There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
In HTML, <div> cannot be a descendant of <p>. This will cause a hydration error.
Minified React error #418; visit https://react.dev/errors/418
MessageWhere you see itDecoded
Hydration failed because the server rendered HTML didn't match the clientReact 19 (#418 in production)Page
A tree hydrated but some attributes … didn't matchReact 19, development onlyPage
Hydration failed because the initial UI does not matchReact 18Page
Text content does not match server-rendered HTMLReact 18Page
There was an error while hydratingReact 18Page
Prop className did not matchReact 18, developmentPage
Expected server HTML to contain a matching …React 18, developmentPage
Extra attributes from the serverReact 18, developmentPage
<div> cannot be a descendant of <p>React 19, development (React 18: validateDOMNesting)Page
Minified React error #418, #423, #425Production buildsPage

The full list, with the React 18 and 19 wording side by side, is on React hydration error messages.

What causes a hydration error in React?

Each cause below has its own fix guide. hydration-proof names the likely cause for every finding.

CauseTypical codeFix guide
The clockDate.now(), new Date() in renderTime
TimezonetoLocaleTimeString() without timeZoneTimezone
LocaletoLocaleString(), Intl.NumberFormat() without a localeLocale
Random valuesMath.random(), crypto.randomUUID()Random
Browser-only APIstypeof window !== "undefined", navigatorBrowser APIs
StoragelocalStorage.getItem() in render or initial stateStorage
Screen sizematchMedia(), window.innerWidthMedia query
Themedark mode read from storageTheme
Datathe client fetches again and gets a different answerData
Invalid HTML<div> in <p>, <a> in <a>, <tr> in <table>Invalid HTML
CSS-in-JSclass names generated in a different orderCSS-in-JS
Idscounters or random ids instead of useId()Unstable ids
Browser extensionsattributes such as cz-shortcut-listenExtensions
Scriptsa tag manager edits the DOM before ReactThird-party scripts
CDN rewritesHTML minification at the edgeCDN
Server Action formsuseActionState with a permalinkForm state

How to fix a React hydration error

  1. Find the element. Read the diff React logs in development, or let a tool point at it. Debugging hydration errors covers both.
  2. Name the cause. Compare the two values: a timestamp, a number in another format, a random token or a different class name each point at one row of the table above.
  3. Make the first client render match the server. Pick the first fix that applies:
    • pass the value the server used as a prop, so both sides render the same data;
    • move browser-only reads into useEffect, so they happen after hydration (two-pass rendering);
    • read external stores with useSyncExternalStore and a getServerSnapshot that returns the server's value;
    • render the component only in the browser (client-only components);
    • fix invalid nesting so the browser does not rewrite the HTML.
  4. Check every page again, not only the one you fixed.

A typical fix, for a date formatted in the reader's timezone:

app/components/last-login.tsx
"use client";
 
// Before: the server formats in its timezone, the browser in the reader's.
export function LastLogin({ at }: { at: number }) {
  return <p>Signed in at {new Date(at).toLocaleTimeString()}</p>;
}
app/components/last-login.tsx
"use client";
 
// After: the same locale and timezone on both sides.
export function LastLogin({ at, timeZone }: { at: number; timeZone: string }) {
  return (
    <p>Signed in at {new Date(at).toLocaleTimeString("en-US", { timeZone })}</p>
  );
}

How to solve a hydration error without hiding it

suppressHydrationWarning makes React stop reporting a difference in one element's own text and attributes. React keeps the server's value and does not patch it, so the user sees the stale value. It is for values that can never match, such as a live clock, not for bugs. suppressHydrationWarning explains when it is safe and why it often does nothing.

Which React hydration issues stay silent?

Not every hydration mismatch error throws:

  • React 19 production builds do not report attribute mismatches at all. A wrong className, style or href stays on the page with no error in the console.
  • Production builds minify messages. You get "Minified React error #418" with no element and no diff. See errors only in production.
  • A hydration mismatch warning is a development feature. React 18's "Warning: Prop className did not match" and React 19's attribute warning are never printed by production builds.

These are the ones a hydration warning in your console will never show you, and why hydration-proof compares every attribute with what React renders on the client.

Framer Motion hydration error

Motion (formerly Framer Motion) renders the initial state of a motion component into the server HTML. Its docs say motion components "are fully compatible with server-side rendering, meaning the initial state of the component will be reflected in the server-generated output" (motion.dev).

So a framer motion hydration error appears when initial or animate depends on something only the browser knows: the window size, a random value, or a stored preference. The server writes one inline style, the browser computes another.

components/slide-in.tsx
"use client";
 
import type { ReactNode } from "react";
import { motion } from "motion/react";
 
// Before: window.innerWidth is unknown on the server.
export function SlideIn({ children }: { children: ReactNode }) {
  const from = typeof window === "undefined" ? 0 : -window.innerWidth;
  return (
    <motion.div initial={{ x: from }} animate={{ x: 0 }}>
      {children}
    </motion.div>
  );
}
components/slide-in.tsx
"use client";
 
import type { ReactNode } from "react";
import { motion } from "motion/react";
 
// After: the same starting value on both sides, in a unit that does not
// depend on the screen.
export function SlideIn({ children }: { children: ReactNode }) {
  return (
    <motion.div initial={{ x: "-100%" }} animate={{ x: 0 }}>
      {children}
    </motion.div>
  );
}

To skip the enter animation entirely, initial={false} renders the animate values on the server and in the browser. A style difference is an attribute mismatch, so React 19 production builds report nothing; hydration-proof reports it as HP1003.

Tools that find hydration errors

ToolWhen it runsWhat it finds
React's console messagesDevelopment, the page you have openThe mismatches React reports, with a diff in React 19
Next.js dev overlaynext dev, one page at a timeThe same, with a code frame
eslint-plugin-hydration-proofYour editor and CI, on source codePatterns that cause mismatches, before they run
hydration-proof testCI, every route, development and production buildsMismatches that happen, including silent attribute ones, with the cause
SentryProduction, real usersErrors React reports in your users' browsers
npx hydration-proof test --mode both