# HP4001: The page was modified before React hydrated

> HP4001 (pre-hydration-mutation) means a script, extension or third-party tag changed the server HTML before React hydrated it. Find the script and move it.

Source: https://hydration.jscrate.dev/docs/issues/hp4001
Last updated: 2026-09-18

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
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](https://hydration.jscrate.dev/docs/issues/hp4002)), and text or attribute changes on an element
marked with `suppressHydrationWarning` (reported as
[HP6001](https://hydration.jscrate.dev/docs/issues/hp6001)).

## Likely causes

- [A script changed the page before hydration](https://hydration.jscrate.dev/docs/causes/third-party-script):
  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](https://hydration.jscrate.dev/docs/causes/extension) that rewrites text, such as a
  translator.
- A theme script that sets a class on `<html>` before hydration, without
  `suppressHydrationWarning` on that element.

## How to fix it

1. **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.
2. **Run it after hydration.** Load third-party tags after the page is
   interactive, for example with `next/script` and
   `strategy="afterInteractive"` or `"lazyOnload"`.
3. **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.
4. **For theme scripts,** mark the element the script changes (usually
   `<html>`) with `suppressHydrationWarning`. The change is then listed as an
   intentional difference instead.
5. **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:

```tsx title="app/layout.tsx"
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

```text
  ✖ /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](https://hydration.jscrate.dev/docs/ignoring). `checks.externalChanges: false` turns off
every HP4xxx check.

## Related

- [HP4002: a browser extension changed the page](https://hydration.jscrate.dev/docs/issues/hp4002)
- [HP4003: HTML rewritten between the server and the browser](https://hydration.jscrate.dev/docs/issues/hp4003)
- [Third-party scripts that change the page](https://hydration.jscrate.dev/docs/causes/third-party-script)
- [How the pre-hydration DOM is captured](https://hydration.jscrate.dev/docs/how-it-works)
- [suppressHydrationWarning for theme scripts](https://hydration.jscrate.dev/docs/guides/suppresshydrationwarning)
