How to Fix Next.js Hydration Error [2026 Complete Guide]

スポンサーリンク

How to Fix Next.js Hydration Error [2026 Complete Guide]

Are you suddenly seeing “Hydration failed because the initial UI does not match what was rendered on the server” or “Text content does not match server-rendered HTML” while developing with Next.js? This article provides a thorough 2026-updated guide covering the causes and concrete solutions for hydration errors, including the latest Next.js 15 environment.


What Is This Error? Symptoms You’ll See

The Next.js Hydration Error occurs when there is a mismatch between the HTML generated by server-side rendering (SSR) and the HTML that React attempts to render on the first client-side render.

What Is Hydration?

Hydration is the process where React takes the pre-rendered static HTML from the server and attaches event handlers and state to make it “interactive” in the browser. It’s called “hydration” because it injects interactivity—like adding water to bring something to life.

Common Error Messages

Here are the typical error messages developers encounter in the console or browser:

  • Hydration failed because the initial UI does not match what was rendered on the server.
  • Text content does not match server-rendered HTML.
  • Error: Hydration failed because the server rendered HTML didn't match the client.
  • Warning: Expected server HTML to contain a matching <div> in <p>.

When Does It Occur?

This error primarily appears in the following situations:

  • A red error appears in the browser console during Next.js app development
  • Parts of a page don’t render correctly or interactions don’t work in production
  • It’s often first noticed during post-build preview
  • Chrome extensions modifying the DOM can also trigger it

In terms of impact, when a hydration error occurs, React attempts to completely rebuild the DOM on the client side, which can lead to performance degradation. Additionally, in Next.js 15 and later (React 19), hydration errors are handled more strictly—what were previously warnings may now be thrown as errors.


Why Does This Error Occur?

Hydration errors have many causes, but they can be broadly categorized into the following five groups.

Cause 1: Using Browser-Only APIs (window, localStorage, navigator)

Since the browser environment doesn’t exist on the server side, directly referencing browser-only objects like window, localStorage, navigator, or document in rendering logic produces different output on the server and client.

// ❌ BAD: window doesn't exist on the server, causing a mismatch
function MyComponent() {
  const width = window.innerWidth; // Error or undefined on server
  return <div>{width > 768 ? 'Desktop' : 'Mobile'}</div>;
}

A typical pattern is using typeof window !== 'undefined' conditional branching in rendering, which returns different JSX on the server and client, causing the error.

Cause 2: Using Dates, Times, and Random Values

Functions that return different values on each call—such as new Date(), Date.now(), Math.random(), and crypto.randomUUID()—will produce mismatched values between server and client when used during rendering.

// ❌ BAD: Different times displayed on server and client
function Clock() {
  return <p>Current time: {new Date().toLocaleTimeString()}</p>;
}

Timezone differences are also a common cause. When the server runs in UTC and the client is in a local timezone, the same Date object produces different strings.

Cause 3: Invalid HTML Nesting

When the HTML structure violates nesting rules (e.g., placing a <div> inside a <p> tag), the browser auto-corrects the HTML, creating a mismatch between the server-sent HTML and the browser-interpreted DOM.

// ❌ BAD: <div> cannot be placed inside <p>
function BadNesting() {
  return (
    <p>
      Text
      <div>This part is the problem</div>
    </p>
  );
}

Browsers follow HTML parser rules and automatically restructure the DOM tree when invalid nesting is detected. This results in a different state between the HTML sent by the server and the DOM interpreted by the browser.

Cause 4: Chrome Extensions Modifying the DOM

As of 2026, one of the most frustrating causes for many developers is Chrome extensions. Extensions like ColorZilla, Grammarly, Google Translate, and password managers add attributes or elements to the DOM before React’s hydration begins, causing mismatches with the server HTML.

Especially in Next.js 15 + React 19 environments, attributes injected by extensions like cz-shortcut-listen="true" have been reported as direct causes of hydration errors.

Cause 5: Third-Party Scripts and Widget Interference

Third-party scripts such as Google Analytics, A/B testing tools, chat widgets, and ad scripts can modify the DOM before React hydration, triggering errors. These scripts add or modify elements in <head> or <body>, creating discrepancies with the original server-sent HTML.


Solution 1: Client-Only Rendering with useEffect (Recommended)

The most recommended solution is using the useEffect hook to set values only on the client side. Since useEffect runs after hydration is complete, it maintains HTML consistency between server and client.

Step 1: State and useEffect Combination Pattern

When you need browser-specific values (screen width, localStorage values, etc.), set the initial value to match the server, then update to client-specific values inside useEffect.

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

function ResponsiveComponent() {
  // Step 1: Set initial value to match server
  const [isMobile, setIsMobile] = useState(false);

  // Step 2: Set client-specific value inside useEffect
  useEffect(() => {
    setIsMobile(window.innerWidth < 768);

    const handleResize = () => setIsMobile(window.innerWidth < 768);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return (
    <div>
      {isMobile ? <MobileNav /> : <DesktopNav />}
    </div>
  );
}

Step 2: Mount State Tracking Pattern

A more versatile pattern tracks whether the component has been mounted on the client side.

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

function ClientOnlyComponent() {
  const [isMounted, setIsMounted] = useState(false);

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

  // Before mount: same output as server (or skeleton)
  if (!isMounted) {
    return <div className="skeleton">Loading...</div>;
  }

  // After mount: display client-specific content
  return (
    <div>
      <p>Current time: {new Date().toLocaleTimeString()}</p>
      <p>Browser: {navigator.userAgent}</p>
    </div>
  );
}

Step 3: Create a Reusable Custom Hook

We recommend creating a custom hook for reuse across your project.

// hooks/useHydration.ts
'use client';
import { useState, useEffect } from 'react';

export function useHydrated() {
  const [hydrated, setHydrated] = useState(false);

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

  return hydrated;
}

// Usage
function MyComponent() {
  const hydrated = useHydrated();

  if (!hydrated) return <Skeleton />;

  return <div>{/* Client-specific content */}</div>;
}

Important Notes

  • It’s crucial to maintain the same output as the server during the initial render of useEffect
  • Use skeleton UI or loading indicators to minimize degradation of user experience
  • This approach may affect SEO, so apply it carefully to content you want search engines to index

Solution 2: Disable SSR with Dynamic Import

When the useEffect pattern is inconvenient or when the entire component is client-only, you can disable SSR using Next.js’s dynamic().

Basic Usage

import dynamic from 'next/dynamic';

// Import with SSR disabled
const ClientOnlyChart = dynamic(
  () => import('../components/Chart'),
  {
    ssr: false,
    loading: () => <p>Loading chart...</p>
  }
);

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

Usage with App Router

With Next.js 13+ App Router, dynamic works the same way.

// app/dashboard/page.tsx
import dynamic from 'next/dynamic';

const MapComponent = dynamic(
  () => import('@/components/Map'),
  { ssr: false }
);

export default function DashboardPage() {
  return (
    <main>
      <h1>Map View</h1>
      <MapComponent />
    </main>
  );
}

When This Approach Is Appropriate

  • Components using map libraries (Leaflet, Google Maps)
  • Components using chart libraries (Chart.js, D3.js)
  • Components heavily dependent on browser APIs
  • Editor components (Monaco Editor, CodeMirror, etc.)

Since SSR is completely disabled, the initial HTML won’t contain the component’s content. This method is not suitable for SEO-critical content.


Solution 3: Fix HTML Structure and Handle Browser Extensions (Advanced)

Fixing HTML Nesting Issues

If invalid HTML nesting is the cause, fix the structure to comply with HTML specifications.

// ❌ BAD: <div> cannot go inside <p>
<p>Text<div>Block element</div></p>

// ✅ OK: Wrap with <div> or use <span>
<div>
  <p>Text</p>
  <div>Block element</div>
</div>

// ✅ OK: Use inline elements
<p>Text<span>Inline element</span></p>

Special care is needed when embedding HTML from CMS or markdown using dangerouslySetInnerHTML. HTML from external sources often contains invalid nesting, so sanitization is recommended.

Handling Chrome Extensions

The following approaches are effective for hydration errors caused by extensions in development environments.

Method A: Restrict Extension Site Access

Right-click the Chrome extension icon and change “Read and change site data” to “When you click the extension.” This prevents extensions from modifying the DOM on localhost during development.

Method B: Create a Development-Only Browser Profile

Create a clean browser profile without extensions specifically for development to completely avoid extension-related hydration errors.

Method C: Use suppressHydrationWarning Selectively

Apply suppressHydrationWarning to elements where extensions commonly add attributes, such as the <body> tag.

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

Important: suppressHydrationWarning only works one level deep—child element mismatches are still detected. It can also hide real bugs in production, so avoid overusing it.

Advanced: Using MutationObserver

This method removes attributes injected by extensions before hydration.

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <script
          dangerouslySetInnerHTML={{
            __html: `
              (function() {
                const observer = new MutationObserver((mutations) => {
                  mutations.forEach((mutation) => {
                    if (mutation.type === 'attributes') {
                      const attr = mutation.attributeName;
                      if (attr && attr.startsWith('cz-')) {
                        mutation.target.removeAttribute(attr);
                      }
                    }
                  });
                });
                observer.observe(document.documentElement, {
                  attributes: true,
                  subtree: true,
                  attributeFilter: ['cz-shortcut-listen']
                });
              })();
            `,
          }}
        />
      </head>
      <body suppressHydrationWarning={true}>
        {children}
      </body>
    </html>
  );
}

How to Prevent Hydration Errors

Here are best practices for preventing hydration errors proactively.

1. Avoid Environment-Dependent Code During Rendering

Don’t use code that generates different values depending on the environment inside your component’s return statement (rendering logic). Move all environment-dependent logic into useEffect.

2. Leverage CSS Media Queries

For responsive design switching, CSS Media Queries are the safest approach—far better than JavaScript conditional branching.

/* Prefer CSS over JavaScript conditional branching */
.mobile-only { display: none; }
.desktop-only { display: block; }

@media (max-width: 768px) {
  .mobile-only { display: block; }
  .desktop-only { display: none; }
}

3. Automate HTML Validation

Use ESLint’s jsx-a11y plugin and eslint-plugin-react rules to detect invalid HTML nesting at build time.

4. Establish a Testing Environment

To verify that hydration errors aren’t occurring in your development environment:

  • Regularly test with next build && next start for production builds
  • Test with a clean browser profile that has no extensions
  • Monitor hydration errors in production using error tracking tools like Sentry

5. Optimize Third-Party Script Placement

For Google Analytics, ad scripts, and similar resources, use the strategy property of Next.js’s <Script> component to load them at the appropriate timing.

import Script from 'next/script';

// Load after hydration
<Script src="https://example.com/analytics.js" strategy="afterInteractive" />

// Load during idle time
<Script src="https://example.com/widget.js" strategy="lazyOnload" />

Summary

Next.js Hydration Error is caused by mismatches between server and client rendering results. In the 2026 Next.js 15 + React 19 environment, these are checked more strictly than before, making proper understanding and handling increasingly important.

Key Takeaways:

  1. The useEffect pattern is the most effective solution for most cases. Always handle browser-only APIs and dynamic values inside useEffect
  2. Dynamic import (ssr: false) should be used when the entire component is client-dependent
  3. Always be mindful of correct HTML structure—avoid invalid nesting like placing <div> inside <p>
  4. For Chrome extension issues, use a development profile or suppressHydrationWarning
  5. Prioritize CSS Media Queries and avoid layout switching through JavaScript conditional branching

If the methods above don’t resolve your issue, try searching for your error message in Next.js GitHub Discussions. You may find solutions from other developers who faced the same problem. Additionally, implementing error monitoring tools like Sentry to continuously monitor hydration errors in production is highly recommended.


References

コメント

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