Next.js hydration error: server and client rendered different things

Hydration is React attaching interactivity to the HTML the server already sent — and it only works if the first client render produces the same tree. When it doesn't, React throws the hydration error and re-renders from scratch. The mismatch is deterministic: something in the render output depends on an input that differs between server and browser.

The usual suspects, most common first

  1. Dates and times rendered directly — new Date() / Date.now() in a component: the server rendered one second (or timezone), the browser another. Even “x minutes ago” strings differ.
  2. Random values — Math.random(), generated IDs, or shuffled lists computed during render.
  3. Browser-only state — window, localStorage, screen size, or a theme read during the first render. The server assumed one thing; the browser knows another.
  4. Invalid HTML nesting — a <div> inside a <p>, a <table> missing its <tbody> structure, nested <button> or <a> elements. Browsers “repair” invalid nesting in the parsed HTML, so the DOM React hydrates against isn't the DOM React described. This cause is silent and extremely common.
  5. Browser extensions injecting attributes into the page before React loads (Grammarly-style data-* attributes) — production users hit this; your dev machine may not.
  6. Locale-dependent formatting — toLocaleString() without an explicit locale: server locale ≠ user locale.

How to hunt it

  1. Read the diff React gives you. Development mode shows the server text vs client text for the offending node — the difference is usually unmistakable once seen (a date, a number, a missing element).
  2. Bisect the page. Comment out half the components; hydrate; narrow down. Mismatches are local — one component is lying.
  3. View source vs DOM. Compare the raw server HTML (view-source:) against the Elements panel at the mismatch site — browser-repaired nesting shows itself immediately.

The fixes, by cause

  1. Move volatile values out of first render: compute dates/random/IDs in useEffect + state, or pass a fixed value from the server (a prop generated once server-side) so both sides agree.
  2. Client-only components: for genuinely browser-dependent widgets, load with next/dynamic and ssr: false, or gate on a mounted flag (const [mounted, setMounted] = useState(false) + effect) rendering a placeholder first.
  3. Fix the HTML semantics for nesting issues — replace the p > div with valid structure; this class of fix is permanent and free.
  4. Pin formats: pass explicit locales to date/number formatters used in shared components.
  5. suppressHydrationWarning is for one narrow case — a single text node (like a timestamp) that is allowed to differ. On that element only, knowingly. Spraying it across a tree hides real bugs.
The error is a comparison failing, not a server being “wrong.” Both renders are usually reasonable; they just can't differ. Any fix that makes the two sides compute the same thing — or delays the differing part until after hydration — addresses the actual mechanism.

If it still happens

Disable browser extensions for the site and retest (extension-injected attributes are invisible in your code, so this eliminates a whole class in one step). If it reproduces only in production, compare the deployed HTML against dev — minification plus extension repair is a different beast than the dev mismatch, and the raw deployed source is the evidence.

Common questions

Does a hydration error break the page? React recovers by re-rendering the whole tree on the client — the page works, but slower, with a visible flicker, and the SEO/perf benefits of server rendering are lost for that page. Worth fixing, not an outage.

Why does it only happen for some users? Extensions, timezones, and locales differ per user — the inputs that differ between server and their browser. Reproducing needs their conditions, which is why the report's details (browser, page, time) matter.

Related errors

TypeError: cannot read properties · CORS error · npm ERESOLVE