Next.js Hydration Errors Explained and Fixed
A hydration error means the HTML that Next.js sent from the server does not match what React tries to render in the browser. React compares the two and throws an error when they differ. The most common causes are browser extensions injecting elements, incorrect HTML nesting, code that checks window or localStorage during render, and date/time formatting that differs between server and client.
What hydration is and why mismatches break things
When Next.js renders a page, it first runs your React components on the server and sends the result as HTML. The browser displays this HTML immediately. Then React "hydrates" the page: it runs the same components in the browser and attaches event listeners to the existing HTML instead of rebuilding it from scratch.
Hydration expects the server HTML and the client render to produce identical output. If they differ by even a single element, React warns (in development) or silently discards the server HTML and re-renders from scratch (in production), which causes a flash of content and loses the performance benefit of server rendering.
Cause 1: Browser extensions injecting elements
Extensions like Grammarly, password managers, or ad blockers inject their own HTML elements into your page. React sees these extra elements during hydration and thinks your component output changed.
Fix: Test in an incognito window with extensions disabled. If the error disappears, a browser extension is the cause. You cannot control what extensions users install, but you can suppress the warning in development by confirming the mismatch is not from your code. In production, React handles this gracefully since version 18.3.
Cause 2: Invalid HTML nesting
Certain HTML combinations are invalid: a <p> inside another <p>, a <div> inside a <p>, or a <button> inside an <a>. The browser's HTML parser silently "fixes" these by moving elements around. The server sends the raw invalid HTML, the browser rearranges it, and React sees a mismatch.
Fix: Check the error message for hints about which elements mismatched. Common fixes:
- Replace
<p>with<div>if the element contains block-level children - Replace nested
<a>tags with a single link - Use
<span>instead of<div>inside paragraphs
Cause 3: Using window or localStorage during render
Code that reads window.innerWidth, localStorage, or navigator.userAgent during the initial render produces different values on the server (where window does not exist) and the client.
Fix: Move browser-only logic into a useEffect hook, which only runs on the client:
import { useState, useEffect } from 'react';
export default function ThemeToggle() {
const [theme, setTheme] = useState('light'); // safe default
useEffect(() => {
// This only runs in the browser
const saved = localStorage.getItem('theme');
if (saved) setTheme(saved);
}, []);
return <button>{theme}</button>;
}The initial server render uses the safe default. After hydration, the useEffect reads the real value and updates the component. There is a brief moment where the default shows, but no hydration error.
Cause 4: Date and time rendering differences
The server might be in UTC while the user's browser is in EAT (East Africa Time, UTC+3). If you render new Date().toLocaleString() during the initial render, the server and client produce different strings.
Fix: Either format dates on the client only (inside useEffect), or use a consistent format that does not depend on timezone:
// Safe: explicit timezone, same output everywhere
const formatted = new Date(article.lastUpdated + 'T00:00:00').toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: 'UTC',
});When to use suppressHydrationWarning
React provides a suppressHydrationWarning prop for cases where a mismatch is expected and harmless, like rendering the current time:
<time suppressHydrationWarning>
{new Date().toLocaleTimeString()}
</time>Use this sparingly and only when the mismatch is cosmetic. It suppresses the warning for that element only, not for its children. Do not use it to hide a real bug; investigate the mismatch first.
Frequently Asked Questions
- Do hydration errors affect production users?
- In production, React does not throw an error. Instead it silently discards the server HTML and re-renders from scratch on the client. Users may see a brief flash of content changing. Performance is worse because the benefit of server rendering is lost for that component.
- How do I debug which element is causing the mismatch?
- In development, the browser console shows the expected vs actual HTML. React 18.3+ includes the full component stack in the error message, pointing you to the exact component. If the message is unclear, add a console.log to your component and compare the server output (view page source) with what the browser renders.
- Can I disable hydration for a specific component?
- Yes. Use dynamic import with ssr: false in Next.js to load a component only on the client. This skips server rendering entirely for that component, which eliminates any mismatch. Use it for widgets that are inherently client-only, like a chart library or a map.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon. Ship 8 production apps with M-Pesa, USSD, and WhatsApp integrations, and get career support until placement.
See Programs