React Error 418 Next.js

React Error #418: Hydration failed because the initial UI does not match β€” Fixed

React Error #418: Hydration failed β€” server vs client mismatch

What is hydration β€” and why does it fail?

When you use a framework like Next.js with SSR or SSG, React renders your component tree twice: once on the server (to generate the HTML string sent to the browser) and once on the client (to attach event listeners to that HTML). This second pass is called hydration.

React Error #418 fires when the HTML produced by the server does not exactly match the DOM that the client-side render would produce. React cannot reconcile them β€” so it either bails out and re-renders from scratch (degrading performance) or throws a fatal error that unmounts your app.

Cause 1: Accessing window, localStorage, or navigator at render time

These browser-only APIs do not exist on the server. Calling them outside a useEffect produces a different render on server vs. client.

❌ Causes the error

// ❌ BAD: `window` is undefined on the server.
// Server renders: <p></p>
// Client renders: <p>1920</p> β†’ MISMATCH

export default function ScreenWidth() {
  const width = window.innerWidth; // ReferenceError on server

  return <p>{width}</p>;
}

βœ… Correct β€” defer to client with useEffect

import { useState, useEffect } from 'react';

export default function ScreenWidth() {
  // Server renders null β†’ matches the initial client render
  const [width, setWidth] = useState<number | null>(null);

  useEffect(() => {
    // Only runs on the client, after hydration
    setWidth(window.innerWidth);
  }, []);

  if (width === null) return <p>Loading…</p>;
  return <p>{width}px</p>;
}

Cause 2: Dynamic dates or random values rendered at SSR time

new Date() called at render time returns the server's timestamp. By the time the client hydrates, milliseconds have passed β€” the strings are different.

❌ Causes the error

// ❌ BAD: Server: "03/08/2026, 14:32:00"
//         Client: "03/08/2026, 14:32:01" β†’ MISMATCH

export default function Timestamp() {
  return <time>{new Date().toLocaleString()}</time>;
}

βœ… Correct β€” render date only after mount

import { useState, useEffect } from 'react';

export default function Timestamp() {
  const [date, setDate] = useState<string>('');

  useEffect(() => {
    setDate(new Date().toLocaleString());
  }, []);

  return <time>{date || 'β€”'}</time>;
}

Cause 3: Invalid HTML nesting caught by the browser parser

Browsers auto-correct invalid HTML before React hydrates β€” creating a DOM structure that never matches the server output. The most common example: a <p> wrapping a <div>.

❌ Causes the error

// ❌ BAD: <p> cannot contain block-level elements.
// The browser extracts the <div> and closes <p> early.

export default function Card({ content }: { content: React.ReactNode }) {
  return (
    <p>
      <div className="card">{content}</div>  {/* invalid! */}
    </p>
  );
}

βœ… Correct β€” use semantically valid wrappers

export default function Card({ content }: { content: React.ReactNode }) {
  return (
    <div>                          {/* block wrapping block β†’ valid */}
      <div className="card">{content}</div>
    </div>
  );
}

When suppressHydrationWarning is the right call

If the mismatch is intentional and inconsequential β€” for example, a timestamp on a user's local timezone displayed inside a <time> element β€” React lets you opt out of the hydration check per-element. Do not use this as a blanket fix.

βœ… Correct β€” suppress only for known-safe intentional mismatches

// OK: The server sends a UTC timestamp. The client replaces it immediately.
// The visual flicker is acceptable (no layout shift for text).

export default function LocalTime({ utcDate }: { utcDate: string }) {
  return (
    <time
      dateTime={utcDate}
      suppressHydrationWarning  // tells React: I know, it's intentional
    >
      {new Date(utcDate).toLocaleTimeString()}
    </time>
  );
}

Alternative: Skip SSR entirely with dynamic()

For entire components that are inherently client-only (maps, editors, charts), Next.js's dynamic() with ssr: false is cleaner than wrapping every browser API call in a useEffect.

βœ… Correct β€” disable SSR for browser-only components

// libs/MapComponent is a Leaflet map β€” server-side render makes no sense.

import dynamic from 'next/dynamic';

const MapComponent = dynamic(
  () => import('../libs/MapComponent'),
  {
    ssr: false,           // never runs on server β†’ no hydration mismatch
    loading: () => <p>Loading map…</p>,
  }
);

export default function LocationPage() {
  return <MapComponent center={[40.416775, -3.703790]} />;
}

How to find which node is mismatching

1. Read the console error carefully

React 18+ prints the specific tag that mismatched. Look for this pattern in your browser DevTools console:

Warning: Expected server HTML to contain a matching <div> in <main>.

The outer element name (<main>) is the parent β€” inspect the direct children of that element in the DevTools β†’ Elements panel.

2. Add ?__NEXT_HYDRATION_DEBUG=1 to the URL (Next.js)

In Next.js 13.4+ you can append this query param to any page to get verbose hydration diff output printed to the console. It shows the exact text content or attribute that differed.

3. Temporarily disable JavaScript

In DevTools β†’ Settings β†’ Debugger β†’ Disable JavaScript, then reload. The page now shows pure SSR HTML. Compare this with the rendered output when JS is enabled β€” any visual difference is your mismatch.

Frequently Asked Questions

The error only happens in production β€” not in next dev. Why?

In development, Next.js uses client-side rendering for fast refresh and does not strictly enforce SSR/CSR parity. In production (next build && next start), SSR is always enabled for pages without export const dynamic = 'force-dynamic'. The hydration mismatch only surfaces when the server actually sends pre-rendered HTML. Always test hydration behavior with next build && next start, not next dev.

Why does the error reference a "text node" with no useful information?

React sometimes points to a text node (rendered whitespace or a String literal) rather than a component. This usually means a browser extension modified the DOM between SSR and hydration. Reproduce the error in an incognito window with all extensions disabled. If the error disappears, a browser extension (ad blockers, password managers) is injecting DOM nodes β€” use suppressHydrationWarning on the parent container as a targeted fix.

Can React.StrictMode cause false hydration errors?

No β€” but it can surface existing hydration errors more aggressively. StrictMode double-invokes render functions in development to detect side effects. If removing StrictMode makes the error disappear, your component has a render-time side effect that is now exposed. Fix the root cause (move the side effect to useEffect) rather than removing StrictMode.

Does this error affect SEO?

Potentially yes. When React detects a severe hydration mismatch it falls back to a full client-side re-render, discarding the server HTML entirely. This means Googlebot (which renders JavaScript) sees the correct content, but if it only crawls the raw HTML (it sometimes does), it sees the pre-hydration version. Fix the error to guarantee both versions are identical β€” that is the whole point of SSR.