Back to articles
Next.jsReactDebugging

How to Fix Hydration Errors in Next.js

Why 'Hydration failed because the server rendered HTML didn't match the client' happens, the five causes behind almost every case, and how to fix each one the right way.

Yash Thakur
4 min read
How to Fix Hydration Errors in Next.js

The short answer: a hydration error means the HTML your server rendered didn't match what React produced in the browser on the first render. Find the thing that differs between server and client — usually a date, a random value, window, a browser extension, or invalid HTML nesting — and make the first client render identical to the server's. Don't paper over it with suppressHydrationWarning until you know why.

If you've used the Next.js App Router for more than a week, you've seen it: "Hydration failed because the server rendered HTML didn't match the client." It's one of the most-searched Next.js errors and one of the most misunderstood. The message is scary but the cause is almost always one of five things. Here's how to read the error and fix the real problem.

What hydration actually is

Next.js renders your page to HTML on the server and sends it down so the user sees content immediately. Then React "hydrates" that HTML in the browser — it renders your components again and attaches event listeners to the existing DOM, assuming the output matches. If React's first browser render produces different markup than the server sent, that assumption breaks, and you get the error. The fix is never "make the warning go away" — it's "make the two renders agree." (If the server/client split itself is still fuzzy, "use client" vs Server Components covers where the boundary goes.)

Cause 1: time and randomness

The classic. Anything non-deterministic produces one value on the server and a different one in the browser:

Date.now(), new Date(), Math.random(), and crypto.randomUUID() all fall in this bucket. The fix: render a stable placeholder on the server and compute the dynamic value after mount, so it only ever changes on the client:

Cause 2: touching window, localStorage, or navigator during render

These don't exist on the server, so code that reads them renders one way server-side (often a fallback or a crash) and another way in the browser. Reading localStorage to pick an initial theme is the single most common offender.

The fix is the same shape as above: initialise with the server-safe value, then read the browser API in useEffect and update. For theming specifically, that one-frame flash is why libraries inject a tiny blocking script in <head> to set the class before React hydrates — if you're hand-rolling theme logic, that's the pattern to copy.

Cause 3: invalid HTML nesting

The browser silently "fixes" invalid HTML as it parses, which rewrites the DOM out from under React. A <div> inside a <p>, a <p> inside a <p>, or a <table> without a <tbody> all trigger this — the server sends your nesting, the browser rearranges it, and now they don't match.

The error often points at a wrapper component rather than the real tag, so when the first four causes don't fit, inspect the actual nesting. The fix is just valid HTML — swap the <div> for a <span>, don't nest <p> tags, give tables their proper structure.

Cause 4: browser extensions mutating the DOM

Password managers, Grammarly, and translation extensions inject attributes (data-gramm, bis_skin_checked) or wrap text nodes before React hydrates, changing the HTML React expected. This one is maddening because it only reproduces on affected machines and isn't a bug in your code.

Confirm it by loading the page in a clean incognito window with extensions disabled. If the error vanishes, it's an extension — and this is the one legitimate place for suppressHydrationWarning, applied narrowly to the specific element the extension touches (commonly <body> or a text input), never to the whole tree.

Cause 5: rendering based on a value that differs by request

Reading something like a cookie, a user-agent, or a locale and branching your markup on it can mismatch if the server and client disagree about that value at hydration time. Make the branch deterministic for the first render — render the same thing the server did, then switch after mount — or push the decision into a Server Component so the branch is resolved before any HTML is sent.

A debugging order that works

  1. Read the diff. The React error overlay shows the mismatched markup — the differing text or attribute is the clue. Start there, not at the top of the stack.
  2. Binary-search the tree. Comment out halves of the page until the error disappears; the half that silences it holds the culprit.
  3. Match it to a cause above. Time/random (1), browser API (2), bad nesting (3), extension (4), per-request value (5) — it's nearly always one of these.
  4. Fix the mismatch, not the symptom. suppressHydrationWarning is correct for cause 4 and for a genuinely unavoidable value like a timestamp on one element. For everything else it hides a real inconsistency that will bite you later.

Hydration errors feel random the first few times, but they're not — they're React telling you the server and the browser disagreed about reality. Once you internalise that the first client render must match the server's byte for byte, every one of these becomes a quick, specific fix instead of a mystery. For the other App Router surprises that catch people out — caching defaults especially — see my honest take on the App Router after a year.

Frequently asked questions

What causes a hydration error in Next.js?

The HTML React rendered in the browser on its first render didn't match the HTML the server sent. The usual culprits are time/random values, reading window or localStorage during render, invalid HTML nesting, a browser extension mutating the DOM, or branching on a per-request value.

Should I use suppressHydrationWarning to fix it?

Only narrowly. It's the right tool for a browser extension changing the DOM, or a genuinely unavoidable value like a timestamp on one element. For everything else it hides a real server/client inconsistency you should fix at the source instead.

Does a hydration error happen in production too?

A real mismatch does. But React Strict Mode's extra checks run only in development, so some double-render noise you see in dev will not occur in production. Fix the underlying mismatch rather than relying on that.

Written by Yash Thakur

Senior React Developer · 8+ years building for the web

More articles

Keep reading