Understanding React Hydration Errors (and How to Fix Them)
Hydration errors are one of the most confusing bugs in React. They look cryptic, the stack traces are unhelpful, and the cause is often non-obvious. Here is a clear mental model.
You have seen the error: 'Hydration failed because the initial UI does not match what was rendered on the server.' It is one of the most common React production bugs and one of the hardest to debug without a clear mental model.
What hydration actually is
When React renders a Server Component, it sends HTML to the browser. That HTML is static — no interactivity. Hydration is the process where React takes over that static HTML and 'attaches' event listeners and client-side state to it.
For hydration to succeed, the HTML React generates on the client must match the HTML that was sent from the server, byte for byte. If they differ — even in whitespace — React throws.
The common causes
1. Browser-only values in render
// This will hydrate wrong — window does not exist on the server
function Layout() {
return <div style={{ width: window.innerWidth }}>...</div>;
}Fix: use useEffect for browser-only reads, or check typeof window !== 'undefined'.
2. Date or random values
// Different on server (build time) vs client (render time)
function Timestamp() {
return <span>{new Date().toLocaleString()}</span>;
}Fix: pass dates as props from the server, or use suppressHydrationWarning on the specific element.
3. Invalid HTML nesting
// <p> cannot contain <div> — browsers auto-fix this, React does not
<p><div>content</div></p>Fix: use <div> or <span> consistently. Validate your HTML structure.
4. Third-party scripts modifying the DOM
Analytics scripts, ad platforms, and browser extensions all modify the DOM. This happens after the server render but before hydration completes.
Fix: for extensions, nothing you can do. For third-party scripts, defer them with next/script strategy='lazyOnload'.
The nuclear option: suppressHydrationWarning
For elements where a mismatch is expected and acceptable (like a locale-dependent timestamp), suppressHydrationWarning={true} tells React to skip the check for that element. Use sparingly — it hides real bugs.
Hydration errors feel magical until you understand the contract: the server and client must produce identical HTML. Once that is clear, the fixes follow logically.