“Hydration failed” in your Next.js app
The console says one of these:
Error: Hydration failed because the server rendered HTML didn't match the client.
Warning: Text content does not match server-rendered HTML.
Uncaught Error: There was an error while hydrating.
This guide is written for apps that came out of v0, Cursor, Claude or another assistant — which is where this error now lives, because generated components do the exact things that trigger it.
What hydration actually is, in one paragraph
Next.js renders your page twice: once on the server, producing the HTML the browser shows immediately, and once in the browser, where React attaches interactivity to that HTML. Hydration is React walking the server's HTML and matching it against what the browser render produced. If the two disagree — any text, any attribute, any element — React reports the mismatch and rebuilds that part from scratch on the client. The error is not "your app crashed"; it is "your component renders different things in two places".
Which means the fix is always the same shape: find what differs between the server render and the browser render.
The four causes, in the order to check them
1. Time, randomness, or locale in render
<span>{new Date().toLocaleTimeString()}</span>
<div id={`item-${Math.random()}`}>
The server renders one value, the browser renders another a moment later —
guaranteed mismatch. AI-generated dashboards are full of this: "last updated"
labels, random ids, locale-formatted numbers (the server formats 1040 with
one locale, the visitor's browser with another).
Fix: render a stable placeholder on the server and fill the live value
after mount, in useEffect — or, for a timestamp that must be in the HTML,
put suppressHydrationWarning on that one element and nowhere else.
2. Browser-only state during render
const theme = localStorage.getItem('theme'); // throws or differs on server
if (typeof window !== 'undefined') { ... } // renders differently per side
window, localStorage, matchMedia, screen size: none exist on the server.
Reading them during render either crashes the server render or makes it
disagree with the client. The typeof window check feels like the fix and
is actually the cause — it makes the two renders take different branches.
Fix: read browser state in useEffect and store it in state; the first
client render must produce what the server produced. For a component that
simply cannot render on the server (a map, a chart reading the DOM), load it
with next/dynamic and ssr: false.
3. Invalid HTML nesting
<p><div>…</div></p> // a div may not live inside a p
<table><div>…</div></table>
The browser silently repairs invalid HTML while parsing the server's output —
moving the div out of the p — and then React finds a tree that no longer
matches what it expects. Generated markup does this with <p> wrapping cards,
tables missing <tbody>, buttons inside buttons. Recent React versions name
the offending elements in the error text; read it closely.
Fix: correct the nesting. This one is a real HTML bug, not a rendering subtlety.
4. A browser extension, not your code
Extensions that edit the page — password managers, translators, ad blockers —
inject attributes and elements before React hydrates, and React then reports
a mismatch you did not cause. Classic tell: the diff in the error shows
attributes like data-gramm or elements you never wrote.
Check: open the site in a private window with extensions off. If the error is gone, it was never yours; do not let an AI "fix" it.
Hand it to the AI with the diff
The error includes a diff of server versus client output. Paste all of it:
Paste this to the AI that built your app
My Next.js app (generated with v0) logs a hydration error. Here is the full error including the server/client diff: [paste everything, including the + and - lines] Identify which component causes the mismatch and why: time/random/locale values in render, browser-only APIs read during render, or invalid HTML nesting. Fix the root cause. Do not add suppressHydrationWarning anywhere except a single element whose content is legitimately time-dependent, and do not wrap components in ssr:false unless they genuinely cannot render on the server.
Prove it is gone
Reload the deployed page in a private window with the console open — the error must not appear on load, and not after navigating either. Then check one thing more: hydration bugs love to hide behind the first page and appear on route changes, so click through the main flows before calling it fixed.
Questions people also ask
Is a hydration error breaking my app or just noise?
Both are possible. React recovers by re-rendering on the client, so the page often looks fine — but the error is telling you some HTML was thrown away and rebuilt, which can flash wrong content, break event handlers, and hide a real logic bug. Treat it as real until you know which cause it is.
Can I just add suppressHydrationWarning?
Only on the specific element whose mismatch is legitimate — a timestamp, a locale-formatted number. Spreading it wider silences the messenger: the mismatches keep happening, you just stop being told.
Why do v0 and AI-generated apps hit this so often?
Because the model writes components that read the current time, random values, localStorage or window during render — natural-looking code that produces different output on the server than in the browser, which is exactly what hydration forbids.