How to Fix Next.js “Hydration failed because the initial UI does not match” Error [2026 Latest Guide]

スポンサーリンク

How to Fix Next.js “Hydration failed because the initial UI does not match” Error [2026 Latest Guide]

If you’re developing with Next.js and suddenly encounter the “Hydration failed because the initial UI does not match what was rendered on the server” error, you’re not alone. This error is especially prevalent in React 19 and Next.js 15 App Router environments, with thousands of comments on GitHub Discussions and consistently trending on Stack Overflow. This article provides a comprehensive 2026-updated guide to identifying the causes and applying specific solutions, complete with real code examples.

What Is This Error? Symptoms You’ll Experience

A Hydration Error in Next.js occurs when the HTML rendered on the server side does not match the HTML that React attempts to render on the client side during the initial render.

Specifically, the following error messages will appear in your browser’s developer console:

Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.

You may also see these variations:

Error: Text content does not match server-rendered HTML.
Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

When this error occurs, Next.js discards the server-side rendering (SSR) result and re-renders the entire page on the client side. This leads to several serious consequences:

  • Significant performance degradation: All benefits of SSR are lost, making initial page load slower
  • Negative SEO impact: Search engine crawlers may not receive correct HTML
  • Poor user experience: Screen flickering (flash of content) occurs
  • Developer experience suffers: Console fills with errors, making it easy to miss other issues

In Next.js 15 combined with React 19, hydration consistency checks have become stricter, and cases that were previously just warnings are now treated as errors.

Causes of This Error

Cause 1: Using Browser-Only APIs (window / localStorage / document)

The most frequent cause is directly referencing browser-only APIs like window, localStorage, and document during rendering. These objects don’t exist on the server side.

On the server, these objects are undefined. Therefore, using conditional branches like typeof window !== 'undefined' in your rendering logic causes different output between server and client, triggering hydration errors.

// BAD: This causes a hydration error
function MyComponent() {
  const isClient = typeof window !== 'undefined';
  return <div>{isClient ? 'Client' : 'Server'}</div>;
}

Cause 2: Non-deterministic Values Like Timestamps and Random Numbers

Using functions that return different values each time they’re called, such as new Date() or Math.random(), during rendering causes mismatches between server and client output.

// BAD: Timestamp differs between server and client
function Clock() {
  return <p>Current time: {new Date().toLocaleTimeString()}</p>;
}

There is always a time lag between when the server renders and when the client hydrates, so the displayed time will naturally differ, resulting in a text content mismatch error.

Cause 3: Invalid HTML Nesting Structure

Invalid nesting structures that violate HTML specifications (e.g., placing a <div> inside a <p> tag) cause the browser’s HTML parser to auto-correct the DOM, creating a DOM structure different from the HTML rendered on the server.

// BAD: <div> cannot be placed inside <p>
function BadNesting() {
  return (
    <p>
      Text
      <div>This is invalid nesting</div>
    </p>
  );
}

The browser auto-corrects this invalid HTML, causing a mismatch between the server-sent HTML and the browser’s DOM.

Cause 4: Browser Extensions Modifying the DOM

Browser extensions like Colorzilla and Grammarly can inject attributes or HTML elements into the page’s DOM. For example, Colorzilla adds a cz-shortcut-listen="true" attribute to the <body> tag. This has been reported as particularly problematic in Next.js 15 + React 19 environments.

Cause 5: Third-Party Scripts and CMS Content Inconsistencies

External scripts (ad tags, analytics, etc.) that modify the DOM, or CMS-sourced HTML content containing invalid nesting structures, can trigger hydration errors.

Solution 1: Isolate Client-Only Logic with useEffect Hook (Recommended)

The most effective and recommended solution is to move browser-dependent logic into the useEffect hook and output only deterministic values during the initial render.

Step 1: Implement the Client State Management Pattern

First, define a State with the same initial value for both server and client, then update it to the client-side value with useEffect.

'use client';

import { useState, useEffect } from 'react';

function ThemeProvider({ children }) {
  // Use a server-safe value as the initial state
  const [theme, setTheme] = useState('light');
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    // Only access localStorage on the client
    const savedTheme = localStorage.getItem('theme') || 'light';
    setTheme(savedTheme);
    setMounted(true);
  }, []);

  // Before mounting, return the same output as the server
  if (!mounted) {
    return <div data-theme="light">{children}</div>;
  }

  return <div data-theme={theme}>{children}</div>;
}

Step 2: Set Timestamps and Random Values Inside useEffect

'use client';

import { useState, useEffect } from 'react';

function Clock() {
  const [currentTime, setCurrentTime] = useState('');

  useEffect(() => {
    const updateTime = () => {
      setCurrentTime(new Date().toLocaleTimeString());
    };
    updateTime();
    const timer = setInterval(updateTime, 1000);
    return () => clearInterval(timer);
  }, []);

  // Show empty string on initial render (matches server)
  return <p>Current time: {currentTime || 'Loading...'}</p>;
}

Step 3: Make It Reusable with a Custom Hook

If you use this pattern across multiple components, extract it into a custom hook.

'use client';

import { useState, useEffect } from 'react';

// Custom hook for managing mount state
function useHasMounted() {
  const [hasMounted, setHasMounted] = useState(false);
  useEffect(() => {
    setHasMounted(true);
  }, []);
  return hasMounted;
}

// Usage example
function ClientOnlyComponent() {
  const hasMounted = useHasMounted();

  if (!hasMounted) {
    return <div>Loading...</div>;
  }

  return <div>Window width: {window.innerWidth}px</div>;
}

Important Notes

  • useEffect is not executed on the server side, so browser APIs can be safely used within it
  • Display a placeholder or fallback UI during the initial render to maintain user experience
  • This pattern may briefly show a placeholder, but it’s far lighter than full-page re-rendering caused by hydration errors

Solution 2: Disable SSR with next/dynamic

For components that are difficult to handle with the useEffect pattern (e.g., third-party libraries that heavily depend on browser APIs), disabling SSR entirely using next/dynamic is effective.

import dynamic from 'next/dynamic';

// Import with SSR disabled
const MapComponent = dynamic(
  () => import('../components/Map'),
  {
    ssr: false,
    loading: () => <div className="map-placeholder">Loading map...</div>
  }
);

// Usage example
function LocationPage() {
  return (
    <div>
      <h1>Store Location</h1>
      <MapComponent />
    </div>
  );
}

This method is especially effective in the following cases:

  • Map libraries (Leaflet, Google Maps, etc.): Because they depend on the window object
  • Rich text editors (Quill, TipTap, etc.): Because they directly manipulate the document object
  • Chart libraries (Chart.js, D3.js, etc.): Because they perform SVG or Canvas DOM operations

However, components with SSR disabled are not included in the initial HTML, so avoid this for SEO-critical content. Best practice is to limit its use to interactive elements other than main content (maps, charts, editors, etc.).

Solution 3: Fix HTML Nesting and Appropriate Use of suppressHydrationWarning

As an advanced solution, you can fix HTML nesting structures and use suppressHydrationWarning for unavoidable cases.

Fixing Invalid HTML Nesting

First, check for invalid HTML nesting. Here are common invalid patterns and their corrections:

// BAD: <div> inside <p>
<p>Text<div>Block element</div></p>

// OK: Change to <div>
<div>Text<div>Block element</div></div>

// BAD: <a> inside <a>
<a href="/parent">
  Parent link
  <a href="/child">Child link</a>
</a>

// OK: Remove nesting
<div>
  <a href="/parent">Parent link</a>
  <a href="/child">Child link</a>
</div>

Limited Use of suppressHydrationWarning

For small elements where server-client value differences are unavoidable (such as timestamp displays), you can use suppressHydrationWarning.

// Use only when differences are acceptable, like timestamps
<time suppressHydrationWarning>
  {new Date().toLocaleDateString()}
</time>

Important note: suppressHydrationWarning only hides the error; it doesn’t solve the underlying problem. It applies only to a single depth of text nodes, and child element mismatches are still detected. Use sparingly and only when absolutely necessary.

Handling Browser Extensions

If browser extensions are the cause, you can add suppressHydrationWarning to the <body> tag.

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body suppressHydrationWarning>{children}</body>
    </html>
  );
}

This suppresses warnings caused by attributes that extensions add to the body. However, mismatches in direct children of the body are still detected.

How to Prevent This Error

To prevent hydration errors proactively, incorporate these preventive measures into your daily development workflow.

1. Keep the Initial Render “Deterministic”

The most important principle is to guarantee identical output for both server and client initial renders. Environment-dependent values (browser APIs, timestamps, random numbers, etc.) must always be placed inside useEffect.

2. Clearly Separate Server Components and Client Components

In Next.js 15’s App Router, components are Server Components by default. When using browser APIs or React hooks (useState, useEffect, etc.), always add the 'use client' directive at the top of the file.

3. Leverage ESLint Rules

eslint-plugin-react includes rules to detect invalid HTML nesting (such as jsx-no-invalid-html-nesting). Integrate these into your CI pipeline for automated checks.

4. Regular Testing

Test periodically in incognito mode (private browsing) or with browser extensions disabled to isolate extension-related errors.

5. CSS-Driven Responsive Design

Instead of branching markup based on viewport size, use CSS media queries or display: none to toggle visibility, keeping the HTML structure consistent between server and client.

// BAD: Viewport branching in JS
{isMobile ? <MobileNav /> : <DesktopNav />}

// OK: CSS-based toggle
<MobileNav className="block md:hidden" />
<DesktopNav className="hidden md:block" />

Summary

The Next.js Hydration Error “Hydration failed because the initial UI does not match what was rendered on the server” occurs due to mismatches between SSR/SSG and client-side rendering, making it one of the most commonly encountered errors in Next.js development.

Key Takeaways:

  • The core issue is “different initial render results between server and client”
  • Most common causes: direct use of browser-only APIs, rendering non-deterministic values, invalid HTML nesting
  • Recommended solutions: staged rendering with useEffect, SSR disable with next/dynamic when needed
  • suppressHydrationWarning should be used as a last resort, with limited scope
  • Prevention: keep initial renders deterministic, clearly separate Server/Client Components

If these solutions don’t resolve the issue, check the following:

  1. Update Next.js and React to the latest versions
  2. Delete node_modules and .next folders and rebuild
  3. Search for similar cases on Next.js GitHub Discussions (https://github.com/vercel/next.js/discussions)
  4. Create a minimal reproduction and report it as an Issue

References

コメント

タイトルとURLをコピーしました