hydration-proof troubleshooting starts with npx hydration-proof doctor, which
checks Node.js, Playwright, the browsers, the config file and framework
detection in one go. The answers below cover the problems people hit most,
grouped by where they show up: installing and starting, signing in, routes,
findings, source locations and speed.
Start hydration-proof troubleshooting with doctor
npx hydration-proof doctorIt prints one line per check, marked as passed, a warning or a failure:
| Check | What it tells you |
|---|---|
| Node.js | The running version; 22.18 or newer is required |
| Package manager, CI | What was detected; CI changes defaults such as retries |
| Playwright | The playwright-core version in use and where it came from |
| chromium, firefox, webkit | Whether each browser is installed, with the command that installs it |
| Config | The config file it found, or the command that creates one |
| Framework | The adapter that was detected, or "not detected (set server.command)" |
| Routes | How many static and dynamic routes the adapter discovered |
doctor exits with 0 when nothing failed and 2 when something did. A
missing Firefox or WebKit is only a warning; a missing Chromium is a failure.
Installing and starting the app
Why does it say "chromium is not installed"?
The browser for the Playwright version hydration-proof uses has not been
downloaded yet, and the run stops with exit code 4. Install it:
npx hydration-proof install
npx hydration-proof install --with-deps # on Linux CI, with system libraries
npx hydration-proof install firefox webkitWhy does it say "Nothing answers at …"?
The app could not be built, started or reached, and the run exits with code
3. Check these in order:
- Run
server.commandon its own.{port}in the command is replaced with the port hydration-proof chose, and the same port is in thePORTenvironment variable. A server that ignores both listens somewhere else. - Give slow builds more time.
server.timeoutis how long to wait for the app to answer, 120000 ms by default. - With
--url, start the app first. Nothing is built or started when you pass a URL.
When a build or start command fails, the run prints the command and the end of its output.
What if hydration-proof does not know how to start the app?
No adapter matched, so there is no start command. Set server.command (and
server.build) in hydration-proof.config.ts, or pass --url to test an app
you started yourself. Adapters shows a
config for a custom server.
Why does Next.js development mode not load?
hydration-proof opens the development server on localhost, because Next.js
blocks development resources for other hosts. If you test a development server
through another host name, add that host to allowedDevOrigins in
next.config.
Why does my TypeScript config fail to load?
Node.js loads hydration-proof.config.ts directly, without a compiler. Use
plain types (no enum or namespace), include the file extension in relative
imports (./routes.ts), or rename the file to .mjs.
Signing in
Why do signed-in pages end on the login page (HP9010)?
The scenario's login did not leave a working session, so the page redirected
and got an HP9010 warning.
- Run with
--headed --workers 1to watch the login. - Check that
loginwaits for the page after signing in, for example withpage.waitForURL. - Check that the session cookie is not limited to another domain.
If the route is supposed to redirect, set expectRedirect on it. See
scenarios and sign-in.
What does "The login of scenario … failed" or "The setup hook failed" mean?
The scenario's login function, or the hooks.setup function, threw an
error; its message follows. Both run after the app is up, with the same
baseUrl the pages use. Either failure stops the run with exit code 2.
Routes
Why are some dynamic routes skipped?
A dynamic route is only tested with example values: from routes.dynamic, or
from pages the build pre-rendered. A route without values is skipped, with a
note that lists it. Add values, or list the URLs in a sitemap and turn on
routes.sitemap:
import { defineConfig } from "hydration-proof";
export default defineConfig({
routes: { dynamic: { "/products/[id]": ["1", "42"] } },
});Why is a new page missing from the run?
Discovered routes are cached until the build or a route folder changes. Run
with --no-cache if a change was missed:
npx hydration-proof test --no-cacheFindings
How do I keep a difference that is intentional?
Mark the element with data-hydration-proof-ignore, add a selector to
ignore.selectors, or add an ignore.issues rule with a reason. For values
that differ on purpose, such as a live clock, render them after mount or put
suppressHydrationWarning on that element; hydration-proof then lists them as
info. Ignoring findings covers each option.
What if a page keeps changing and never settles (HP9009)?
Pages with animations, polling or live data may never be quiet for the 400 ms hydration-proof waits by default, and get an HP9009 warning. Tell it what "ready" means for the page instead:
import { defineConfig } from "hydration-proof";
export default defineConfig({
ready: {
selector: "#app-ready", // an element that exists once the page is ready
quietMs: 200, // or wait less time without changes
},
});ready.function takes a page function (as a string) that must return a truthy
value. A route object can have its own ready options.
Why is a cause "not proven"?
Probes prove a cause when a finding changes with exactly one factor. When the page renders differently on identical reloads, the value comes from the server or an API, for example a counter or the current time on the server, which the browser cannot control. Send the data the server rendered with to the client.
Why do navigation checks report differences?
Client navigation keeps state that a direct load does not have: module
variables, context in layouts, parallel route slots. If the difference is
intended (a modal from an intercepting route, a slot that keeps its page), add
an ignore rule for the route with code: "HP5004". To start from another page,
set checks.navigation.from, or navigateFrom on the route.
What is a lost click?
A server-rendered button is visible before React can handle its clicks, and a click in that window does nothing (HP5001). Show the control as disabled or loading until the page is interactive, or make it work without JavaScript. If that is acceptable for your app, ignore HP5001.
Source locations
Why is there no source location in production?
The issue's sourceUnavailableReason says why. Enable browser source maps
(productionBrowserSourceMaps: true in Next.js) to get the component, or run
with --mode development for the exact line. Elements rendered by Server
Components have no client code to point at.
Why are source maps from my CDN not used?
Scripts and source maps are only fetched from the app's own origin, and the run
ends with a note that names the origin it refused. Add the CDN to
sourceOrigins. Security explains why the restriction
exists.
Why are component names single letters?
Production builds minify component names. Run with --mode development to see
the real names:
npx hydration-proof test --mode developmentSpeed
How do I make the matrix faster?
Every environment loads every route. Keep the default strategy: "pairwise",
lower matrix.max, limit the matrix to some scenarios with
matrix.scenarios, or run quick local checks with --no-matrix. In CI, split
the pages across jobs with --shard. See the
environment matrix.
Why is network throttling different in Firefox and WebKit?
Only Chromium can throttle the network. In Firefox and WebKit, every request except the page itself is delayed by the latency instead, which turns the HTTP cache off. CPU slowdown is Chromium only.
Related
- CLI commands, flags and exit codes
- Configuration reference
- Compatibility: supported Node.js, React and framework versions
- Scenarios and sign-in
- Ignore known hydration mismatches