Hydration Proof

Search documentation

Find a page or section

Fix the next/script hydration error

Anything that edits the page before React hydrates edits React's page.

A next/script hydration error happens when a script changes the server-rendered DOM before React hydrates. A tag manager, an A/B testing snippet, a consent banner or a beforeInteractive script adds attributes, rewrites text or inserts elements, and React finds HTML it did not render. Load the script after hydration, or let it change only elements outside React's tree.

Symptoms

Hydration failed because the server rendered text didn't match the client.
Warning: Text content did not match. Server: "Hello world" Client: "Hola mundo"
Warning: Extra attributes from the server: data-experiment
Minified React error #418; visit https://react.dev/errors/418

The error appears only when the script wins the race against hydration, so it often comes and goes with network speed and cache state.

hydration-proof reports it as HP4001, "The page was modified before React hydrated", with the cause A script changed the page before hydration and the exact change:

HP4001 The page was modified before React hydrated  (a script changed the page before hydration, 80%)
  #greeting
  server: "Hello world"
  client: "Hola mundo"
  → Load the script after hydration (for example next/script with strategy="afterInteractive" or "lazyOnload"), or make it change only elements outside React's tree.

Why the next/script hydration error happens

React hydrates the DOM as it finds it, not the HTML the server sent. Any script that runs between the browser parsing the page and React hydrating it can change what React sees:

  • scripts in <head> without defer or async, and inline scripts
  • next/script with strategy="beforeInteractive", which Next.js injects into the <head> of the server HTML and fetches before any Next.js code
  • tag managers and experimentation tools that swap text or classes as early as they can, to avoid a flash
  • personalization, translation and consent tools that edit the page on load

next/script beforeInteractive hydration error

The Next.js docs say beforeInteractive scripts are "downloaded before any Next.js module", and that their execution "does not block page hydration from occurring". A script that touches the DOM can therefore run before hydration or during it. Scripts with this strategy in the root layout causing hydration errors were reported in vercel/next.js#51242, now closed. Keep beforeInteractive for scripts that must run first and leave the DOM alone, such as bot detectors and consent managers.

next/script nonce CSP hydration warning

With a Content Security Policy that uses a nonce, you may see:

Warning: Extra attributes from the server: nonce

Browsers hide the nonce attribute from the DOM after parsing, for security, so the element React hydrates no longer shows the value the server rendered (react/react#26028). The script still runs with its nonce. Do not work around the warning by exposing the nonce to client code, which weakens the policy. Let Next.js apply the nonce on the server as its CSP guide describes, and keep Next.js up to date: the report in vercel/next.js#77952 was closed as a duplicate of #63749.

Vercel Speed Insights hydration error

A Vercel Speed Insights hydration error with minified errors 418 and 423 was reported in vercel/next.js#59379 against a Next.js 14 canary, and is closed. If you still see it, upgrade @vercel/speed-insights and Next.js, then run hydration-proof: it shows whether the page was changed before hydration (HP4001) or has an ordinary mismatch with another cause.

How to fix it

Load the script after hydration

afterInteractive (the default) injects the script on the client after some hydration has happened. lazyOnload waits for browser idle time:

app/layout.tsx
import Script from "next/script";
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
        {/* Before: strategy="beforeInteractive" */}
        <Script
          src="https://example.com/experiments.js"
          strategy="afterInteractive"
        />
        <Script
          src="https://example.com/chat-widget.js"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}

Tag managers and analytics fit afterInteractive; chat widgets and social embeds fit lazyOnload.

Give the script its own element

If a script must run early, let it change only elements React does not render: a container it creates itself and appends to <body>, or one your layout renders empty and never updates. Changes outside React's roots cannot break hydration, and hydration-proof reports them as info at most.

Move experiments to the server

A/B tests that swap copy in the browser before hydration change React's DOM by design. Decide the variant on the server (in middleware or the page) and render it directly, so both sides agree. See server and client data.

Mark inline theme scripts

An inline script that sets a class on <html> before paint (a theme script) is fine if it changes only <html> and that element has suppressHydrationWarning. The theme guide shows the pattern.

Catch it with ESLint

No lint rule can see what an external script does at runtime, so there is no rule for this cause. hydration-proof test catches it in the running page.

Catch it in CI

hydration-proof compares the server HTML as the browser parsed it with the DOM right before React hydrated, after undoing React's own streaming moves. Whatever remains was changed by someone else: HP4001 for your scripts and third-party tags, HP4002 for changes that look like a browser extension. To test a tag before you ship it, add it to a scenario's initScripts, which run before the page's own scripts. See how it works.

npx hydration-proof test