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,hrefordata-*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| Message | Where you see it | Decoded |
|---|---|---|
| Hydration failed because the server rendered HTML didn't match the client | React 19 (#418 in production) | Page |
| A tree hydrated but some attributes … didn't match | React 19, development only | Page |
| Hydration failed because the initial UI does not match | React 18 | Page |
| Text content does not match server-rendered HTML | React 18 | Page |
| There was an error while hydrating | React 18 | Page |
Prop className did not match | React 18, development | Page |
| Expected server HTML to contain a matching … | React 18, development | Page |
| Extra attributes from the server | React 18, development | Page |
<div> cannot be a descendant of <p> | React 19, development (React 18: validateDOMNesting) | Page |
| Minified React error #418, #423, #425 | Production builds | Page |
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:
| Cause | Typical code | Fix guide |
|---|---|---|
| The clock | Date.now(), new Date() in render | Time |
| Timezone | toLocaleTimeString() without timeZone | Timezone |
| Locale | toLocaleString(), Intl.NumberFormat() without a locale | Locale |
| Random values | Math.random(), crypto.randomUUID() | Random |
| Browser-only APIs | typeof window !== "undefined", navigator | Browser APIs |
| Storage | localStorage.getItem() in render or initial state | Storage |
| Screen size | matchMedia(), window.innerWidth | Media query |
| Theme | dark mode read from storage | Theme |
| Data | the client fetches again and gets a different answer | Data |
| Invalid HTML | <div> in <p>, <a> in <a>, <tr> in <table> | Invalid HTML |
| CSS-in-JS | class names generated in a different order | CSS-in-JS |
| Ids | counters or random ids instead of useId() | Unstable ids |
| Browser extensions | attributes such as cz-shortcut-listen | Extensions |
| Scripts | a tag manager edits the DOM before React | Third-party scripts |
| CDN rewrites | HTML minification at the edge | CDN |
| Server Action forms | useActionState with a permalink | Form state |
How to fix a React hydration error
- Find the changed element. React's development diff or a test report can point to it. See debugging hydration errors.
- Compare the values. Is it a date, a class name, a browser preference, or an element that appears on only one side?
- 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
useEffectso they happen after hydration. See two-pass rendering. - For an external store, use
useSyncExternalStorewith a matchinggetServerSnapshot. - 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.
- 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:
"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>;
}"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,styleorhrefstays 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
classNamedid 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.
"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>
);
}"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
| Tool | When it runs | What it finds |
|---|---|---|
| React's console messages | Development, the page you have open | The mismatches React reports, with a diff in React 19 |
| Next.js dev overlay | next dev, one page at a time | The same, with a code frame |
| eslint-plugin-hydration-proof | Your editor and CI, on source code | Patterns that cause mismatches, before they run |
| hydration-proof test | CI, every route, development and production builds | Mismatches that happen, including silent attribute ones, with the cause |
| Sentry | Production, real users | Errors React reports in your users' browsers |
npx hydration-proof test --mode both