Next.js Hydration Error — How to Fix 'Text Content Does Not Match'
Error: Text content does not match server-rendered HTML.
Error: Hydration failed because the initial UI does not match what was rendered on the server.
Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
Hydration errors happen when the HTML rendered on the server doesn’t match what React tries to render on the client. React compares them and panics when they’re different.
Fix 1: Don’t use browser-only APIs during render
// ❌ window doesn't exist on the server
function Component() {
const width = window.innerWidth; // Crashes on server!
return <div>{width}</div>;
}
// ✅ Use useEffect for browser-only code
function Component() {
const [width, setWidth] = useState(0);
useEffect(() => {
setWidth(window.innerWidth);
}, []);
return <div>{width}</div>;
}
Same for localStorage, document, navigator, sessionStorage.
Fix 2: Don’t render dates/times directly
// ❌ Server and client render at different times
function Component() {
return <p>Current time: {new Date().toLocaleString()}</p>;
}
// ✅ Render on client only
function Component() {
const [time, setTime] = useState('');
useEffect(() => {
setTime(new Date().toLocaleString());
}, []);
return <p>Current time: {time}</p>;
}
Fix 3: Don’t use Math.random() in render
// ❌ Different random number on server vs client
function Component() {
return <div id={`item-${Math.random()}`}>Hello</div>;
}
// ✅ Use useId() for unique IDs
import { useId } from 'react';
function Component() {
const id = useId();
return <div id={id}>Hello</div>;
}
Fix 4: Fix invalid HTML nesting
Browsers auto-correct invalid HTML, causing mismatches:
// ❌ <p> can't contain <div> — browser fixes it, React sees a mismatch
<p>
<div>Hello</div>
</p>
// ❌ <a> inside <a>
<a href="/"><a href="/about">About</a></a>
// ✅ Use valid nesting
<div>
<div>Hello</div>
</div>
Fix 5: Suppress hydration warnings (last resort)
// Only use this if you know the mismatch is intentional
<div suppressHydrationWarning>
{typeof window !== 'undefined' ? clientValue : serverValue}
</div>
Fix 6: Use dynamic import with ssr: false
For components that simply can’t work on the server:
import dynamic from 'next/dynamic';
const MapComponent = dynamic(() => import('./Map'), {
ssr: false,
loading: () => <p>Loading map...</p>,
});