"Hydration failed because the initial UI does not match what was rendered on the server" is React 18's error for a structural mismatch: the first render in the browser has a different element than the server's HTML. React discards the HTML and renders the page again. The warning logged just before it names the element to fix.
The error
In development, React 18 logs a warning that names the element, then throws the error, then reports that it switched to client rendering:
Warning: Expected server HTML to contain a matching <div> in <div>.
Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
Uncaught Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.Next.js 13 and 14 add a link to the error:
Error: Hydration failed because the initial UI does not match what was rendered on the server.
See more info here: https://nextjs.org/docs/messages/react-hydration-errorA production build prints the code instead:
Minified React error #418; visit https://reactjs.org/docs/error-decoder.html?invariant=418 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.React 19 renamed it to "Hydration failed because the server rendered HTML didn't match the client" and added a diff.
What hydration failed because the initial UI does not match what was rendered on the server means
React 18 hydrates by walking the server HTML and your first client render side by side. The React 18 "Hydration failed because the initial UI does not match" error is thrown when it expects an element and finds a different one, or finds nothing. Text is checked separately: a text difference throws "Text content does not match server-rendered HTML" instead.
After the error, React renders the nearest <Suspense> boundary, or the whole
root, again in the browser. That is the third line:
"There was an error while hydrating".
The page works, but it renders twice, can flash, and loses any input the user
typed before hydration.
The error itself never says which element. The warning above it does:
| Warning | Meaning |
|---|---|
Expected server HTML to contain a matching <div> in <div> | The browser rendered a <div> the server HTML does not have there |
Did not expect server HTML to contain a <div> in <div> | The server HTML has a <div> the browser did not render |
validateDOMNesting(...): <div> cannot appear as a descendant of <p> | Invalid nesting that the browser rewrote before React ran |
React 18 logs only the first mismatch per page load. If you fix one, reload: the next one appears.
Next.js 13 and 14 print the React 18 wording, so in Next.js 13 "Hydration failed because the initial UI does not match" is what any structural mismatch looks like, in both routers. It is a common error: the Stack Overflow question with this message as its title has about 690,000 views.
Common causes
Anything that renders a different element in the browser than on the server:
- Invalid HTML nesting. A
<div>inside a<p>, or a<tr>without<tbody>. The browser repairs the server HTML while parsing it, so React finds a different tree. See invalid HTML nesting. - A
typeof windowbranch.typeof window !== 'undefined' ? <A /> : <B />renders<B />on the server and<A />in the browser. See browser-only APIs. - Screen size, theme or storage read during render. A mobile menu chosen
from
window.innerWidth, a logged-in header chosen fromlocalStorage. See media queries, theme and storage. - Different data. The browser fetched the list again and got one more item. See server and client data.
- Something edited the HTML first. A browser extension, a third-party script or a CDN that minifies HTML. See browser extensions, third-party scripts and CDNs.
How to fix it
-
Run the app in development and find the warning logged before the error. It names the element and its parent, and the component stack under it names your component.
-
Decide why that element differs. Use the causes above.
-
Render the same element on both sides. A layout that depends on the screen size is a common case. Before, the server always renders the desktop navigation:
components/nav.tsx "use client"; import { DesktopNav, MobileNav } from "./navs"; export function Nav() { const isMobile = typeof window !== "undefined" && window.innerWidth < 768; return isMobile ? <MobileNav /> : <DesktopNav />; }After, both navigations are in the HTML and CSS picks one, so the server and the browser render the same elements:
components/nav.tsx import { DesktopNav, MobileNav } from "./navs"; export function Nav() { return ( <> <div className="md:hidden"> <MobileNav /> </div> <div className="hidden md:block"> <DesktopNav /> </div> </> ); }When the value must come from the browser, render a default first and read it in an effect: see useEffect and two-pass rendering.
-
Reload and check the console for the next mismatch.
-
Put unstable parts in a
<Suspense>boundary. React then re-renders only that boundary when hydration fails, not the whole root.
Find every instance
hydration-proof loads every route, compares the server HTML with the hydrated DOM and names the element, even in production builds where React only prints #418:
npx hydration-proof testStructural differences are HP1007 (a different
element), HP1008 (only in the server HTML) and
HP1009 (missing from it). Nesting the browser repaired
is HP3001, and a page React rendered again is
HP1011. Add --mode development to get React's own
warnings attached.
The ESLint plugin flags the usual sources while you type:
no-window-render-branch,
no-match-media-in-render,
no-storage-in-initial-render and
no-invalid-interactive-nesting.