All posts
reactnextjshydrationdebugging

Hydration failed / text content mismatch — What It Means and How to Fix It

"Hydration failed / text content mismatch" explained — why it happens, a real code example that triggers it, and the exact fix.

SR

Suhail Roushan

August 6, 2026

·
3 min read
·
0 views

Hydration failed / text content mismatch means the HTML the server sent doesn't match what the browser rendered on first client-side render.

What "Hydration failed / text content mismatch" Means

This is a React (and Next.js) runtime error that occurs when the server-rendered HTML and the client-side virtual DOM disagree on the initial content. React expects a byte-for-byte match during hydration, and any difference throws this warning or error in the browser console.

Why It Happens

The most common causes are:

  1. Unstable data sources — using Date.now(), Math.random(), or localStorage during render produces different values on server vs. client.
  2. Browser-only APIs — accessing window, document, or navigator without guards crashes the server render or returns different results.
  3. Conditional rendering based on environment — checking typeof window !== 'undefined' inside JSX creates divergent output.

Example Code That Triggers It

Here's a minimal Next.js App Router component that will throw this error:

// app/page.tsx
export default function Page() {
  return (
    <div>
      <p>Current time: {new Date().toLocaleTimeString()}</p>
    </div>
  );
}

The server renders one timestamp. When the client hydrates, new Date() returns a different time. React sees the mismatch and logs: Hydration failed because the server rendered HTML didn't match the client.

How to Fix It

Use useEffect to defer browser-only calculations until after hydration:

// app/page.tsx
'use client';

import { useEffect, useState } from 'react';

export default function Page() {
  const [time, setTime] = useState('');

  useEffect(() => {
    setTime(new Date().toLocaleTimeString());
  }, []);

  return (
    <div>
      <p>Current time: {time || 'Loading...'}</p>
    </div>
  );
}

The fix works because useEffect runs only on the client after hydration completes. Both server and client render the same initial HTML (Loading...), then the client updates it. This guarantees a consistent first paint.

Common Mistakes That Cause This

  1. Suppressing the error with suppressHydrationWarning — this hides the symptom but leaves the underlying bug. You'll get inconsistent UI on first load and no error to trace later.

  2. Conditional rendering on typeof window — developers write {typeof window !== 'undefined' && <Component />} thinking it's safe. It's not: the server renders false, the client renders the component, and hydration fails.

When Should You Worry About This?

You should worry when the mismatch affects visible content that users interact with — forms, navigation, or data displays. If it's just a timestamp or a random ID, it's cosmetic but still worth fixing. If it causes layout shift or breaks event listeners, it's critical. React may skip attaching handlers to mismatched nodes, leaving buttons and inputs non-functional.

Also worry if you see this in production logs frequently. It signals your data layer isn't deterministic, which can lead to SEO issues since crawlers may see different content than users.

Next time this error appears, check the first thing that renders dynamic data — Date, Math.random, or any browser API call — and move it into a useEffect or a client-only component boundary.

Related posts

Written by Suhail Roushan — Full-stack developer. More posts on AI, Next.js, and building products at suhailroushan.com/blog.

Get in touch