Hydration Proof

Search documentation

Find a page or section

Compatibility and stability

What you can build on, and what may change.

hydration-proof compatibility covers two things: the versions of Node.js, React, frameworks, browsers and package managers it is tested with, and the parts of the tool you can build on. Everything listed as stable follows semver: it changes only in a major release, with a migration path.

The hydration-proof compatibility table

Supported
Node.js22.18 and later, the first version that imports .ts directly. Tested on 22.18, 24 and 26, on Linux, macOS and Windows
React18.3 and 19.x, in development and production builds. React 18.0–18.2 are not tested; they hydrate differently enough that findings may be less precise
Next.js15.5 and 16.x, App Router and Pages Router, webpack and Turbopack. The App Router always uses Next.js's own bundled React, so the installed React version only matters for the Pages Router
React RouterFramework mode (@react-router/dev); the fixtures run version 8.4
Other frameworksRemix v2, Astro with React islands, Vite SSR, and custom Node.js servers
BrowsersChromium, Firefox and WebKit through Playwright. CPU slowdown and native network throttling are Chromium only
Playwrightplaywright-core 1.63 and later. Your own copy is used when it is new enough, so browsers are never downloaded twice
Package managersnpm, pnpm, Yarn Classic, Yarn Berry (including Plug'n'Play) and Bun
ESLint pluginESLint 9 and 10, flat config only

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

SurfaceStable part
CLIThe commands, their flags and the exit codes. New flags are added; existing ones keep their meaning
ConfigEvery documented option and configVersion: 1. Removing or renaming one needs a major release, and hydration-proof migrate keeps working for at least one major version
Reportreport.json with schemaVersion: 1, described by schema/report.json. Fields are added, never removed or repurposed
Issue codesAn HPxxxx code keeps its meaning forever and is never reused for something else
FingerprintsStable for a given fingerprintVersion, so baselines and ignore rules keep matching
Node APIrun(), defineConfig, definePlugin, defineAdapter, mergeReports, ISSUES and the types they use
Plugin and adapter APIThe fields documented for plugins and adapters
Reporter outputThe JUnit, SARIF and GitLab shapes, as their own schemas define them

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.