HP4001 (pre-hydration-mutation) means something other than React changed the
server-rendered DOM after the browser parsed it and before React hydrated it: a
script, a browser extension or a third-party tag. React then hydrates markup it
did not render. Find the script and run it after hydration, or keep it out of
React's tree.
| Code | HP4001 |
|---|---|
| Name | pre-hydration-mutation |
| Default severity | Error |
| Group | Changes made outside React |
| What it means | A script, browser extension or third-party tag changed the server-rendered DOM before hydration. |
What the HP4001 pre-hydration mutation finding means
hydration-proof compares the tree the browser parsed from the server HTML with the DOM right before React's first hydration commit. React's own streaming moves (Suspense content revealed from the stream) are undone first, so what is left was done by other code. The finding shows the value on each side:
Text changed before hydration: "Pro" became "Pro (recommended)".The severity depends on where the change happened:
| Where | Severity |
|---|---|
| On an element React renders, inside a React root | error |
On <html>, <body> or <head> | warning |
| Outside every React root | info (it cannot break hydration) |
Some changes are not reported: <link>, <script>, <style>, <meta> and
similar tags added to or removed from <head> (loaders, preloads and style
injection), changes that look like a browser extension (reported as
HP4002), and text or attribute changes on an element
marked with suppressHydrationWarning (reported as
HP6001).
Likely causes
- A script changed the page before hydration: A/B testing snippets, personalization or consent tools, translation widgets or an inline script of your own that sets text or classes.
- A browser extension that rewrites text, such as a translator.
- A theme script that sets a class on
<html>before hydration, withoutsuppressHydrationWarningon that element.
How to fix it
- Find the script. The finding names the element and both values, and the HTML report's timeline shows which scripts loaded before hydration. Loading the page with the script blocked in your browser's developer tools confirms it.
- Run it after hydration. Load third-party tags after the page is
interactive, for example with
next/scriptandstrategy="afterInteractive"or"lazyOnload". - Or keep it out of React's tree. A widget that renders into its own
container appended to
<body>does not touch elements React hydrates. - For theme scripts, mark the element the script changes (usually
<html>) withsuppressHydrationWarning. The change is then listed as an intentional difference instead. - If a browser extension causes it, it cannot be fixed in code; add an ignore rule for the extension's attributes.
Loading a tag after hydration in Next.js:
import Script from "next/script";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
<Script src="https://cdn.example.com/widget.js" strategy="lazyOnload" />
</body>
</html>
);
}Example
✖ /pricing 1.3s 1 error
HP4001 The page was modified before React hydrated (a script changed the page before hydration, 80%)
#plan-name
server: "Pro"
client: "Pro (recommended)"
→ Load the script after hydration (for example next/script with strategy="afterInteractive" or "lazyOnload"), or make it change only elements outside React's tree.When the change is expected
If a script you cannot move changes an element on purpose, ignore the element
with ignore.selectors, or the attribute with ignore.attributes, or add an
ignore.issues rule with code: "HP4001" and a reason. See
ignoring findings. checks.externalChanges: false turns off
every HP4xxx check.