ReactNext.jsHydrationSSR

React Hydration Failed: Causes, Debugging, and Fixes

Debug server and client rendering mismatches caused by dates, random values, browser APIs, invalid HTML, local storage, locale differences, and DOM-changing extensions.

·Updated ·9 min·Counting...
React Hydration Failed: Causes, Debugging, and Fixes

A hydration failure means React received server-rendered HTML, but the client's first render produced different text or structure. The durable fix is to locate the unstable output—not to silence the warning across an entire component tree.

TOC

The most common causes

  1. Calling Date.now(), new Date(), or Math.random() while rendering.
  2. Returning different JSX from a typeof window !== "undefined" branch.
  3. Reading localStorage, viewport size, or media queries during the first render.
  4. The server and client rendering different data snapshots.
  5. Invalid HTML nesting that the browser repairs before React starts.
  6. A browser extension or third-party script changing the DOM.
  7. Different locale, time-zone, or number-formatting behavior.

Dates and random values

This output is not stable:

export function Timestamp() {
  return <time>{new Date().toLocaleString()}</time>
}

The server and browser may use different times, locales, and time zones. Pass a stable value from the server instead:

export function Timestamp({ iso }: { iso: string }) {
  return <time dateTime={iso}>{iso.slice(0, 10)}</time>
}

If the interface must display the viewer's local time, render a stable placeholder first and update it after hydration. Reserve enough space to avoid an unnecessary layout shift.

Browser APIs and local storage

Do not make the first render depend on browser detection:

// Incorrect: the server and client can return different text
return <p>{typeof window === "undefined" ? "server" : "client"}</p>

Read client-only values in an effect when appropriate:

"use client"

import { useEffect, useState } from "react"

export function ThemeLabel() {
  const [theme, setTheme] = useState("system")

  useEffect(() => {
    setTheme(localStorage.getItem("theme") ?? "system")
  }, [])

  return <span>Theme: {theme}</span>
}

Both environments initially render system; the browser synchronizes the stored value afterward. If the correct theme is required before the first paint, use a server-readable cookie or a carefully tested initialization script.

Invalid HTML nesting

Browsers repair invalid markup, so the DOM React hydrates may not match the original server string:

// Incorrect
<p>
  Introduction
  <div>Details</div>
</p>

Inspect the actual Elements tree, not just the component source. Rich-text renderers, Markdown pipelines, and third-party UI components are common sources of invalid nesting.

Data snapshot mismatches

Typical causes include:

  • the server reading cached version A while the client immediately fetches version B;
  • different sorting or filtering rules in each environment;
  • client code constructing a second default state instead of reusing server data;
  • locale-dependent formatting without an explicit locale and time zone.

For the first screen, fetch data in a Server Component when possible, then pass the same snapshot into Client Components that need hydration.

A practical debugging sequence

  1. Read the component stack and the text difference in the error.
  2. Reduce the problem to the smallest suspicious Client Component.
  3. Search for Date, Math.random, window, document, localStorage, and locale formatting.
  4. Validate HTML nesting.
  5. Reproduce in a private browser window with extensions disabled.
  6. Compare View Source—the server HTML—with the post-hydration Elements DOM.
  7. Test a production build, not only the development server.

When to use suppressHydrationWarning

suppressHydrationWarning is a narrow escape hatch for a known, unavoidable single-level text difference. It does not repair data flow or a mismatched subtree.

<time suppressHydrationWarning>{clientSpecificTime}</time>

If structure, event binding, or business state differs, fix the rendering source. Adding the prop to a large wrapper only hides evidence.

Client Components do not automatically solve it

A Client Component can still participate in the initial server-rendered preview and then hydrate in the browser. Adding "use client" does not guarantee matching output. Keep the initial render deterministic and isolate truly browser-only behavior.

For another common Next.js boundary decision, see Server Actions vs client fetch.

References

Subscribe to FreeMac

Weekly picks: free Mac software reviews, trusted source updates, alternatives, and low-friction guides.