← Cheatsheets / Error Fixes

How to Fix Hydration Mismatch Errors

Getting 'Hydration failed' or 'Text content mismatch' errors in React, Next.js, or Astro? Here's what causes hydration mismatches and how to fix them.

hydration React Next.js Astro SSR debugging error-fix

Your page loads, then breaks. The console says “Hydration failed.” This error means the HTML the server rendered doesn’t match what the client-side JavaScript tried to create. React can’t reconcile the difference, so it either errors out or re-renders everything (losing performance).

AI tools are particularly bad at causing hydration errors because they don’t think about the server vs. client distinction.


What Is Hydration?

In plain English: The server generates HTML. The browser receives it. Then React “hydrates” it — attaching JavaScript interactivity to the existing HTML.

If the HTML React expects to create on the client doesn’t match what the server already sent, you get a hydration mismatch. React can’t “adopt” the server HTML because it’s different from what it would render.

Common Error Messages

# React 18+
Hydration failed because the initial UI does not match what was rendered on the server.
Text content does not match. Server: "X" Client: "Y"
Expected server HTML to contain a matching <div> in <p>.

# Next.js
Error: Hydration failed because the initial UI does not match
Warning: Prop `className` did not match.

# Astro
Hydration directive error or client:load mismatch

5 Common Causes

1. Using Date, Math.random(), or window During Render

These produce different values on server vs. client.

// 💥 This causes a mismatch:
function Greeting() {
  const hour = new Date().getHours();  // Different on server vs client!
  return <p>It's {hour}:00</p>;
}

// Fix: use useEffect for client-only values
function Greeting() {
  const [hour, setHour] = useState<number | null>(null);
  useEffect(() => {
    setHour(new Date().getHours());
  }, []);
  return <p>{hour !== null ? `It's ${hour}:00` : 'Loading...'}</p>;
}

2. Invalid HTML Nesting

Browsers auto-correct invalid HTML, creating a mismatch.

// 💥 <p> can't contain <div> — browser auto-fixes it
function Card() {
  return (
    <p>
      <div>This is inside a paragraph</div>  {/* Invalid! */}
    </p>
  );
}

// Fix: use valid nesting
function Card() {
  return (
    <div>
      <div>This is inside a div</div>
    </div>
  );
}

Common invalid nestings that cause hydration errors:

  • <p> containing <div>, <p>, <ul>, <table>
  • <a> containing <a> (nested links)
  • <button> containing <button>

3. Browser Extensions Modify the DOM

Browser extensions (Dark Reader, Grammarly, ad blockers) inject elements into your HTML, causing mismatches.

// This is NOT your fault — suppress the warning:
// Next.js: add suppressHydrationWarning to <html> or <body>
<html suppressHydrationWarning>
  <body suppressHydrationWarning>
    {children}
  </body>
</html>

4. Conditional Rendering Based on Client-Only APIs

// 💥 window doesn't exist on server
function Layout() {
  const isMobile = window.innerWidth < 768;  // Server crash or mismatch
  return isMobile ? <MobileNav /> : <DesktopNav />;
}

// Fix: detect client-side with useEffect
function Layout() {
  const [isMobile, setIsMobile] = useState(false);
  useEffect(() => {
    setIsMobile(window.innerWidth < 768);
    const handleResize = () => setIsMobile(window.innerWidth < 768);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  return isMobile ? <MobileNav /> : <DesktopNav />;
}

5. localStorage / sessionStorage in Render

// 💥 localStorage doesn't exist on server
function ThemeProvider({ children }) {
  const theme = localStorage.getItem('theme') || 'light';  // Server error!
  return <div className={theme}>{children}</div>;
}

// Fix: read from storage in useEffect
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  useEffect(() => {
    const saved = localStorage.getItem('theme');
    if (saved) setTheme(saved);
  }, []);
  return <div className={theme}>{children}</div>;
}

Step-by-Step Fix Process

Step 1: Read the Error Details

React 18+ tells you exactly what mismatched:

Text content does not match. Server: "Tuesday" Client: "Wednesday"

This tells you: a date or time-dependent value differs between server and client.

Step 2: Find the Dynamic Value

Search your component for anything that varies between server and client:

  • new Date(), Date.now()
  • Math.random()
  • window.*, document.*, navigator.*
  • localStorage, sessionStorage
  • Conditional rendering based on client-only state

Step 3: Move Client-Only Logic to useEffect

// The universal fix pattern:
const [clientValue, setClientValue] = useState(serverSafeDefault);
useEffect(() => {
  setClientValue(getClientOnlyValue());
}, []);

Step 4: Validate Your HTML

Check for invalid nesting. Use the W3C validator or React DevTools to spot issues.


Framework-Specific Fixes

Next.js (App Router)

// Use 'use client' for components that need browser APIs
'use client';

// Or use dynamic imports to skip SSR entirely
import dynamic from 'next/dynamic';
const ClientOnlyComponent = dynamic(() => import('./MyComponent'), {
  ssr: false,
});

Next.js (Suppress Warnings)

// For things you can't fix (browser extensions, etc.)
<time suppressHydrationWarning>{new Date().toLocaleDateString()}</time>

Astro

<!-- Use client:only to skip SSR for a component -->
<ReactComponent client:only="react" />

<!-- vs client:load which SSRs then hydrates -->
<ReactComponent client:load />

🤖 Prompt to Fix This

I'm getting a hydration mismatch error in my [Next.js/React/Astro] app:

[PASTE THE FULL ERROR MESSAGE]

Here's the component that's causing it:

[PASTE THE COMPONENT CODE]

My setup: [Next.js App Router / Pages Router / Astro with React]

Please:
1. Identify what's causing the server/client mismatch
2. Show the fixed component code
3. Use the right pattern (useEffect, dynamic import, or client:only)
4. Make sure the fix doesn't cause a flash of wrong content

Prevention Tips

  1. Never access window, localStorage, or document during render — always in useEffect
  2. Use valid HTML nesting — no <div> inside <p>, no nested <a> tags
  3. Avoid dates/random in render — use useEffect or pass from server as props
  4. Use suppressHydrationWarning sparingly — only for things you can’t control
  5. Tell AI you’re using SSR — “This is a Next.js App Router server component” changes what AI generates

✓ Copied to clipboard!