Error: Hydration failed because the server rendered HTML didn't match the client.
Warning: Expected server HTML to contain a matching <div> in <div>.
React rendered different HTML on the server vs. the client. When the client tries to βhydrateβ (attach event listeners to the server HTML), it finds a mismatch and throws this error.
Fix 1: Browser-Only Values
The most common cause. Youβre using something that doesnβt exist on the server.
// β window doesn't exist on the server
function App() {
return <p>Width: {window.innerWidth}</p>;
}
// β
Use useEffect for browser-only values
function App() {
const [width, setWidth] = useState(0);
useEffect(() => {
setWidth(window.innerWidth);
}, []);
return <p>Width: {width}</p>;
}
Common culprits: window, document, localStorage, navigator, Date.now(), Math.random().
Fix 2: Date/Time Differences
Server and client are in different timezones or render at different times.
// β Different time on server vs client
function App() {
return <p>{new Date().toLocaleString()}</p>;
}
// β
Render on client only
function App() {
const [time, setTime] = useState('');
useEffect(() => {
setTime(new Date().toLocaleString());
}, []);
return <p>{time}</p>;
}
Fix 3: Invalid HTML Nesting
Browsers auto-correct invalid HTML, causing mismatches.
// β <p> can't contain <div>
<p>
<div>This breaks hydration</div>
</p>
// β
Use valid nesting
<div>
<div>This works</div>
</div>
// β <a> inside <a>
<a href="/parent">
<a href="/child">Nested link</a>
</a>
Fix 4: Browser Extensions
Extensions like ad blockers, Grammarly, or translation tools modify the DOM, causing mismatches.
// Suppress hydration warnings on specific elements
<div suppressHydrationWarning>
{/* Content that might be modified by extensions */}
</div>
This is a valid escape hatch for content you know might differ.
Fix 5: Conditional Rendering Based on Auth/State
// β Server doesn't know if user is logged in
function Nav() {
const user = getUser(); // null on server, object on client
return user ? <LogoutButton /> : <LoginButton />;
}
// β
Use a loading state
function Nav() {
const [user, setUser] = useState(null);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setUser(getUser());
setLoaded(true);
}, []);
if (!loaded) return null; // or a skeleton
return user ? <LogoutButton /> : <LoginButton />;
}
Fix 6: Next.js Dynamic Import
For components that should only render on the client:
import dynamic from 'next/dynamic';
const Chart = dynamic(() => import('./Chart'), {
ssr: false,
loading: () => <p>Loading chart...</p>,
});
Quick Checklist
- Are you using
window,document, orlocalStorageduring render? β Move touseEffect - Are you rendering dates or random values? β Move to
useEffect - Is your HTML nesting valid? β Check
<p>,<a>,<table>rules - Does it only happen in dev? β Might be a browser extension