Hydration failed because the initial UI does not match what was rendered on the

Fix React hydration error: text mismatch

Programming & Dev Tools Intermediate 👁 13 views 📅 May 25, 2026

React throws this when client and server HTML differ after hydration. Most common cause: date or time rendering that doesn't match timezone.

1. Date or time rendering mismatch (the most common cause)

Had a client last month whose Next.js ecommerce site started throwing this error on every product page. The culprit? A Date.now() call inside a component that showed 'Last updated 2 minutes ago'. The server rendered with UTC time, the client with local time. Boom — hydration error.

The fix is dead simple: don't render anything that depends on the current time, timezone, or locale during server-side rendering. Use useEffect to set the value after mount, or use Next.js dynamic imports with ssr: false.

import { useEffect, useState } from 'react';

function TimeAgo({ timestamp }) {
  const [label, setLabel] = useState(null);

  useEffect(() => {
    setLabel(timeSince(timestamp)); // runs only on client
  }, [timestamp]);

  if (!label) return null; // or a placeholder
  return {label};
}

If you're using React 18's streaming SSR, you can also wrap the component in a <Suspense> boundary. But honestly, the useEffect approach is cleaner for most cases.

2. localStorage or browser API references during SSR

Another classic: checking typeof window !== 'undefined' to access localStorage or sessionStorage in a conditional render. You'd think it's safe, but React's hydration still sees the mismatch between the server-rendered branch and the client branch.

I saw this on a Next.js dashboard where a dark mode toggle checked localStorage for a saved preference. The server rendered the toggle off, the client read localStorage and toggled it on — hydration error.

Solution: delay the localStorage read until after hydration. Same pattern with useEffect:

function ThemeToggle() {
  const [theme, setTheme] = useState(null);

  useEffect(() => {
    const saved = localStorage.getItem('theme') || 'light';
    setTheme(saved);
  }, []);

  // render nothing until we know the theme
  if (!theme) return null;

  return ;
}

If you can't avoid rendering something initially, use suppressHydrationWarning on the wrapping element. But be warned: this tells React to ignore the mismatch, which can lead to flickering. Use it sparingly.

3. Third-party scripts modifying the DOM before hydration

This one is sneaky. A third-party script (analytics, chat widget, A/B testing tool) runs before React hydration finishes and changes the HTML. React then tries to hydrate the altered DOM and fails.

I debugged this for a SaaS client whose Intercom widget was injecting a <style> tag into the <head> before React could hydrate. The server-rendered <head> didn't have that style, so hydration barfed.

Fix: defer third-party scripts until after React mounts. Add defer or async to script tags, or load them programmatically in a useEffect:

useEffect(() => {
  const script = document.createElement('script');
  script.src = 'https://third-party.com/widget.js';
  script.async = true;
  document.body.appendChild(script);
}, []);

If you're using Next.js, wrap the script in next/script with strategy="afterInteractive". That delays execution until after hydration.

Quick-reference summary table

Cause Symptoms Fix
Date/time rendering Text like '2 minutes ago' or formatted dates differ Use useEffect to set value after mount
localStorage/ browser APIs UI depends on client-only data (theme, user prefs) Defer reads until hydration completes
Third-party scripts Scripts inject DOM elements before React hydrates Load scripts after hydration (defer/useEffect)

One last thing: if you can't figure out which element is causing the error, open your browser console. React logs the mismatched HTML in the error message — look for the Server: ... and Client: ... lines. That'll point you straight to the culprit.

Was this solution helpful?