On this page

Engineering7 min read

How to Fix the Next.js Hydration Error (Text Content Does Not Match)

The Next.js hydration error "Text content does not match server-rendered HTML" breaks your app silently. Here's what causes it and how to fix it permanently.

Next.jsReacthydrationdebuggingSSR

What Is a Hydration Error?

When Next.js renders a page, it does it twice: once on the server to send HTML to the browser, and once on the client where React hydrates that HTML to make it interactive. If what React renders on the client does not match what the server sent, you get a hydration mismatch.

Error: Text content does not match server-rendered HTML. Warning: Expected server HTML to contain a matching <div> in <div>.

This usually shows as a white flash, broken layout, or a full-page re-render. In production it is silent but damages user experience and Core Web Vitals.

The 5 Most Common Causes

1. Date or Math.random() Called During Render

Server renders at request time. Client renders at load time. Any value that changes between those two moments will mismatch.

javascript
// Always mismatches — server time is not client time
function BlogDate() {
  return <p>{new Date().toLocaleDateString()}</p>
}

Move time-dependent values into useEffect so they only run on the client:

javascript
function BlogDate() {
  const [date, setDate] = useState('')
  useEffect(() => {
    setDate(new Date().toLocaleDateString())
  }, [])
  return <p>{date}</p>
}

2. Browser-Only APIs in Render

window, localStorage, navigator do not exist on the server. If your component reads them during render, the server crashes or returns undefined while the client gets the real value.

Error: ReferenceError: window is not defined

javascript
// Crashes on server
function ThemeToggle() {
  const theme = localStorage.getItem('theme') || 'light'
  return <button>{theme}</button>
}

Move the browser API call to useEffect:

javascript
function ThemeToggle() {
  const [theme, setTheme] = useState('light')
  useEffect(() => {
    setTheme(localStorage.getItem('theme') || 'light')
  }, [])
  return <button>{theme}</button>
}

3. Third-Party Scripts That Modify the DOM

Ad scripts, chat widgets, and analytics tools sometimes inject DOM nodes before React hydrates. React then finds unexpected elements and fails.

Warning: Hydration failed because the server rendered HTML did not match the client. The most common reason is that a browser extension modified the HTML before React could hydrate it.

Use the Next.js <Script> component with strategy="lazyOnload" instead of raw <script> tags in _document. This ensures third-party scripts fire after hydration completes:

javascript
import Script from 'next/script'

<Script src="https://cdn.example.com/widget.js" strategy="lazyOnload" />

4. Invalid HTML Nesting

React and the browser both enforce HTML nesting rules. If you nest a <div> inside a <p>, or a <p> inside an <a>, the browser silently fixes it during parsing but React's VDOM does not match the corrected DOM.

javascript
// div inside p is invalid HTML
function Card({ text, children }) {
  return (
    <p>
      {text}
      <div>{children}</div>
    </p>
  )
}

Use <div> or <span> consistently. Run your page through the W3C HTML validator if you are not sure.

5. Conditional Rendering Based on typeof window

This pattern looks safe but is not. React tries to reconcile server output where window is undefined with client output where it exists.

javascript
// Hydration mismatch — server and client render different things
function Component() {
  if (typeof window === 'undefined') return null
  return <div>Client content</div>
}

Use the mounted pattern instead:

javascript
function Component() {
  const [mounted, setMounted] = useState(false)
  useEffect(() => setMounted(true), [])
  if (!mounted) return null
  return <div>Client content</div>
}

Suppressing vs Fixing

You will find this suggested online:

javascript
// Do not do this
<div suppressHydrationWarning>
  {new Date().toLocaleDateString()}
</div>

suppressHydrationWarning hides the error but does not fix it. React still re-renders the whole subtree on the client, causing layout shift. Use it only for values that genuinely cannot be known at SSR time, like browser fingerprints or ad content, and isolate it to a single element.

How to Debug Hydration Errors Fast

Next.js 13+ shows a detailed diff in development mode. Look for this in your terminal or browser console:

Warning: Text content did not match.
  Server: "Hello"
  Client: "Hello!"

The server/client diff tells you exactly which component is mismatching.

Note: In Next.js 14+, hydration errors include a component stack trace in dev mode. Run next dev not next start to see the full trace.

Summary

CauseFix
new Date() or Math.random() in renderMove to useEffect
window or localStorage accessGuard with useEffect or mounted pattern
Third-party DOM scriptsUse <Script strategy="lazyOnload">
Invalid HTML nestingFix nesting and validate HTML
typeof window conditional renderUse mounted state pattern

Hydration errors are always a server/client mismatch. Find what is different between the two environments and defer the client-only part to useEffect. For complex component trees where the mismatch source is not obvious, paste the error and component into DebugAI and it will trace the exact render path causing the diff.

Debug faster starting today.

Free VS Code extension. 10 sessions/day. No credit card.

Install Free →

Related Posts

Engineering

GitHub Copilot Just Changed Its Pricing. What Developers Need to Know

5 min read

Engineering

Why Your AI Agent Harness Fails at Debugging (And How to Fix It)

5 min read

← All posts