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/418The 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>withoutdeferorasync, and inline scripts next/scriptwithstrategy="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: nonceBrowsers 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:
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