Hydration Proof

Search documentation

Find a page or section

Compatibility and stability

What you can build on, and what may change.

Check hydration-proof compatibility before adding it to a project. The table below lists the Node.js, React, framework, browser, and package manager versions tested. Stable APIs follow semver: breaking changes require a major release and a migration path.

The hydration-proof compatibility table

  • Node.js: 22.18 or later, on Linux, macOS and Windows. Tests cover Node.js 22.18, 24 and 26.
  • React: 18.3 and 19.x, in development and production builds. React 18.0–18.2 are not tested; findings may be less precise there.
  • Next.js: 15.5 and 16.x, with App Router or Pages Router, webpack or Turbopack. App Router uses Next.js's bundled React; your installed React version matters for Pages Router.
  • React Router: Framework mode with @react-router/dev; fixtures use version 8.4.
  • Other frameworks: Remix v2, Astro with React islands, Vite SSR and custom Node.js servers.
  • Browsers: Chromium, Firefox and WebKit through Playwright. CPU slowdown and native network throttling work only in Chromium.
  • Playwright: playwright-core 1.63 or later. An up-to-date copy from your project is reused.
  • Package managers: npm, pnpm, Yarn Classic, Yarn Berry (including Plug'n'Play) and Bun.
  • ESLint plugin: ESLint 9 and 10 with flat config.

Each package manager is tested by installing the packed tarball into a fresh app and running the CLI against it. A version leaving this table is a major release; adding one is not.

Adapters explains how each framework is built, started and read.

How the table is checked

Two workflows in the repository back the table with evidence:

  • compat.yml runs the whole fixture suite again against other Next.js versions (16.3, 16.2 and 15.5), and against React 18.3 and 19.0 to 19.3 for the Pages Router and custom SSR.
  • real-world.yml runs the packed CLI against pinned open-source Next.js apps that nobody on the project wrote: Next.js's own blog-starter, with-styled-components and app-dir-i18n-routing examples, pinned to a release tag. It runs without a config file, the way a first run on a real app does, and with --fail-on warning: the apps are expected to be clean, so any finding above info is a false positive to investigate.

A tool that only works on its own fixtures is not a tool, which is why the second workflow matters most.

What is stable

  • CLI: Commands, flags and exit codes keep their meaning. New flags may be added.
  • Config: Documented options and configVersion: 1 are stable. A removal or rename requires a major release, and hydration-proof migrate keeps working for at least one major version.
  • JSON report: report.json uses schemaVersion: 1, described by schema/report.json. Fields may be added, but not removed or repurposed.
  • Issue codes: An HPxxxx code keeps its meaning and is never reused.
  • Fingerprints: Stable within a fingerprintVersion, so baselines and ignore rules keep matching.
  • Node API: run(), defineConfig, definePlugin, defineAdapter, mergeReports, ISSUES and their types.
  • Plugin and adapter API: The fields described on the plugins and adapters pages.
  • Reporter output: JUnit, SARIF and GitLab output follow their schemas.

The issue codes and CLI pages list the stable values.

What is not stable

These are internals. They change in minor releases, and nothing outside the package should depend on them:

  • everything the public exports do not re-export, including the capture stages, the mutation log, the rewind and the diff
  • the browser runtime and its protocol, which is rebuilt and inlined on every release (both sides always ship together)
  • the HTML report's markup and its embedded data
  • the exact wording of messages, the set of evidence entries, and confidence scores
  • which cause a finding is given, and whether it has a cause at all. Diagnosis improves over releases, so treat a cause as an explanation for a human, not as something to assert on

severity is in between: a code's severity can be lowered or raised in a minor release when experience shows it was wrong. Pin the behavior you care about with ci.failOn and ci.budget rather than relying on the defaults. Baselines and budgets shows how.

Fingerprints and baselines

A fingerprint identifies a finding across runs, so a baseline recorded today still matches tomorrow. It is built from the issue code, the route pattern, the scenario and the element's position. It never uses values that change per request, or anything redaction rewrites: fingerprints are computed first.

If a fix has to change how fingerprints are computed, fingerprintVersion in the report is raised, the release notes say so, and hydration-proof baseline rewrites your baseline. Old baselines keep working until you record them again: entries with an older fingerprintVersion are matched by code, route and selector instead.

How deprecation works

When something stable has to go:

  1. It keeps working, and using it prints a note that names the replacement.
  2. hydration-proof migrate rewrites it where a rewrite is possible.
  3. It is removed in the next major release at the earliest.

Config options are the common case, and migrate covers them. Nothing is ever removed in a patch release.

Upgrading

List what the new version renamed or replaced, then apply the safe renames:

npx hydration-proof migrate
npx hydration-proof migrate --write

--write keeps the old file as <file>.backup. Changes that need a judgment call are listed for you to make by hand.

Between minor versions, expect new findings: better detection is the point. If a release reports something you have decided to live with, an ignore rule with a reason and an expiry date is the honest way to park it. ci.newIssuesOnly (--new-only) lets you adopt a release without a red pipeline on day one.