Tutorials Logic, IN info@tutorialslogic.com

Hydration failed in React SSR Mismatch Fix: Causes and Fixes

What is This Error?

The "Hydration failed" error occurs in React apps with Server-Side Rendering (SSR) "" like Next.js "" when the HTML generated on the server doesn't match what React tries to render on the client. React "hydrates" the server HTML by attaching event listeners, but if the content differs, it throws this error.

Common Causes

  • Using browser-only APIs (window, document, localStorage) during SSR
  • Rendering different content based on client-side state (like Date.now())
  • Invalid HTML nesting (e.g., <p> inside <p>, <div> inside <p>)
  • Browser extensions modifying the DOM before hydration
  • Using Math.random() or Date.now() that produces different values server vs client

Quick Fix (TL;DR)

Immediate Fix: Problem different output on server vs client

Immediate Fix: Problem different output on server vs client
// Wrong Problem "" different output on server vs client
function Clock() {
  return <p>Time: {new Date().toLocaleTimeString()}</p>; // Different each render!
}

// Correct Solution "" use useEffect for client-only content
function Clock() {
  const [time, setTime] = useState('');

  useEffect(() => {
    setTime(new Date().toLocaleTimeString()); // Only runs on client
  }, []);

  return <p>Time: {time || 'Loading...'}</p>;
}

Common Scenarios & Solutions

Failure: Window doesn't exist on server

Failure: Window doesn't exist on server
// Wrong window doesn't exist on server
function ThemeToggle() {
  const theme = localStorage.getItem('theme') || 'light'; // Error on server!
  return <div className={theme}>Content</div>;
}

Correction: Read localStorage only on client via useEffect

Correction: Read localStorage only on client via useEffect
// Correct Read localStorage only on client via useEffect
function ThemeToggle() {
  const [theme, setTheme] = useState('light'); // Default for SSR

  useEffect(() => {
    const saved = localStorage.getItem('theme');
    if (saved) setTheme(saved); // Only runs on client
  }, []);

  return <div className={theme}>Content</div>;
}

Failure: Invalid HTML div inside p

Failure: Invalid HTML div inside p
// Wrong Invalid HTML "" div inside p
<p>
  <div>This is invalid HTML!</div>  {/* Browser auto-corrects, causing mismatch */}
</p>

// Wrong a inside a
<a href="/outer">
  <a href="/inner">Nested links</a>  {/* Invalid! */}
</a>

Correction: Use span inside p (inline elements only)

Correction: Use span inside p (inline elements only)
// Correct Use span inside p (inline elements only)
<p>
  <span>This is valid!</span>
</p>

// Correct Or change p to div
<div>
  <div>This is valid!</div>
</div>

Correction: SuppressHydrationWarning for intentional differences

Correction: SuppressHydrationWarning for intentional differences
// Correct suppressHydrationWarning for intentional differences
<time suppressHydrationWarning>
  {new Date().toLocaleTimeString()}
</time>

// Correct Dynamic import with ssr: false (Next.js)
import dynamic from 'next/dynamic';

const ClientOnlyComponent = dynamic(
  () => import('./ClientOnlyComponent'),
  { ssr: false }  // Don't render on server
);

Correction: Render client-only content after mount

Correction: Render client-only content after mount
// Correct Render client-only content after mount
function ClientOnly({ children }) {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  if (!mounted) return null; // Same as server output

  return children;
}

// Usage
<ClientOnly>
  <ComponentThatUsesWindow />
</ClientOnly>

Best Practices to Avoid This Error

  • Use useEffect for browser APIs - window, document, localStorage only run on client
  • Provide consistent initial state - Server and client must render the same HTML initially
  • Validate HTML nesting - Use an HTML validator to check for invalid nesting
  • Use dynamic imports with ssr: false - For components that can't run on server
  • Avoid non-deterministic values - Don't use Date.now() or Math.random() in render
  • Test with SSR disabled - Isolate whether the issue is SSR-specific
  • Use suppressHydrationWarning sparingly - Only for intentional client/server differences
Before you move on

Hydration failed in React SSR Mismatch Fix: Causes and Fixes Mastery Check

4 checks
  • Make the first client render deterministic and equivalent to the server-produced markup.
  • Move browser-only reads, time-dependent values, and client personalization behind an effect or client boundary.
  • Validate HTML nesting and compare the server response with the DOM expected before hydration.
  • Use hydration-warning suppression only for one understood unavoidable mismatch, never as a broad repair.

Try this next

React JS Hydration Failed Repair Drills

0 of 2 completed

  1. Reproduce a mismatch from Date.now or Math.random, pass the server value into the client render, and verify the initial HTML is identical. Move later client-only changes behind an effect after hydration.
  2. Repair a component that reads localStorage during its first render, then compare the server response, pre-hydration DOM, and hydrated result. Also validate HTML nesting before suppressing any hydration warning.

React JS Questions Learners Ask

The server-rendered HTML can look fine before React takes over, but hydration compares that HTML with what the client render produces. If the text, attributes, element order, or generated IDs differ, React cannot safely attach event handlers to the existing DOM.

localStorage exists only in the browser. If a component reads it during render, the server must render without that value while the client may render with a saved value immediately.

Do not read window during server render. Put browser-only reads inside useEffect, guard utility code with typeof window !== "undefined", or load a browser-only component dynamically with SSR disabled when the whole component depends on the DOM.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.