"Hydration failed because the initial UI does not match what was rendered on the server" means React 18 found a different element in the first browser render. Find the warning just before this error: it often names the changed element and its parent.
The error
React 18 may print three messages for the same problem:
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-errorProduction uses a short code:
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 compares the server HTML with the first browser render. The React 18 "Hydration failed because the initial UI does not match" error points to a structural difference. Text has a separate message: "Text content does not match server-rendered HTML" instead.
React may then render a Suspense boundary or the whole root again. The follow-up message describes that recovery: "There was an error while hydrating". The page may still work, but content can flash and input typed before hydration can be lost.
Use the earlier warning to locate the element:
| 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 |
After fixing one mismatch, reload and check for another.
Next.js 13 and 14 apps using React 18 show this wording in either router. If you're searching for "NextJS 13 hydration failed because the initial UI does not match," use the preceding warning to find the element first.
Common causes
Look for a different element on the server and in the browser:
- 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
-
Find the first warning in development. Read the element name and component stack.
-
Identify the cause using the list above.
-
Render the same elements on both sides. In this example, the server always chooses 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 />; }Render stable markup and let CSS choose the visible version:
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> </> ); }For browser-only values, see two-pass rendering.
-
Reload and check again.
-
Use Suspense boundaries where appropriate. They can contain the effect of a future failure, but do not fix this one.
Find every instance
hydration-proof checks routes in a browser and can identify the changed element in a production build:
npx hydration-proof testThe report uses HP1007 for a different element, HP1008 or HP1009 for a missing element, and HP3001 for repaired HTML nesting. A re-rendered root appears as HP1011.
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.