How to Fix Hydration Errors in Next.js
By Pawan Singh · Jun 2026
A hydration error means the HTML React generated on the server didn't match what it generated on the client during the first render — React detects the mismatch, warns loudly, and (depending on severity) either patches the DOM or throws, both of which are worse than the error simply not happening.
Cause 1: Rendering Anything That Differs Between Server and Client
// Bug: Date.now() (or Math.random()) produces a different value on
// the server (build/request time) than when the client re-renders it
function Timestamp() {
return <span>{new Date().toLocaleTimeString()}</span>;
}
Anything genuinely non-deterministic between server and client render — the current time, Math.random(), a randomly generated ID — will produce a mismatch, because the server rendered it once (at request/build time) and the client renders it again (at hydration time), inevitably getting a different value. Fix it by computing the value only after mount, so the server-rendered version and the client's first paint agree (both render nothing/a placeholder), and the real value appears in a second, client-only update:
function Timestamp() {
const [time, setTime] = useState(null);
useEffect(() => {
setTime(new Date().toLocaleTimeString());
}, []);
return <span>{time ?? '--:--:--'}</span>;
}
Cause 2: Checking window or Other Browser-Only Globals During Render
// Bug: `window` doesn't exist during server rendering at all — this
// throws server-side, or if guarded, produces mismatched output
function ResponsiveWidget() {
const isMobile = window.innerWidth < 768; // crashes on the server
return <div>{isMobile ? 'Mobile' : 'Desktop'}</div>;
}
Any browser-only API (window, localStorage, navigator) is unavailable during server rendering by definition — there is no browser on the server. Read it inside useEffect (which only runs client-side, after mount) instead of directly in the render body, and store the result in state.
Cause 3: Browser Extensions Modifying the DOM Before React Hydrates
Some browser extensions (password managers, ad blockers, accessibility tools) inject attributes or elements into the page before React hydrates — React sees a DOM that doesn't match what it rendered server-side, even though nothing in your own code is actually wrong. This is diagnosable by testing the exact same page in an incognito window with no extensions; if the warning disappears, it's environmental, not a bug in the app, and generally isn't worth chasing further.
Cause 4: Invalid HTML Nesting
// Bug: a <div> is not valid inside a <p> per the HTML spec — the
// browser silently "fixes" this by restructuring the DOM differently
// than what React actually rendered, causing a mismatch
<p>
<div>Some content</div>
</p>
The browser's own HTML parser corrects certain invalid nesting patterns automatically (moving, closing, or dropping elements) — producing a DOM structure that no longer matches what React's virtual DOM expects from its own render output. Fixing the actual HTML structure (not nesting block elements inside inline-only-context elements like <p>) resolves this at the source.
Cause 5: A Third-Party Script or Extension That Injects Content, or Locale/Timezone Differences
Server-rendering with one timezone/locale assumption and the client formatting a date in the visitor's actual local timezone is a common, subtle version of Cause 1 — a date formatted with toLocaleDateString() can legitimately differ between a server (often UTC) and a client (the visitor's local timezone), even for the exact same underlying timestamp. Format dates in a fixed, explicit timezone/locale server-side, or defer locale-sensitive formatting to a client-only effect the same way as the timestamp example above.
The suppressHydrationWarning Escape Hatch
<span suppressHydrationWarning>{new Date().toLocaleTimeString()}</span>
This silences the warning for a single element without fixing the underlying mismatch — appropriate only when the mismatch is genuinely expected and harmless (a timestamp that's allowed to differ by a few seconds), never as a way to make an unexplained error message go away without understanding why it's happening.
A Systematic Way to Diagnose It
- Read the actual diff React prints in the console — recent React versions show exactly which attribute or text content didn't match, not just "a mismatch occurred somewhere."
- Reproduce in an incognito window with no extensions first, to rule out Cause 3 before debugging your own code.
- Search the flagged component for anything reading
Date,Math.random(),window, or other browser-only/non-deterministic values directly in the render body. - Check for invalid HTML nesting around the flagged element.
Chasing a stubborn Next.js hydration bug? Get in touch — I work as a freelance Next.js developer.