Fix “Hydration Failed” Errors in React and Next.js
On this page
- Cause 1: Values that change between server and browser
- Cause 2: Checking typeof window during render
- Cause 3: Invalid HTML nesting
- Cause 4: Browser extensions
- Cause 5: A component that should never render on the server
- When suppressHydrationWarning is fine
- What happens during hydration, step by step
- A reliable debugging process
- Formatting dates without mismatches
- Reading browser-only values the clean way
- Third-party scripts and ads
- Next.js App Router specifics
- Frequently asked questions
- Oh hi there 👋It’s nice to meet you.
- Sign up to receive awesome content in your inbox.
Your Next.js page loads, then the console or the error overlay shows something like:
Hydration failed because the server rendered HTML didn't match the client.
Older React versions word it as “Hydration failed because the initial UI does not match what was rendered on the server”. Either way, the meaning is the same.
Hydration is the step where React takes the HTML the server sent and attaches event handlers to it in the browser. To do that, React renders your components again in the browser and expects exactly the same result. If even one text node or element differs, you get this error, and React may throw away the server HTML and re-render, which costs performance.
Recent React versions print a diff showing the mismatching text or element. Read it first: it usually points straight at the cause.
Cause 1: Values that change between server and browser
Dates, times, random numbers and generated IDs differ on each render:
// Mismatch: the server and the browser render different times
export default function Footer() {
return <p>Page loaded at {new Date().toLocaleTimeString()}</p>;
}
Render the changing value only after the component mounts in the browser:
'use client';
import { useEffect, useState } from 'react';
export default function Footer() {
const [time, setTime] = useState(null);
useEffect(() => {
setTime(new Date().toLocaleTimeString());
}, []);
return <p>Page loaded at {time ?? '…'}</p>;
}
Dates formatted with toLocaleString() are a quiet version of this bug: the server and the visitor’s browser can have different time zones and languages. Format dates in a fixed time zone, or format them on the client.
For element IDs that must match on both sides, use React’s useId() hook instead of Math.random().
Cause 2: Checking typeof window during render
// Mismatch: the server renders null, the browser renders the button
const isBrowser = typeof window !== 'undefined';
return isBrowser ? <ThemeButton theme={localStorage.getItem('theme')} /> : null;
The first browser render must match the server. Read browser-only values such as localStorage, window.innerWidth or navigator inside useEffect, store them in state, and render the real value after mounting.
Cause 3: Invalid HTML nesting
Browsers repair invalid HTML before React sees it, so the browser’s DOM no longer matches what React expects. The most common cases:
- A
<div>or another<p>inside a<p>. Change the outer<p>to a<div>. - A link inside a link.
- Table rows without a
<tbody>.
Watch out for components that render a <div> internally, such as some UI library components, placed inside a paragraph.
Cause 4: Browser extensions
Extensions like password managers, translators and Grammarly add attributes or elements to the page before React hydrates. If the error mentions attributes you never wrote, open the page in an incognito window with extensions disabled. If the error disappears there, your code is fine.
Cause 5: A component that should never render on the server
Some libraries, such as maps and rich text editors, only work in the browser. Load them on the client only:
'use client';
import dynamic from 'next/dynamic';
const Map = dynamic(() => import('./Map'), {
ssr: false,
loading: () => <p>Loading map…</p>,
});
In the Next.js App Router, ssr: false only works inside a Client Component, so keep the 'use client' line.
When suppressHydrationWarning is fine
For a single element whose text is expected to differ, like a timestamp, you can silence the warning:
<time dateTime={iso} suppressHydrationWarning>{formatted}</time>
It only works one level deep and only hides the warning. Don’t spread it around to make errors go away.
New to how server and client rendering split work? Read React Server Components explained in plain English.
What happens during hydration, step by step
- The server runs your components and produces HTML. That HTML is sent to the browser and appears quickly, before any JavaScript runs.
- The browser downloads your JavaScript bundle.
- React runs your components again in the browser, using the same props and data, and builds its own picture of what the page should look like.
- React walks through the existing HTML and its own picture side by side, attaching event handlers as it goes. This is hydration.
- If the two pictures differ, React reports a hydration error. Depending on the version and the size of the difference, it may patch the text or throw away the server HTML for that section and render it again on the client.
The key point: the first render in the browser must produce exactly what the server produced. After hydration finishes, components can change as much as they like. That is why moving browser-only logic into useEffect works: effects run after hydration.
A reliable debugging process
When the error message alone doesn’t make the cause obvious, work through these steps:
- Read the diff. Recent React versions print the expected server value and the client value. Search your code for whatever produces that value.
- Disable JavaScript and view the page. In Chrome’s developer tools, open the command menu, choose “Disable JavaScript” and reload. What you see is the pure server HTML. Compare it with the page once JavaScript is enabled again.
- Try an incognito window. If the error disappears, a browser extension was changing the page.
- Bisect the page. Comment out half of the components and reload. Keep halving the part that still errors until one component remains.
- Check data sources. If the server and the browser fetch the same API, they may get different results seconds apart. Pass data fetched on the server down as props instead of fetching again on the client.
Formatting dates without mismatches
Dates are the most common cause in real apps, especially for Indian users when the server runs in UTC. A post published at 2:00 am IST on 6 March was published at 8:30 pm UTC on 5 March. A server running in UTC prints “5 March”, a browser in India prints “6 March”, and React sees two different texts.
Fix it by formatting with an explicit time zone and locale, so both sides produce the same text:
const formatter = new Intl.DateTimeFormat('en-IN', {
dateStyle: 'medium',
timeZone: 'Asia/Kolkata',
});
export function PublishedDate({ iso }) {
return <time dateTime={iso}>{formatter.format(new Date(iso))}</time>;
}
If you want to show each visitor their own local time instead, render the fixed version first and switch to local time in an effect, or use suppressHydrationWarning on that single <time> element.
Reading browser-only values the clean way
Many components need something only the browser knows: screen width, online status, a saved preference. React’s useSyncExternalStore hook lets you give a server value and a client value explicitly, so hydration stays consistent:
'use client';
import { useSyncExternalStore } from 'react';
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
export function useOnlineStatus() {
return useSyncExternalStore(
subscribe,
() => navigator.onLine, // value in the browser
() => true // value during server rendering and hydration
);
}
React uses the server value during hydration, then switches to the real browser value right after, without a mismatch warning.
Third-party scripts and ads
Scripts that change the page before React hydrates can also cause mismatches. Ad scripts (including AdSense auto ads), chat widgets, A/B testing tools and some analytics snippets insert elements into the page. Load them after hydration:
- In Next.js, use the
Scriptcomponent fromnext/scriptwithstrategy="afterInteractive"or"lazyOnload". - Give ad slots their own empty container element that React renders and never changes, so the ad script fills a space React doesn’t compare.
- Avoid scripts that rewrite the whole
<body>during page load.
Next.js App Router specifics
Server Components can’t cause hydration errors on their own, because they never run in the browser. Mismatches always come from Client Components, the files marked 'use client', or from the HTML they render.
Reading cookies or headers. In Server Components, use cookies() and headers() from next/headers to read things like a theme preference on the server, then pass the value down as a prop. The server and the client then render the same theme, and you avoid both the mismatch and a flash of the wrong theme.
Static pages with dynamic values. A page built at build time contains the time of the build. If a Client Component then renders the current time, the two will differ. Either make the value dynamic on the server, or render it only on the client.
Frequently asked questions
Do hydration errors happen in production?
The detailed message appears in development. In production the mismatch still happens, and React may re-render the whole section on the client, which is slower and can cause a visible flash.
Do Vite or Create React App apps get hydration errors?
Only if they use server-side rendering. A plain client-rendered React app has no server HTML to hydrate, so this error doesn’t occur.
How do I find which component causes the mismatch?
Read the diff in the error overlay, which shows the server and client values. If it’s unclear, comment out half of the page, reload, and keep narrowing down.
Can I just ignore hydration warnings?
Not safely. Besides the console noise, React may re-render whole sections on the client, which slows the page down and can make content jump. Fix the cause, and use suppressHydrationWarning only for single elements that are meant to differ.
Does useEffect run during server rendering?
No. Effects run only in the browser, after hydration. That is exactly why moving browser-only code into useEffect fixes most hydration errors.



