TL;DR: When you use Next.js, the server generates HTML and sends it to the browser first — so the page loads fast. Then the browser's JavaScript "wakes up" that HTML and makes it interactive. That waking-up process is called hydration. If the server's HTML and the browser's version do not match exactly, you get a hydration error. AI commonly generates code that causes this mismatch without warning you. The fix is usually one line: 'use client' at the top of the file.
Why AI Coders Need to Know This
"Hydration failed" is one of the top five most Googled errors by vibe coders using Next.js. It comes up constantly on r/vibecoding, r/NextJS, and r/ClaudeCode — usually from people who did nothing wrong. The AI built their code. The AI didn't warn them. The app broke.
Here's why this keeps happening: React and Next.js use a technique called server-side rendering (SSR) that generates your page's HTML on a server before the browser ever sees it. That's great for performance and SEO. But it creates a constraint that AI frequently violates without realizing it: code that only works in a browser cannot run on the server.
When you ask Claude or Cursor to "build a dashboard with a sidebar that shows the current user," the AI writes code that reads from the browser's storage, checks the window size, or calls APIs that only exist in a browser context. That code works fine on the client — but the server has no window, no localStorage, no document. The server can't run it. And when the server's output doesn't match the browser's output, React throws a hydration error and your app breaks.
This isn't a bug in your thinking. It's a predictable failure mode in how AI generates Next.js code. Once you understand hydration, you'll recognize the pattern immediately and know exactly what to ask the AI to do differently.
Real Scenario: The Error That Comes From Nowhere
You're building a SaaS landing page. You open Claude Code and type:
Prompt I Would Type
Build me a Next.js component that shows a personalized greeting.
If the user's name is stored in localStorage, show "Welcome back, [name]!"
Otherwise show "Welcome! Create your account to get started."
Claude generates this component:
export default function WelcomeBanner() {
const userName = localStorage.getItem('userName');
return (
<div className="banner">
{userName
? <h2>Welcome back, {userName}!</h2>
: <h2>Welcome! Create your account to get started.</h2>
}
</div>
);
}
Looks totally reasonable. You paste it in. You run npm run dev. The browser opens and you see this:
Error: Hydration failed because the initial UI does not match what was rendered on the server.
Warning: Expected server HTML to contain a matching <h2> in <div>.
What went wrong? The server tried to render WelcomeBanner to generate the initial HTML. But on the server, there is no localStorage — that's a browser-only API. The server either throws an error or returns null. Meanwhile, the browser does have localStorage and renders the correct version. The two versions don't match. Hydration fails.
The fix here is simple — but you need to understand why to know which fix to apply. Let's break down what hydration actually is first.
What Hydration Actually Does
Think about how a construction project hands off from the framing crew to the finishing crew. The framing crew builds the structure — the walls, the roof, the bones of the building. It looks like a building, but you can't live in it yet. There are no fixtures, no outlets, no running water. Then the finishing crew comes in and makes it functional.
Hydration works the same way, in two phases:
Phase 1 — Server renders HTML: When someone visits your Next.js site, the server runs your React code and generates a complete HTML page. It's fast. It's visible immediately. The browser can display it before any JavaScript loads. This is what makes SSR sites feel fast — you see content right away.
Phase 2 — Browser hydrates it: After the HTML displays, the browser downloads your JavaScript bundle. React then "takes over" the HTML that's already on screen — it attaches event listeners (so your buttons work), connects state (so your data updates), and makes everything interactive. This takeover process is hydration.
The critical rule: for hydration to work, React's JavaScript on the client must generate the exact same HTML that the server already sent. React doesn't re-render the whole page — it walks through the existing HTML and attaches behavior to it. If the structure doesn't match, React doesn't know what to attach to what. That mismatch is a hydration error.
This is why hydration is unique to SSR frameworks like Next.js. A purely client-side React app (like a Create React App project) doesn't have this problem because there's no server rendering — the browser builds everything from scratch. But Next.js is server-rendered by default, so hydration is always in play.
The Timeline, Visualized
1. User visits your URL
└─ Server runs your React components
└─ Server generates HTML string
└─ Server sends HTML to browser
2. Browser receives HTML
└─ Renders it immediately (fast first paint ✓)
└─ Page is VISIBLE but not yet interactive
3. Browser downloads JavaScript bundle
└─ React loads
└─ React "hydrates" — walks the existing HTML
└─ Attaches event listeners, connects state
└─ Page is now INTERACTIVE ✓
⚠️ If server HTML ≠ client HTML → Hydration Error
What AI Gets Wrong About Hydration
AI tools are trained on millions of React examples — but most of those examples are from client-side React apps where hydration doesn't exist. When the AI generates Next.js code, it often applies client-side patterns to server-side components without flagging the incompatibility. Here are the four most common mistakes:
1. Using Browser-Only APIs in Server Components
The browser has APIs the server doesn't: window, document, localStorage, sessionStorage, navigator. When AI generates code that reads these in a Next.js component, and that component runs on the server, it crashes or returns undefined — which creates a mismatch.
// This will cause a hydration error — window doesn't exist on server
export default function ThemeToggle() {
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
return <button>{isDark ? '☀️ Light' : '🌙 Dark'}</button>;
}
'use client'; // ← This one line fixes it
export default function ThemeToggle() {
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
return <button>{isDark ? '☀️ Light' : '🌙 Dark'}</button>;
}
2. Using new Date() or Math.random()
These functions return different values every time they're called. The server generates a timestamp at 10:00:01.234. By the time the browser runs the same code, it's 10:00:01.890. Different output. Hydration error.
export default function RandomBanner() {
const id = Math.random(); // Different on server vs. client
return <div data-id={id}>Special offer just for you!</div>;
}
'use client';
import { useState, useEffect } from 'react';
export default function RandomBanner() {
const [id, setId] = useState(null);
useEffect(() => {
setId(Math.random()); // Runs only in the browser, after hydration
}, []);
return <div data-id={id ?? ''}>Special offer just for you!</div>;
}
3. Conditional Rendering Based on Client State
AI frequently generates components that show different content based on whether a user is logged in, what theme is selected, or what's in localStorage. The server has none of that state — it renders the "logged out" or "default" version. The browser has the state and renders the "logged in" version. Mismatch.
export default function UserGreeting() {
const isLoggedIn = localStorage.getItem('token') !== null;
// Server: localStorage doesn't exist → isLoggedIn = crash or false
// Browser: localStorage exists → isLoggedIn = true
// Mismatch!
return isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>;
}
4. Browser Extensions Injecting Content
This one isn't AI's fault — it's a real-world quirk. Extensions like Grammarly, password managers, or ad blockers sometimes inject HTML into your page. The server's HTML doesn't have that injected content. When React hydrates, it sees extra elements it didn't render and throws a hydration warning. If you see hydration errors only in your own browser but not in production, this is probably why. Try opening an incognito window — extensions are usually disabled there.
The Most Common Hydration Errors (With Fixes)
Fix 1: Add 'use client' at the Top of the File
This is the fix you'll use 80% of the time. In Next.js 13 and later, all components are server components by default. Adding 'use client' as the very first line tells Next.js: "only run this component in the browser, not on the server." No server rendering means no hydration mismatch.
'use client'; // ← Must be the FIRST line in the file, before any imports
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
When to use it: Any time your component uses window, localStorage, document, navigator, event handlers (onClick, onChange), or React hooks like useState and useEffect.
Fix 2: Use dynamic() with { ssr: false }
Sometimes an entire component just cannot run on the server at all — like a chart library, a map component, or anything that requires a real browser environment. For those cases, Next.js's dynamic() function lets you import the component in a way that skips server rendering entirely.
import dynamic from 'next/dynamic';
// This component will ONLY render in the browser — server skips it entirely
const ChartComponent = dynamic(
() => import('./ChartComponent'),
{ ssr: false }
);
export default function Dashboard() {
return (
<div>
<h1>Analytics</h1>
<ChartComponent /> {/* Renders on client only, no hydration risk */}
</div>
);
}
When to use it: Third-party libraries that access window during import (like some chart or map libraries), components that read device-specific data, anything that throws errors during server rendering.
Fix 3: Use suppressHydrationWarning for Intentional Mismatches
Sometimes a mismatch is okay. A classic example: showing a timestamp on the server and a formatted local time in the browser. They'll always be slightly different. For those cases, you can add suppressHydrationWarning to the specific element to tell React: "I know this won't match — that's fine."
export default function LastUpdated({ timestamp }) {
return (
<p suppressHydrationWarning>
Last updated: {new Date(timestamp).toLocaleTimeString()}
</p>
);
}
Use sparingly. suppressHydrationWarning only suppresses the warning — it doesn't fix anything. If your whole app is throwing hydration errors and you slap this on everything, you're papering over real problems. Use it only when you've consciously decided the mismatch is acceptable.
Fix 4: Check for Client-Side Mount with useEffect
If you need browser-only values but want to keep the component as a server component initially, use a mounted state pattern. The component renders a placeholder on the server, and after hydration runs on the browser, it switches to the real content.
'use client';
import { useState, useEffect } from 'react';
export default function UserGreeting() {
const [userName, setUserName] = useState(null);
useEffect(() => {
// This runs AFTER hydration, so it's safe to use localStorage here
const name = localStorage.getItem('userName');
setUserName(name);
}, []);
// Server renders this (no localStorage needed)
// Browser renders this initially too, then updates after useEffect
return (
<div>
{userName
? <h2>Welcome back, {userName}!</h2>
: <h2>Welcome! Create your account to get started.</h2>
}
</div>
);
}
What to Tell Your AI
Now that you understand the problem, here are prompts you can use to prevent hydration errors before they happen — or fix them once they appear.
Prevention Prompt
I'm building a Next.js 15 app with the App Router. For any component
that uses window, localStorage, document, navigator, onClick, onChange,
useState, or useEffect — always add 'use client' at the top of the file.
Do not put browser-only APIs in server components.
Fix a Hydration Error Prompt
I'm getting this error in my Next.js app:
"Hydration failed because the initial UI does not match
what was rendered on the server."
Here is the component that's causing it: [paste your component]
Please fix the hydration error. Options to consider:
1. Add 'use client' if it uses browser-only APIs
2. Use dynamic() with ssr:false if it can't run server-side at all
3. Move browser-only code into useEffect so it runs after hydration
Explain which fix you're applying and why.
Audit Existing Code Prompt
Review this Next.js component and check for potential hydration errors.
Look for: window, document, localStorage, sessionStorage, navigator,
Math.random(), new Date(), and any conditional rendering based on
browser state. Flag each issue and suggest the appropriate fix.
[paste component]
Third-Party Library Prompt
I want to add [library name] to my Next.js app. This library may use
browser APIs. Please import it using next/dynamic with ssr: false to
prevent hydration errors. Show me the complete implementation.
How This Connects to Other Concepts
Hydration sits at the intersection of several things you'll encounter when building with AI:
- Server-Side Rendering (SSR): Hydration only exists because of SSR. If you understand SSR — the server builds the HTML, not the browser — hydration clicks immediately.
- The DOM: Hydration is ultimately about React taking control of the DOM — the browser's live representation of your HTML. When server and client don't agree on what the DOM should look like, React panics.
- Code Splitting: The JavaScript bundle that triggers hydration is often split into chunks. Understanding code splitting explains why some components hydrate before others.
- Next.js: Hydration is a Next.js-specific pain point because Next.js defaults to SSR. Other frameworks handle this differently. Understanding how Next.js works gives you the full picture.
Frequently Asked Questions
What is hydration in web development?
Hydration is the process where a browser takes static HTML that was pre-rendered on the server and "wakes it up" by attaching JavaScript event handlers to it. After hydration, the page becomes fully interactive. Before hydration, you can see the content but buttons and interactive elements do not work yet.
What causes hydration errors in Next.js?
Hydration errors occur when the HTML generated on the server does not match the HTML that React tries to generate in the browser. Common causes include: using browser-only APIs like window, localStorage, or document in server components; using Math.random() or new Date() which produce different values each time; and conditional rendering that depends on client-side state that the server doesn't have access to.
What does 'use client' do in Next.js?
Adding 'use client' at the very top of a Next.js component file (before any imports) tells Next.js to only run that component in the browser, skipping server rendering entirely. This prevents hydration errors caused by browser-only code. It's the most common and simplest fix for hydration errors in Next.js 13 and later. The tradeoff: client components don't benefit from server-side data fetching, so don't mark everything as a client component — only what needs it.
What is suppressHydrationWarning in React?
suppressHydrationWarning is a React prop you can add to an HTML element to tell React to ignore hydration mismatches for that specific element. It's appropriate for intentional mismatches like timestamps or user-specific content where a brief discrepancy is acceptable. It does not fix the underlying issue — it just silences the warning. Use it sparingly and only when you've consciously decided the mismatch is safe to ignore.
What is the difference between SSR and CSR?
Server-Side Rendering (SSR) generates the full HTML page on the server before sending it to the browser. The browser sees content immediately, even before JavaScript loads. This is what Next.js does by default. Client-Side Rendering (CSR) sends a mostly empty HTML page and relies on JavaScript to build the entire UI in the browser. SSR is faster for first paint and better for SEO; CSR is simpler but shows a loading state on first visit. Hydration only applies to SSR — it's the step that makes server-rendered HTML interactive.
The one thing to remember: A hydration error means your server and your browser generated different HTML. The fix is almost always: add 'use client', use dynamic() with ssr: false, or move browser-only code into useEffect. Next time AI generates a component that reads from localStorage or uses window — you'll know to check for 'use client' before it bites you.