Hydration Proof

Search documentation

Find a page or section

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

A React hydration error means the first browser render does not match the HTML sent by the server. React may have to render that part of the page again. To fix it, find what changed and make the first render agree on both sides.

Hydration error meaning: what React is telling you

The server renders your components to HTML. During hydration, the browser renders them again and expects the same result. A hydration error in React JS apps tells you those results differ.

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.

A hydration mismatch error has two broad sources: your code produced different output, or something changed the HTML before React received it. That something might be the browser's HTML parser, an extension, a script, or a CDN.

The messages you see

The exact message depends on your React version and whether the build is for development or production:

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

See React hydration error messages for the full list and individual explanations.

What causes a hydration error in React?

Start with the value or element React says changed. This table links common differences to their fix guides:

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 changed element. React's development diff or a test report can point to it. See debugging hydration errors.
  2. Compare the values. Is it a date, a class name, a browser preference, or an element that appears on only one side?
  3. Make the first browser render match. Choose the fix that fits:
    • Pass server data as a prop so both renders use the same value.
    • Move browser-only reads into useEffect so they happen after hydration. See two-pass rendering.
    • For an external store, use useSyncExternalStore with a matching getServerSnapshot.
    • If the component cannot render on the server, use a client-only component.
    • If the HTML is invalid, fix its nesting so the browser does not change it.
  4. Run the check again on every route that uses the component.

For example, a date can differ when the server and browser use different timezones:

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 hides a warning for one element's text or attributes; it does not make the renders agree. Use it only when a value cannot reasonably match, such as a live clock. See suppressHydrationWarning for its limits.

Which React hydration issues stay silent?

Some differences are easy to miss. The same hydration problem in React may appear as a warning locally and leave no visible message in production:

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

That is why a clean production console does not prove the page hydrated correctly. A missing hydration warning tells you little about silent attribute changes. hydration-proof compares attributes with the first client render as well.

Framer Motion hydration error

Motion (formerly Framer Motion) includes a component's initial state in the server HTML (Motion docs). That state must match the first browser render.

A framer motion hydration error can happen when initial depends on screen size, a random value, or a saved preference. The server and browser then produce different inline styles.

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

If you do not need an enter animation, initial={false} uses the animate values from the start. A style mismatch is an attribute mismatch; hydration-proof reports it as HP1003, even when React's production build stays quiet.

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