Fix Next.js Hydration Errors Using Claude Code
The Problem
Your Next.js application throws a hydration error in the browser console:
Error: Hydration failed because the initial UI does not match what was
rendered on the server.
Warning: Expected server HTML to contain a matching <div> in <div>.
See more info here: https://nextjs.org/docs/messages/react-hydration-error
The page may flash, show incorrect content briefly, or lose interactive state. React cannot reconcile the server-rendered HTML with what the client tries to render.
Quick Fix
The most common cause is rendering browser-only values during SSR. Wrap dynamic content with a client-side check:
'use client';
import { useState, useEffect } from 'react';
function UserGreeting() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return <div>Welcome</div>; // Server-safe fallback
}
return <div>Welcome, it is {new Date().toLocaleTimeString()}</div>;
}
What’s Happening
Next.js renders your React components on the server to produce HTML. The browser receives this HTML and displays it immediately. Then React “hydrates” the page by attaching event handlers and reconciling the server HTML with the client-side React tree.
Hydration fails when the server and client produce different output. React expects the HTML structure to match exactly. When it does not match, React must discard the server HTML and re-render from scratch, which causes a flash of content and loses the performance benefits of SSR.
Common causes of mismatches:
- Date/time rendering: Server and client are in different timezones
- Random values:
Math.random()orcrypto.randomUUID()produce different values - Browser APIs:
window.innerWidth,localStorage,navigator.userAgent - Conditional rendering: Different CSS media query results
- Invalid HTML nesting:
<p>inside<p>,<div>inside<p> - Third-party scripts: Browser extensions modifying the DOM
Step-by-Step Fix
Step 1: Identify the mismatch
Ask Claude Code to find the source:
I have a hydration error in my Next.js app. Search the codebase for
components that use browser-only APIs (window, document, localStorage,
navigator), date formatting, or Math.random() during render. Also check
for invalid HTML nesting.
Claude Code will scan your components and identify the specific files causing mismatches.
Step 2: Fix date/time mismatches
Dates are the number one cause of hydration errors:
// BROKEN: Server renders UTC, client renders local timezone
function PostDate({ date }: { date: string }) {
return <time>{new Date(date).toLocaleDateString()}</time>;
}
// FIXED: Use suppressHydrationWarning for date display
function PostDate({ date }: { date: string }) {
return (
<time suppressHydrationWarning>
{new Date(date).toLocaleDateString()}
</time>
);
}
// BETTER: Render a stable format on server, enhance on client
function PostDate({ date }: { date: string }) {
const [formatted, setFormatted] = useState(date); // ISO string
useEffect(() => {
setFormatted(new Date(date).toLocaleDateString());
}, [date]);
return <time dateTime={date}>{formatted}</time>;
}
Step 3: Fix browser API usage
Components that access window or document during render cause mismatches:
// BROKEN: window is undefined on server
function ResponsiveLayout({ children }: { children: React.ReactNode }) {
const isMobile = window.innerWidth < 768; // Crashes on server
return <div className={isMobile ? 'mobile' : 'desktop'}>{children}</div>;
}
// FIXED: Use a hook that handles SSR
function useIsMobile(): boolean {
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const check = () => setIsMobile(window.innerWidth < 768);
check();
window.addEventListener('resize', check);
return () => window.removeEventListener('resize', check);
}, []);
return isMobile;
}
function ResponsiveLayout({ children }: { children: React.ReactNode }) {
const isMobile = useIsMobile();
return <div className={isMobile ? 'mobile' : 'desktop'}>{children}</div>;
}
Step 4: Fix localStorage/sessionStorage access
// BROKEN: localStorage not available during SSR
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState(
localStorage.getItem('theme') || 'light' // Crashes on server
);
// ...
}
// FIXED: Read from storage in useEffect
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState('light'); // Server-safe default
useEffect(() => {
const saved = localStorage.getItem('theme');
if (saved) {
setTheme(saved);
}
}, []);
return <ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>;
}
Step 5: Fix invalid HTML nesting
React is strict about valid HTML. These cause hydration errors:
// BROKEN: <div> cannot be inside <p>
<p>
Some text
<div className="highlight">Highlighted</div>
</p>
// FIXED: Use <span> or restructure
<p>
Some text
<span className="highlight">Highlighted</span>
</p>
// BROKEN: Interactive elements inside interactive elements
<a href="/link">
<button>Click me</button>
</a>
// FIXED: Use one or the other
<a href="/link" className="button-link">Click me</a>
Ask Claude Code to find all nesting violations:
Search my components for invalid HTML nesting that could cause
hydration errors. Check for: div inside p, p inside p, button inside a,
a inside a, interactive elements inside buttons.
Step 6: Fix third-party component issues
Some libraries render differently on server and client:
// Use dynamic import with ssr: false for client-only components
import dynamic from 'next/dynamic';
const MapComponent = dynamic(() => import('./Map'), {
ssr: false,
loading: () => <div className="map-skeleton" />,
});
function LocationPage() {
return (
<div>
<h1>Our Location</h1>
<MapComponent />
</div>
);
}
Step 7: Debug with React DevTools
If the error source is still unclear, add a debug boundary:
'use client';
import { useEffect, useState } from 'react';
function HydrationDebug({ children }: { children: React.ReactNode }) {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return (
<div data-hydrated={isClient}>
{children}
</div>
);
}
Prevention
Add these rules to your CLAUDE.md for Next.js projects:
## Next.js Hydration Rules
- Never access window, document, or localStorage during render
- Use useEffect for browser-only values
- Use suppressHydrationWarning only for dates/times
- Use dynamic import with ssr: false for client-only libraries
- Validate HTML nesting (no div inside p, no button inside a)
- Always test SSR output matches client output