# HP9009: The page never became quiet

> HP9009 (ready-timeout) means the page kept changing, or your ready selector or function never passed, before the timeout. How to set the ready options.

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

HP9009 (`ready-timeout`) means the page hydrated, but its ready conditions were
not met before the timeout: the DOM kept changing, or your ready selector or
function never passed. The final snapshot was taken anyway and may be early.
Set `ready` options that fit the page: a longer timeout, a shorter quiet
period or a selector.

| | |
| --- | --- |
| Code | `HP9009` |
| Name | `ready-timeout` |
| Default severity | Warning |
| Group | Test run problems |
| What it means | The ready conditions (quiet DOM, selector, function) were not met before the timeout. |

## What the HP9009 ready timeout finding means

After hydration, hydration-proof waits for the page to settle before it takes
the stable snapshot. hydration-proof never waits for "network idle". A page
counts as settled when:

1. the element in `ready.selector` exists, if you set one;
2. the page function in `ready.function` returns a truthy value, if you set
   one;
3. no DOM change or React commit happened for `ready.quietMs` (400 ms by
   default).

All three must happen within `ready.timeout` (30 seconds by default). If they
do not, the snapshot is taken at the timeout and the page gets this warning:

```text
The page kept changing until the timeout; the stable snapshot may be early.
```

## Likely causes

- Animations driven by JavaScript, carousels, tickers or a clock that updates
  the DOM more often than `quietMs`.
- Polling or live data that re-renders the page every few hundred
  milliseconds.
- A `ready.selector` that never appears, or a `ready.function` that never
  returns true, for example after a change to the page.
- A slow page that settles, but after the timeout.

## How to fix it

The issue code's advice is to increase the timeout or provide `ready.selector`
for pages with continuous updates. In practice, pick by situation:

| Situation                               | Fix                                                                     |
| --------------------------------------- | ----------------------------------------------------------------------- |
| The page settles, but slowly            | Raise `ready.timeout`, or pass `--timeout`                              |
| The page updates all the time           | Lower `ready.quietMs` for that route, below the interval of the updates |
| The content you care about arrives late | Set `ready.selector` or `ready.function`, so the snapshot waits for it  |
| The selector or function never passes   | Fix it, or remove it                                                    |

A route object can set its own `ready` options, so the rest of the app keeps
the defaults:

```ts title="hydration-proof.config.ts"
import { defineConfig } from "hydration-proof";

export default defineConfig({
  routes: {
    paths: [
      "/",
      {
        path: "/live-scores",
        ready: { quietMs: 150, selector: "#scoreboard" },
      },
    ],
  },
  ready: { timeout: 45_000 },
});
```

When the updates can be turned off for tests, a scenario can do it before any
page script runs, for example with `localStorage` or `initScripts` (see
[scenarios](https://hydration.jscrate.dev/docs/scenarios)). If none of this fits, ignore
`code: "HP9009"` for the route with a `reason`
([ignoring findings](https://hydration.jscrate.dev/docs/ignoring)).

## Related

- [HP9001: hydration did not finish in time](https://hydration.jscrate.dev/docs/issues/hp9001)
- [Troubleshooting: a page that never settles](https://hydration.jscrate.dev/docs/troubleshooting)
- [The ready options in the configuration](https://hydration.jscrate.dev/docs/configuration)
- [Route options](https://hydration.jscrate.dev/docs/routes)
