Next.js Hydration Errors: What They Mean and How to Fix Them
"Hydration failed because the server rendered HTML didn't match the client" — what hydration is, the usual culprits (dates, random values, window, browser extensions, invalid HTML, theme switchers), and the correct fix for each.
Next.js and other React frameworks sometimes show an error like:
Hydration failed because the server rendered HTML didn't match the client.
or "Text content does not match server-rendered HTML". It looks alarming, but it describes one specific problem, and the fixes are well known.
What "hydration" means
With Next.js, your page is rendered twice:
- On the server, React produces HTML and sends it to the browser, so the page appears quickly and search engines can read it.
- In the browser, React runs the same components again and "hydrates" that HTML — attaching click handlers and making it interactive.
React expects both renders to produce exactly the same output. If the browser's version differs from the server's, React can't safely attach itself, and you get a hydration error.
So the question is always: what renders differently on the server than in the browser?
The usual culprits
1. Dates and times
<p>Last updated: {new Date().toLocaleTimeString()}</p>
The server renders its time (and its time zone); the browser renders a slightly later time in the user's time zone. Mismatch.
Fix: render times only in the browser (see the useEffect pattern below), or format them in a fixed time zone. More in dates and time zones in apps.
2. Random values
Math.random() or random IDs generated during rendering differ between the two runs. For element IDs, use React's useId() hook, which produces matching IDs on both sides.
3. Browser-only things: window, localStorage
const theme = typeof window !== "undefined" ? localStorage.getItem("theme") : "light";
On the server there's no window, so this is "light"; in the browser it might be "dark". Different output.
Fix: read browser-only values after hydration:
"use client";
import { useEffect, useState } from "react";
export function ThemeLabel() {
const [theme, setTheme] = useState("light"); // same as the server
useEffect(() => {
setTheme(localStorage.getItem("theme") ?? "light"); // browser only, after hydration
}, []);
return <span>{theme}</span>;
}
useEffect runs only in the browser, after hydration, so the first render matches.
4. Theme switchers and dark mode
A classic: the page renders light on the server, and a script switches to dark before React hydrates. Theme libraries usually handle this by setting the theme on the <html> element and asking you to add suppressHydrationWarning to it:
<html lang="en" suppressHydrationWarning>
That suppresses the warning for that one element only — it's the documented approach for this case, not a general fix. See dark mode for your website.
5. Invalid HTML nesting
Browsers "repair" invalid HTML, so the browser's structure differs from what React sent:
- a
<div>inside a<p>, - a
<p>inside another<p>, - an
<a>inside another<a>, - a table without
<tbody>.
The error message usually names the elements. Fix: change the outer <p> to a <div>, or restructure.
6. Browser extensions
Some extensions (password managers, translators, grammar checkers) insert elements or attributes into the page before React hydrates. If the error disappears in a private window with extensions disabled, that's your answer — and it isn't a bug in your app.
How to find the cause
- Read the full error in the browser console. Recent versions of React and Next.js show a diff of what the server rendered versus what the client expected.
- Test in a private window to rule out extensions.
- Search the component for
Date,Math.random,window,localStorage,navigator,typeof window. - Check nesting of
<p>,<a>and table elements.
What not to do
- Don't put
suppressHydrationWarningeverywhere. It hides the warning on one element; the mismatch is still there and can cause odd behaviour. - Don't disable server rendering for whole pages just to make the error go away. You lose speed and SEO. Move the browser-only part into a small client component instead.
The summary
- Hydration = React making server-rendered HTML interactive in the browser.
- The error means the server and browser rendered different output.
- Usual causes: dates, random values,
window/localStorage, theme scripts, invalid HTML nesting, browser extensions. - Fix by making the first render identical and reading browser-only values in
useEffect.
EasySpawn gives Claude Code a server where it can run your Next.js app in development and production mode and see the real errors — including the ones that only appear after a build. See how it works or join the waitlist.
Related: What Is Next.js? · What Is React? · Cannot Read Properties of Undefined · Self-Hosting Next.js Without Vercel
Keep reading
"Unexpected Token < in JSON at Position 0": What It Means and How to Fix It
Your code expected JSON and got HTML — almost always an error page or your app's index.html. Why it happens (wrong URL, 404, server error, SPA fallback, login redirect), how to see what the server actually sent, and how to parse responses safely.
"Each Child in a List Should Have a Unique Key Prop": What It Means
React's key warning appears when you render a list without telling React which item is which. Why keys matter, why array index is a bad key for lists that change, what to use instead, and how to fix the warning in fragments and nested maps.