Fix "Hydration Failed Because the Server Rendered HTML Didn't Match the Client" in Next.js
A practical, cause-by-cause guide to diagnosing and fixing the most common Next.js hydration mismatch error, with real code fixes for each root cause.

Fix "Hydration Failed Because the Server Rendered HTML Didn't Match the Client" in Next.js
A practical, cause-by-cause guide to diagnosing and fixing the most common Next.js hydration mismatch error, with real code fixes for each root cause.
If your console shows Error: Hydration failed because the server rendered HTML didn't match the client, React is telling you that the HTML it generated on the server does not match what it re-rendered in the browser during hydration. This is not a syntax error โ your app usually still "works," but React throws this away and re-renders the whole tree on the client, hurting performance and occasionally causing visible flicker.
What This Error Means
Next.js renders your page twice: once on the server (producing static HTML sent to the browser) and once on the client (React "hydrates" that HTML by attaching event listeners and reconciling it with what it would have rendered itself). If the two outputs differ โ even by a single character โ React logs a hydration mismatch and discards the server markup for that subtree.
Why It Happens (Root Causes, Ranked by Frequency)
In real-world Next.js apps, this error almost always traces back to one of these four causes:
- ๐ Using
Date(),Math.random(), orDate.now()directly in render output - ๐งฉ Browser extensions injecting attributes into the DOM before React hydrates (Grammarly, password managers, dark-mode extensions)
- ๐ Reading
window,localStorage, ornavigatorduring initial render for conditional UI - ๐ท๏ธ Invalid HTML nesting (e.g. a
<div>inside a<p>) that the browser silently "fixes" before React sees it
Fix 1: Non-Deterministic Values (Date, Random, IDs)
The server renders at one moment in time; the client renders a split second later. Any value that changes between those two renders will mismatch. Move it into a useEffect so it only runs client-side, after hydration is complete.
// โ Runs during render โ mismatches every time
function Timestamp() {
return <span>{new Date().toLocaleTimeString()}</span>;
}
// โ
Set after mount, once hydration is done
function Timestamp() {
const [time, setTime] = useState(null);
useEffect(() => {
setTime(new Date().toLocaleTimeString());
}, []);
return <span>{time ?? "--:--:--"}</span>;
}
Fix 2: Browser Extensions Injecting Attributes
Extensions like Grammarly or password managers add attributes (e.g. data-gr-ext-installed) to your <body> before React hydrates. This is outside your control โ the fix is telling React to ignore it on that specific element.
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="en">
<body suppressHydrationWarning>
{children}
</body>
</html>
);
}
Fix 3: Reading window / localStorage During Render
The server has no window object, so any condition based on it will always render differently server-side vs. client-side. Gate it behind a mounted check.
// โ window is undefined on the server
function ThemeBadge() {
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
return <span>{isDark ? "Dark" : "Light"}</span>;
}
// โ
Defer to client-only state
function ThemeBadge() {
const [mounted, setMounted] = useState(false);
const [isDark, setIsDark] = useState(false);
useEffect(() => {
setIsDark(window.matchMedia("(prefers-color-scheme: dark)").matches);
setMounted(true);
}, []);
if (!mounted) return null; // render nothing until client confirms
return <span>{isDark ? "Dark" : "Light"}</span>;
}
Fix 4: Invalid HTML Nesting
Putting block elements like <div> or <ul> inside a <p> tag is invalid HTML. The browser silently rewrites the DOM to fix it, which no longer matches what React thinks it rendered.
// โ Invalid: div cannot be inside p
<p>
<div>Some content</div>
</p>
// โ
Valid: use span or restructure
<div>
<div>Some content</div>
</div>
suppressHydrationWarning as a blanket fix across your whole app โ it silences real bugs too. Apply it only to the specific element affected by known browser-extension noise.
Diagnostic Checklist
- โ
Search your component tree for
Date(,Math.random(, anduuid(used directly in JSX - ๐ Reproduce in an Incognito window with no extensions to rule out Fix 2
- ๐ Search for
window.orlocalStorageusage outsideuseEffect - ๐ก๏ธ Run your HTML through the browser's DevTools "Elements" tab and check for auto-corrected nesting
- ๐ Check the full React error message โ recent versions list the exact mismatched element
Official-Style Error Explanation
Hydration failed because the server rendered HTML didn't match the client indicates that React's client-side render produced different markup than the server-generated HTML it was hydrating. This is a rendering determinism failure, not a build or syntax error. Identify the non-deterministic or environment-dependent code causing the divergence and defer it to run after mount.
Frequently Asked Questions
โ Does this error break my app in production?
Not usually โ React recovers by re-rendering the mismatched subtree on the client. But it costs a performance hit and can cause a visible flash of incorrect content, so it's worth fixing rather than ignoring.
โ Should I just add suppressHydrationWarning everywhere?
No. That hides the warning without fixing the underlying cause, and you'll lose visibility into real bugs elsewhere in your tree. Use it only on the specific element affected by uncontrollable factors like browser extensions.
โ Why does this happen more with the App Router than Pages Router?
The App Router renders more of the tree on the server by default (Server Components), which increases the surface area where server and client output can diverge if client-only APIs sneak into shared logic.
Once you isolate which of these four causes is triggering your mismatch, the fix is usually a one-line change โ the hard part is finding the culprit, not writing the patch.


