Hydration failed: UI does not match server-rendered HTML (React / Next.js)
React expected the browser DOM to match the HTML the server sent. Something rendered differently on the client — a date, a random id, locale text, or an extension that rewrote the page — and hydration aborted with a mismatch warning.
What hydration is supposed to do
In SSR / RSC frameworks (Next.js, Remix, etc.) the server sends HTML so the first paint is fast. React then hydrates: attach event handlers to that existing markup. If render() on the client would produce different text or structure, React warns — Hydration failed because the initial UI does not match what was rendered on the server or Text content does not match server-rendered HTML — and may rebuild the subtree.
// Classic mismatch: server time ≠ client time
export default function Page() {
return <p>{new Date().toLocaleString()}</p>;
}
Cause table
| Pattern | Why it mismatches | Fix |
|---|---|---|
Date.now() / new Date() in render | Server and browser clocks / TZ differ | Render a stable placeholder; set time in useEffect; or pass a server-fixed ISO string |
Math.random() / uuid() in render | New value every render pass | Generate ids on the server and pass as props, or create in useEffect / useId |
typeof window !== "undefined" branches | Server takes the false branch, client the true | Default to server-safe UI; enhance after mount |
localStorage / window.innerWidth | Browser-only APIs crash or differ on server | Read them in useEffect; never during SSR render |
Locale / toLocaleString | Server locale ≠ user locale | Pass explicit locale / timeZone; or format on the client only |
| Invalid HTML nesting | Browser “fixes” DOM (<p><p>, <a><a>) | Valid nesting; React then sees a different tree |
| Browser extensions | Grammar/password/dark-mode tools inject nodes | Reproduce in private window; warn users; don’t suppress blindly |
| Whitespace / HTML entities | Markdown or CMS tweaks text nodes | Diff View Source vs client; normalize content |
Client-only the right way
function ClientTime() {
const [text, setText] = React.useState("—");
React.useEffect(() => {
setText(new Date().toLocaleString());
}, []);
return <time>{text}</time>;
}
First paint shows a stable placeholder (same on server and client); the real value appears after mount — no mismatch. For a whole widget, dynamic import with ssr: false (Next.js) is the heavy option.
suppressHydrationWarning — sparingly
- Valid on a single text node you knowingly allow to differ (e.g. a timestamp).
- It does not fix broken structure or “make SSR optional.” Overuse hides real bugs.
- Prefer fixing the render path over silencing the alarm.
<time suppressHydrationWarning>{new Date().toISOString()}</time>
View Source vs DevTools
- View Page Source — raw bytes from the server (true SSR HTML).
- DevTools Elements — live DOM after hydration, extensions, and browser fixes.
- Diff them (or copy both into the text diff tool). If View Source already differs from what your component should render, the bug is on the server path. If Source looks right but Elements don’t, suspect client code or an extension.
Need a stable “now” for APIs? Unix time converter. Mystery injected markup? What is this?
Next.js notes
- App Router: server components should stay pure; push browser APIs into client components (
"use client"). - Don’t read
cookies()/headers()in a way that diverges from what the client assumes without a plan. - Strict Mode double-invokes render in development — mismatches still indicate a real purity problem.
Related
Invalid hook call (React) · TypeError: Failed to fetch · CORS errors · Invisible zero-width characters · Cannot use import statement outside a module · Text diff · Unix time
Questions
What does Hydration failed because the initial UI does not match mean?
React’s first client render produced different markup than the HTML the server sent, so attaching to that HTML is unsafe.
Why does Date.now() break hydration?
The server stamps one time; the browser renders another. Put time updates in useEffect or pass a fixed server value.
Can a browser extension cause this?
Yes — many inject nodes or rewrite text before/during hydration. Test in a private window with extensions disabled.
Should I always use suppressHydrationWarning?
No. Use it only on small intentional differences (like a clock). Fix impure render logic instead.
How do I debug it quickly?
Compare View Page Source (SSR) with DevTools Elements; search the component for Date, random, window, localStorage, and locale formatters.
Is this a Next.js-only bug?
No — any React SSR/hydration setup can hit it. Next.js just surfaces the warning often because SSR is the default.
Published .