Back to Blogs
December 11, 2025

How to Fix Hydration Errors in Next.js: "Text content did not match server-rendered HTML"

#Next.js#React#Debugging#Hydration#Frontend#TypeScript
How to Fix Hydration Errors in Next.js: "Text content did not match server-rendered HTML"

If you have been building with Next.js for more than a week, you have inevitably encountered the dreaded red error screen: "Error: Text content did not match server-rendered HTML."

This is a Hydration Error, and it is one of the most highly searched issues by React developers transitioning to Server-Side Rendering (SSR). Today, we are going to break down exactly why this happens and how to fix it cleanly without sacrificing performance.

Why Do Hydration Errors Happen?

In Next.js, a page is first rendered on the server into static HTML. This HTML is sent to the browser so the user sees the page instantly. Then, React loads in the background and "hydrates" that HTML, turning it into an interactive application.

A hydration mismatch occurs when the HTML generated by the server differs from the HTML React expects to render on the client. The most common culprits are:

  1. Using window or localStorage directly in your component render.
  2. Rendering dates or times (the server's time zone differs from the user's browser time zone).
  3. Invalid HTML nesting (like putting a <div> inside a <p> tag).

Solution 1: The useEffect Mounting Trick

If you need to access the window object or localStorage, you must wait until the component has mounted on the client. Here is the cleanest pattern to handle this:

typescript
'use client';
import { useState, useEffect } from 'react';

export default function ThemeToggle() {
  const [mounted, setMounted] = useState(false);
  const [theme, setTheme] = useState('light');

  // useEffect only runs on the client, after the initial hydration
  useEffect(() => {
    setMounted(true);
    setTheme(window.localStorage.getItem('theme') || 'light');
  }, []);

  // Return a skeleton or null during SSR to perfectly match the server HTML
  if (!mounted) {
    return <div className="w-8 h-8 bg-slate-200 animate-pulse rounded-full" />;
  }

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current Theme: {theme}
    </button>
  );
}

Solution 2: suppressHydrationWarning

Sometimes, a mismatch is unavoidable and harmless—like rendering a timestamp that might differ by a millisecond between the server and the client. You can explicitly tell React to ignore the warning on a specific HTML element.

typescript
export default function CurrentTime() {
  const time = new Date().toLocaleTimeString();
  
  return (
    // Suppresses hydration warnings only for this specific element
    <time suppressHydrationWarning>
      {time}
    </time>
  );
}

Solution 3: Dynamic Imports with ssr: false

If you have a heavy client-side component (like a map, a rich text editor, or a complex chart) that relies heavily on browser APIs, you can tell Next.js to skip server-rendering it entirely.

typescript
import dynamic from 'next/dynamic';

// This component will only ever render on the client
const ClientOnlyChart = dynamic(() => import('@/components/HeavyChart'), { 
  ssr: false,
  loading: () => <p>Loading chart...</p>
});

export default function Dashboard() {
  return (
    <section>
      <h1>Analytics</h1>
      <ClientOnlyChart />
    </section>
  );
}

By understanding the boundary between the server and the browser, hydration errors become a minor annoyance rather than a project-blocking nightmare.


Muhammad Asad

Muhammad Asad

Senior Full-Stack Engineer & Consultant

Share / Connect:
Next Steps

Need help implementing this?

I partner with founders and technical teams to architect scalable, high-performance solutions.