Next.js Client-Server Hydration Errors
One of the most common errors developers encounter when working with Next.js is the Hydration Mismatch:
"Error: Hydration failed because the initial UI does not match what was rendered on the server."
To solve and debug this error during frontend interviews, you must understand how React's Hydration process works.
1. What is Hydration?
In Server-Side Rendered (SSR) frameworks like Next.js, the server renders the page components into a static HTML string. The browser receives this HTML and displays it immediately, making the site load fast.
However, this raw HTML is not interactive yet. Hydration is the process where React runs on the client, matches the loaded HTML structure, and attaches event listeners (like onClick) to the DOM elements.
2. What Causes a Hydration Mismatch?
A hydration error occurs when the HTML generated by the server is different from the first HTML tree generated by React on the client.
The two most common causes are:
Cause A: Using Browser-Only APIs on Initial Render
If you use APIs like window, localStorage, or date objects directly in your rendering logic, the server and client will output different markup.
function BadComponent() {
// Server: doesn't have 'window', returns empty or throws error
// Client: has 'window', returns the innerWidth value
const width = typeof window !== "undefined" ? window.innerWidth : 0;
return <div>Screen width: {width}px</div>;
}Cause B: Invalid HTML Structure Nesting
Browsers automatically correct malformed HTML. If your React component contains illegal nesting, the browser will restructure the DOM on load, making it differ from the raw HTML React expects.
A classic example is nesting a <div> inside a <p> tag:
function InvalidHtml() {
// Browsers do not allow block elements (div) inside inline paragraphs (p).
// The browser DOM parser splits the paragraph, breaking React's hydration tree.
return (
<p>
<div>This will trigger a hydration error!</div>
</p>
);
}3. How to Fix Hydration Errors
Solution 1: Use useEffect to Run Client-Only Logic
To prevent browser-only calculations from running on the server, move them inside a useEffect hook. useEffect only runs on the client after hydration is complete.
import { useState, useEffect } from "react";
function GoodComponent() {
const [width, setWidth] = useState(0);
useEffect(() => {
// Runs only in the browser
setWidth(window.innerWidth);
}, []);
return <div>Screen width: {width}px</div>;
}Solution 2: Disable SSR for Specific Components
If a component must be rendered entirely on the client (like a theme switcher reading localStorage), load it dynamically with Next.js dynamic imports and disable Server-Side Rendering (ssr: false):
import dynamic from "next/dynamic";
// Loads ThemeToggle only on the client
const ThemeToggle = dynamic(() => import("./ThemeToggle"), {
ssr: false,
});
export default function Layout() {
return (
<nav>
<ThemeToggle />
</nav>
);
}Solution 3: Use suppressHydrationWarning
For minor dynamic content (such as date stamps or user location values), you can add the suppressHydrationWarning attribute. This stops React from warning you about minor differences in attributes or text content.
function DateDisplay() {
// Suppress warnings on the outer element
return (
<span suppressHydrationWarning>
Current time: {new Date().toLocaleTimeString()}
</span>
);
}Key Takeaways
- Hydration is the step where React attaches event listeners to pre-rendered server HTML.
- A hydration mismatch occurs when the server-rendered HTML doesn't match the client's first render tree.
- Common culprits are invalid HTML structures (e.g.
divinsidep) or browser-only values (likewindoworlocalStorage) executed during rendering. - Fix hydration errors by moving browser-only code to
useEffect, loading client components withssr: falsedynamic imports, or wrapping tags insuppressHydrationWarning.