Next.js: Hydration failed because the server rendered HTML didn't match the client
Hydration failed — server/client HTML mismatch
Verified against Next.js 16 docs (messages/react-hydration-error), React 19 docs (link-a-react-component-errors), Stack Overflow #68945655 · Updated April 2026
> quick_fix
Something in your component renders differently on the server vs the first client render. Top suspects: new Date(), Math.random(), window.*, localStorage, or invalid HTML nesting (div inside p, etc.). Move client-only code into useEffect or gate it with a useState flag.
'use client'
import { useEffect, useState } from 'react'
// ❌ Mismatches
function Clock() {
return <p>{new Date().toLocaleString()}</p>
}
// ✅ Client-only render after mount
function Clock() {
const [time, setTime] = useState('')
useEffect(() => {
setTime(new Date().toLocaleString())
}, [])
return <p suppressHydrationWarning>{time}</p>
}What causes this error
Next.js renders your components on the server, sends the HTML, and then React hydrates by re-rendering the component tree on the client and comparing to the existing DOM. If the first client render produces different markup, React logs this error and falls back to client-only rendering — slow and janky.
How to fix it
- 01
step 1
Open DevTools and find the mismatch
The console error lists the expected vs received text. Search your codebase for that text or the surrounding component name.
- 02
step 2
Isolate non-deterministic values
Date.now, Math.random, window.*, navigator.language, navigator.userAgent, locale-dependent formatting — all render differently on server vs client. Move them to useEffect.
- 03
step 3
Check for invalid HTML nesting
<div> inside <p>, <p> inside <p>, or <a> inside <button> — the browser auto-corrects these on the client and diverges from server HTML. Fix the nesting.
- 04
step 4
For genuinely dynamic values, use suppressHydrationWarning
React's escape hatch. Only use on single text nodes you know will diverge intentionally.
- 05
step 5
For complex client-only components, use dynamic import
next/dynamic with ssr: false disables server rendering for that component entirely — the right choice for things like a Leaflet map that must be client-only.
import dynamic from 'next/dynamic' const Map = dynamic(() => import('./Map'), { ssr: false })
Why Hydration failed happens at the runtime level
Next.js renders Server Components and the SSR pass of Client Components to a streaming HTML response, then the client React runtime hydrates that HTML by re-rendering the same component tree and walking the DOM with hydrateRoot. Internally, ReactFiberHydrationContext.js compares each emitted token (tag name, attribute set, text content) against the existing DOM node. The first mismatch in a subtree aborts hydration of that subtree, logs the error in the console with the text diff, and falls back to a fresh client render. The mismatch typically resolves to non-deterministic values, browser-only globals, or HTML the browser auto-corrected.
Common debug mistakes for Hydration failed
- Calling new Date() inside a Server Component body and a useEffect on the client without freezing the value, the server timestamp is from request time, the client timestamp is post-hydration, and they never match.
- Reading localStorage or document.cookie at module top-level, Next.js evaluates the module on the server during build/render and the client at hydration with completely different results.
- Wrapping the diverging code in 'use client' but still passing serialized server props that contain the divergent value, the value travels in the RSC payload and mismatches anyway.
- Conditionally rendering based on navigator.userAgent, the server has no navigator, so the conditional renders one branch on server and the other on client.
- Mounting a UI library that injects style tags before hydration completes, the injected tags become part of the DOM before React expects them and trigger nesting mismatches.
When Hydration failed signals a deeper problem
Repeated hydration errors in a Next.js codebase usually indicate the App Router boundaries are not respected. If most of your tree is wrapped in 'use client' to silence hydration issues, you have given up the streaming-RSC benefit Next.js was designed around and turned the app into a single-page React shell with extra steps. The architectural fix is to separate truly server-rendered content (data, navigation, layout) from genuinely interactive islands and accept that interactive parts hydrate from a known-good server snapshot. Hydration errors are symptoms of a boundary leak, not a Next.js bug.
Editor's take
This error tends to detonate at the worst possible moment for a small startup engineering team: right after merging a timezone-sensitive feature the night before a product launch. The staging environment runs Node 22 with a fixed TZ=UTC, the preview Vercel deployment inherits the same, but production gets a different regional default — and suddenly new Date().toLocaleDateString() serializes differently on the server versus the client. The red screen hits during the founder's recorded demo walk-through, not during the PR review where it could have been caught with a simple NEXT_DISABLE_OPTIMIZATION=true local build.
Hitting this error and actually tracing it to its root — not just slapping suppressHydrationWarning on the offending node — is one of the clearest signals that an engineer understands the request-response lifecycle at the framework level, not just the component level. A junior dev patches it with the suppress flag and moves on. A mid-level engineer reaches for useEffect to defer the client-only value. The developer who's genuinely leveling up understands why the App Router's server/client boundary exists, what the streaming HTML response contains before hydration starts, and why wrapping everything in 'use client' is trading a warning for a performance regression measured in Largest Contentful Paint milliseconds.
When you're in the debugger chasing this, you'll often find it accompanied by a cluster of related failures. The console that shows the hydration mismatch frequently also shows a NEXT_REDIRECT thrown inside a Client Component — because whoever added the redirect didn't realize it belongs in a Server Component or middleware. Right behind that comes an unhandled runtime error from a window is not defined check that nobody added because the component 'worked fine in Create React App.' And if cookies or auth tokens are involved, a cookies() called in a Client Component warning from next/headers appears too, which points to the same root misunderstanding about where server context lives.
By Bikram Nath · Curator · Updated April 2026
Frequently asked questions
What does hydration mean in Next.js?
Hydration is the process where React re-uses the HTML sent by the server and attaches event handlers and state, without rebuilding the DOM from scratch. Fast; requires the first client render to match the server.
Does hydration mismatch crash my app?
Not always — React falls back to client-only rendering for the mismatched subtree. But it's slow, logs an error, and breaks SSR benefits. Fix it.
Can I disable SSR for a specific page?
Yes — use `export const dynamic = "force-dynamic"` or wrap the whole page in `next/dynamic` with ssr:false. But this defeats the point of Next.js for that page.