Scaleup Infotech
Scaleup Infotech.
Back to Blog
Bug Fixes9 min read

How to Fix 'Hydration failed because the initial UI does not match' in Next.js

Scaleup Infotech

Scaleup Infotech

Software & Marketing Agency

Jun 02, 2026
How to Fix 'Hydration failed because the initial UI does not match' in Next.js
Next.jsReactHydrationSSR

If you have built anything with the Next.js App Router, you have almost certainly met this red wall of text. Your page renders, then React throws away the server HTML and complains that the markup it rendered on the client does not match what came from the server. Let's understand why it happens and walk through every reliable fix.

The Error

Hydration failed because the initial UI does not match what was rendered on the server. Warning: Text content did not match. Server: "..." Client: "..."

Why This Happens

Next.js renders your component to HTML on the server, sends it to the browser, then React 'hydrates' it — attaching event listeners and reconciling its own render against the existing DOM. Hydration fails whenever the first client render produces different markup than the server did. The most common triggers are:

  • Using Date.now(), new Date(), Math.random() or crypto.randomUUID() directly in render.
  • Reading window, localStorage, or navigator during the first render.
  • Invalid HTML nesting — a <div> inside a <p>, or a <p> inside another <p>.
  • Browser extensions (Grammarly, dark-mode injectors) mutating the DOM before hydration.
  • Locale or timezone-dependent formatting that differs between server and client.

Fix 1: Defer Browser-Only Values to useEffect

Anything that can only be known in the browser must not be rendered on the first pass. Render a stable placeholder, then update inside useEffect (which never runs on the server):

tsx
"use client";
import { useEffect, useState } from "react";

export function Clock() {
  const [time, setTime] = useState<string | null>(null);

  // Runs only in the browser, after hydration is complete.
  useEffect(() => {
    setTime(new Date().toLocaleTimeString());
  }, []);

  // First render matches the server (null), so no mismatch.
  return <span>{time ?? "--:--:--"}</span>;
}

Fix 2: Use suppressHydrationWarning for Unavoidable Mismatches

For a single element whose content is legitimately time-dependent, React lets you silence the warning for that node only. Use it sparingly:

tsx
<time suppressHydrationWarning>{new Date().getFullYear()}</time>

Fix 3: Dynamically Import Client-Only Components

If an entire component depends on the browser (charts, maps, editors), skip SSR for it entirely with next/dynamic:

tsx
import dynamic from "next/dynamic";

const MapView = dynamic(() => import("./MapView"), { ssr: false });

Fix 4: Check Your HTML Nesting

The browser silently 'repairs' invalid HTML, which changes the DOM out from under React. A classic offender is putting a block element inside a paragraph. Replace the outer <p> with a <div> and the mismatch disappears.

Quick Checklist

1) No Date/Math.random in render. 2) No window/localStorage on first render. 3) Valid HTML nesting. 4) Test in an incognito window to rule out extensions.

Hydration errors look scary but almost always reduce to *the server and client disagreed about the first render*. Make that first render deterministic and the error is gone for good.

Share this article:

Keep Reading

Ready to implement these ideas?

Work With Scaleup Infotech