Skip to content

Hydration Mismatch — Causes and Fixes for SSR Hydration Errors

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Hydration Mismatch. We cover key concepts, practical examples, and best practices to help you master this topic.

Hydration mismatch occurs when server-rendered HTML differs from what React expects on the client, causing console errors, unnecessary re-renders, and poor user experience.

What You'll Learn

By the end of this tutorial, you will understand what causes hydration mismatches, the most common sources of mismatches (dates, random values, browser APIs, data differences), how to debug hydration errors, and how to fix each type of mismatch with proper patterns.

Why It Matters

Hydration mismatches are the most common SSR bug. They cause React to re-render parts of the page on the client, defeating the purpose of SSR. Users may see a flash of changed content, and console errors confuse developers. Understanding mismatches is essential for building reliable SSR applications.

Real-World Use

A team migrating to Next.js spent 3 days debugging a hydration error. The cause: a component used localStorage to show a user preference. The server rendered with a default value, but the client immediately updated to the stored preference on hydration, causing a mismatch. The fix: using useState with lazy initialization.

Hydration Mismatch Flow
    ┌──────────────────────────────────────────────────────────┐
    │              Hydration Mismatch Process                  │
    ├──────────────────────────────────────────────────────────┤
    │                                                          │
    │  Server renders:                                         │
    │    <div>Theme: light</div>                               │
    │                                                          │
    │  HTML sent to browser                                    │
    │    User sees: Theme: light                               │
    │                                                          │
    │  Client hydrates:                                        │
    │    Component renders → Theme: dark (from localStorage)   │
    │                                                          │
    │  React compares:                                         │
    │    Server: <div>Theme: light</div>                       │
    │    Client: <div>Theme: dark</div>                        │
    │                                                          │
    │  Result: MISMATCH!                                       │
    │    Error: Hydration failed because initial UI does not   │
    │    match what was rendered on the server.                │
    │                                                          │
    │  React falls back: replaces DOM with client version      │
    │    User sees flicker: light → dark                      │
    │                                                          │
    └──────────────────────────────────────────────────────────┘

Think of hydration mismatch like trying to assemble a puzzle where the picture changed between when you looked at the reference and when you started assembling. The server gives you a blueprint (HTML), but the client builds something slightly different. React is the assembly worker who notices the mismatch and has to rebuild that section from scratch.

Common Mismatch: Browser-Only APIs

// Problem: Browser-only code runs during hydration
function ThemeSwitcher() {
    // localStorage is not available on the server
    // On the server, this throws an error
    const theme = localStorage.getItem('theme') || 'light';

    return <div className={theme}>Current theme: {theme}</div>;
}

// Fix 1: Lazy initialization with useState
function ThemeSwitcher() {
    const [theme, setTheme] = useState(() => {
        if (typeof window !== 'undefined') {
            return localStorage.getItem('theme') || 'light';
        }
        return 'light';
    });

    return <div className={theme}>Current theme: {theme}</div>;
}

// Fix 2: useEffect for client-only code
function ThemeSwitcher() {
    const [theme, setTheme] = useState('light');

    useEffect(() => {
        const stored = localStorage.getItem('theme');
        if (stored) {
            setTheme(stored);
        }
    }, []);

    // Server and initial hydration render: "light"
    // useEffect runs on client: updates to stored value
    // No mismatch! Both server and client render "light" initially
    return <div className={theme}>Current theme: {theme}</div>;
}

Common Mismatch: Dynamic Values

// Problem: Dynamic values differ between server and client
function TimeDisplay() {
    // Different time on server vs client
    const time = new Date().toLocaleTimeString();
    return <div>The time is {time}</div>;
}

// Fix: Use state and useEffect
function TimeDisplay() {
    const [time, setTime] = useState(null);

    useEffect(() => {
        setTime(new Date().toLocaleTimeString());
        const interval = setInterval(() => {
            setTime(new Date().toLocaleTimeString());
        }, 1000);
        return () => clearInterval(interval);
    }, []);

    // Server renders: "The time is " (no value)
    // Client hydrates: same — no mismatch!
    // Then useEffect fires: shows and updates time
    return <div>The time is {time || 'Loading...'}</div>;
}

// Problem: Random values
function RandomId() {
    const id = Math.random().toString(36).substring(7);
    return <div id={id}>Content with random ID</div>;
}

// Fix: Generate ID once and pass consistently
function RandomId({ id }) {
    // ID is generated on the server and passed to both
    return <div id={id}>Content with random ID</div>;
}

// Server:
// const id = generateId();
// renderToString(<RandomId id={id} />);

// Client:
// const id = window.__DATA__.id;
// hydrateRoot(root, <RandomId id={id} />);

Debugging Hydration Mismatches

// Enable hydration warnings in development
// React already logs warnings for hydration mismatches
// But you can add more detailed logging

// Custom debug component
function DebugHydration({ name, children }) {
    const serverHtml = useRef(null);

    if (typeof window === 'undefined') {
        // Server: render children
        return children;
    }

    // Client: check for mismatch
    return (
        <HydrationChecker name={name}>
            {children}
        </HydrationChecker>
    );
}

function HydrationChecker({ name, children }) {
    const elRef = useRef(null);

    useEffect(() => {
        if (elRef.current) {
            const expected = elRef.current.innerHTML;
            const actual = document.getElementById(
                `hydration-check-${name}`
            )?.innerHTML;

            if (expected !== actual) {
                console.warn(`Hydration mismatch in ${name}:`, {
                    expected,
                    actual,
                    component: name
                });
            }
        }
    });

    return <div ref={elRef}>{children}</div>;
}

// Alternative: React DevTools shows hydration warnings
// Console message example:
// Warning: Expected server HTML to contain a matching <div> in <div>.
//   mismatched node: <div>The time is 2:45 PM</div>
//   expected: <div>The time is Loading...</div>

Common Mistakes

  1. Using browser-only APIs without checks. window, document, localStorage, sessionStorage are not available on the server. Always check typeof window !== 'undefined' or use useEffect.
  2. Date/time/random values in render output. Values that differ between server and client cause mismatches. Use state + useEffect for dynamic values.
  3. Different data sources on server and client. If the server fetches data from an API and the client fetches it again on hydration, the data may differ. Always serialize and pass server data to the client.
  4. Third-party libraries that modify the DOM. Libraries that manipulate the DOM outside React (like jQuery plugins) can change server HTML before hydration, causing mismatches.
  5. CSS-in-JS without server extraction. If styles are applied differently on server and client, the rendered classes or styles may differ, causing hydration mismatches.

Practice Questions

  1. What are the three most common causes of hydration mismatch?
  2. How do you fix a mismatch caused by localStorage?
  3. Why does Date.now() cause a hydration mismatch?
  4. How do you debug a hydration mismatch in a large component tree?
  5. What happens when React encounters a hydration mismatch in production?

Challenge: Create an SSR page with 5 intentional hydration mismatches (localStorage, Date.now(), Math.random(), window.innerWidth, different API data). Fix each one using the appropriate pattern. Add a debug component that logs any hydration mismatches to the console with component names.

FAQ

Do hydration mismatches affect SEO?

Yes. If React re-renders content after hydration, the initial HTML that search engines see may differ from what users see. This can confuse SEO if content changes significantly.

Can I suppress hydration warnings?

You should fix the underlying cause, not suppress warnings. Suppressing warnings hides real bugs. Use suppressHydrationWarning attribute only as a last resort for intentional differences.

What is suppressHydrationWarning?

An attribute that tells React to ignore text content mismatches for that element. Use sparingly for things like timestamps where the difference is intentional and harmless.

How do I test for hydration mismatches?

Run your SSR app, view the page source to see server HTML, then inspect the DOM after hydration. If they differ, you have a mismatch. React logs warnings in development.

Does hydration mismatch affect performance?

Yes. React re-renders mismatched subtrees, which uses CPU and may cause layout shifts. Fixing mismatches improves both correctness and performance.

Mini Project

Build a debugging playground with 5 common hydration mismatches: a theme switcher using localStorage, a live clock, a random ID generator, a responsive component using window.innerWidth, a data list that fetches fresh data on the client. Fix each and verify no hydration errors in the console.

What's Next

You understand hydration mismatches. Now learn about Next.js Pages Router to use SSR in a production framework.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro