React Error #418: Hydration failed — server HTML does not match client
Hydration failed because the server rendered HTML didn't match the client
Verified against React 19 source (ReactFiberHydrationContext.js), Next.js docs: react-hydration-error, Stack Overflow #68945655 · Updated April 2026
> quick_fix
The server rendered different HTML than the client would render on the first pass. Find the non-deterministic value (Date.now, Math.random, window.*, user locale) and render it only after mount using useEffect.
// ❌ Mismatches on reload
function Timestamp() {
return <p>{new Date().toLocaleString()}</p>
}
// ✅ Render after hydration
function Timestamp() {
const [ts, setTs] = useState('')
useEffect(() => setTs(new Date().toLocaleString()), [])
return <p suppressHydrationWarning>{ts}</p>
}What causes this error
During SSR, React renders the HTML on the server. On the client, React hydrates by re-rendering the component tree and comparing to the existing DOM. If the first client render produces different markup, React discards the server tree and falls back to client-only rendering — slow, janky, and logged as #418.
How to fix it
- 01
step 1
Check browser console for the mismatch
React logs the expected vs received text. Look for timestamps, randomised IDs, or locale-specific formatting.
- 02
step 2
Move non-deterministic values into useEffect
Anything that depends on window, navigator.language, Date.now, Math.random, or user timezone must render only after mount.
- 03
step 3
For intentional mismatches, add suppressHydrationWarning
React's escape hatch for known-diverging content like timestamps. Only the single element is suppressed, not its children.
- 04
step 4
Check for HTML nesting bugs
<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 instead of suppressing.
Why #418 happens at the runtime level
During SSR, React calls renderToString which runs your components synchronously and emits HTML. The browser receives that HTML, then React's hydrateRoot walks the DOM and re-runs each component to attach event handlers and reconcile state. Inside ReactFiberHydrationContext.js, every text node, attribute, and element type is compared against what the client-side render produces. Any divergence triggers invariant #418, React discards the matched subtree, and falls back to a full client render of that branch. The check is exact: a single space character or different timestamp string is enough to throw.
Common debug mistakes for #418
- Putting suppressHydrationWarning on a parent div, it only suppresses the immediate text node, not the entire descendant tree, so children still throw.
- Reading window or localStorage inside the component body and guarding with typeof window !== 'undefined', this returns false on server, true on client, so server renders one tree and client another, guaranteeing #418.
- Using a useMemo with Date.now() as input, useMemo runs on both server and client with different timestamps, the rendered output diverges, and the cache key gives no protection.
- Assuming the trace points at the right component, React often blames the parent of the diverging element because that's where reconciliation gave up; the actual bug is in a deeper child.
- Adding noSSR wrappers everywhere, defeats the entire point of SSR and ships a blank shell to crawlers and slow clients.
When #418 signals a deeper problem
Repeated hydration mismatches usually mean the architectural boundary between server and client code is not enforced at the type level. When the same component reads from both a server fetch and a browser-only store, every dynamic field becomes a hydration risk. The fix is structural: mark client-only components with 'use client' and pass server data down as props, or render dynamic values inside a useEffect-gated child. Without that boundary, every new feature that touches the DOM, browser locale, or randomness becomes another source of #418 and the bug count grows linearly with the codebase.
Editor's take
This error has a particular talent for surfacing at the worst possible moment: thirty minutes before a Next.js 15 app goes live and someone on a four-person startup team runs a final smoke test in production mode for the first time. The console fills with `Text content does not match server-rendered HTML` warnings, the whole page flickers on load, and nobody can explain why it worked fine in `next dev`. The root cause is almost always something that slipped through because `NODE_ENV=development` suppresses strict hydration checks — a `new Date().toLocaleDateString()` without a fixed locale, or a `Math.random()` seeding an animation key that nobody flagged in code review.
Solving this error cleanly is a reasonable proxy for understanding the React execution model at a level beyond "components are functions." A junior dev who hits it tends to reach for `suppressHydrationWarning` and move on, which hides the symptom. An engineer who actually traces the mismatch — using the `--experimental-vm-modules` flag in Jest to isolate SSR rendering, or adding `REACT_APP_DEBUG_HYDRATION=1` to a custom server setup — has internalized why server and client run the same component twice. That mental model pays dividends when debugging Suspense boundaries, React Server Components, and streaming responses in the App Router.
The adjacent errors that appear in the same incident are predictable once you know what to look for. React Error #425 (`useId` mismatch) often co-occurs when dynamic IDs are generated outside `useId`. Error #130 (`Objects are not valid as a React child`) can cascade if the hydration failure leaves a subtree in an inconsistent state. And upstream, you will frequently find a `NEXT_PUBLIC_` environment variable read at module scope — present on the server, undefined on the client during the hydration pass — which explains the mismatch without any obviously non-deterministic code in the component itself.
By Bikram Nath · Curator · Updated April 2026
Frequently asked questions
What causes React hydration mismatch?
Anything non-deterministic between server and initial client render: Date.now, Math.random, window.*, navigator.language, or HTML nesting auto-corrected by the browser.
Is suppressHydrationWarning safe to use?
For single elements where divergence is intentional and bounded (like a timestamp), yes. Do not scatter it across the component tree to silence real bugs.
Does Next.js have a special hydration fix?
Next.js 16 adds the `use cache` directive, which lets you render dynamic values on the server without mismatches. See cache-components docs.