A hydration error baseline and budget let you adopt hydration-proof on an app that already has problems. The baseline records today's findings, so CI fails only on new ones. A budget allows a number of findings per severity, route or issue code. Owners say who fixes each finding, and the history shows whether the numbers go down.
Record a baseline
Run the tests once and record every finding:
npx hydration-proof baseline # records the current findingsCommit the file it writes:
git add .hydration-proof/baseline.jsonThen fail CI only on findings that are not in it:
npx hydration-proof test --new-onlyThe same setting in the config is ci.newIssuesOnly: true. Findings in the
baseline are reported as known ("in baseline") and do not fail the run; new
findings are marked "new" and do. The terminal summary counts both.
baseline takes the same options as test. The file lives at
.hydration-proof/baseline.json; change the path with ci.baseline.
How baseline entries match
Entries match by fingerprint: the issue code, the route pattern, the element and the attribute. So an entry keeps matching when the page changes elsewhere, and a new problem on the same page is still new.
Each entry records the route, the issue, the likely cause, the scenarios it was seen in, and the dates it was first and last seen:
{
"$schema": "../node_modules/hydration-proof/schema/baseline.json",
"version": 1,
"tool": "hydration-proof",
"updatedAt": "2026-09-18T09:12:44.120Z",
"fingerprintVersion": 1,
"entries": [
{
"fingerprint": "3f9c2a7d41e0b865",
"code": "HP1004",
"title": "Class name differs between server and client",
"route": "/settings",
"scenarios": ["default"],
"firstSeen": "2026-09-18",
"lastSeen": "2026-09-18",
"selector": "#theme",
"attribute": "class",
"cause": "theme",
"expires": "2026-12-31",
"reason": "The theme toggle rewrite is tracked in ACME-512."
}
]
}Add a reason and an expiry date
Add reason and expires (YYYY-MM-DD) to an entry by hand. The reason is
shown in reports. After the expiry date the finding fails the run again, with a
message such as "The baseline entry for HP1004 on /settings (#theme) expired on
2026-12-31.", so accepted problems do not stay forever.
Update the baseline
After fixing problems, record again:
npx hydration-proof baseline
# or, as part of a normal run:
npx hydration-proof test --update-baselineFixed entries are removed. The fields you wrote by hand and the firstSeen
dates are kept.
If a release has to change how fingerprints are computed, the report's
fingerprintVersion goes up. Until you record again, entries with an older
version are matched by code, route and selector instead, so the pipeline does
not turn red overnight.
Budgets
A budget allows a number of findings instead of failing on the first one:
import { defineConfig } from "hydration-proof";
export default defineConfig({
ci: {
budget: {
error: 3,
warning: 20,
routes: { "/checkout/**": { error: 0, warning: 0 } },
codes: { HP1004: 2 },
},
},
});error,warning,info: the most findings of that severity in the run. A budget for a severity replaces the "any finding fails" rule for that severity.routes: limits per severity for the routes matching each glob.codes: limits per issue code, such as{ HP1004: 3 }.
When a limit is exceeded, the run fails with a message like "4 errors exceed the budget of 3." Lower the numbers as problems are fixed, so the count can only go down.
failOn and maxWarnings
ci.failOn is the lowest severity that fails the run: 'error' (the default),
'warning', 'info' or 'never'. --fail-on sets it for one run.
ci.maxWarnings fails the run when there are more warnings than this, even
when warnings do not fail it on their own.
Pin these explicitly if the outcome matters to you: an issue code's default severity can be raised or lowered in a minor release (see compatibility).
Combine a hydration error baseline and budget
A CI policy for an app that already has findings: green from day one, a ratchet that stops the numbers from growing, and a checkout flow that must stay clean.
import { defineConfig } from "hydration-proof";
export default defineConfig({
ci: {
failOn: "error",
// Only findings that are not in the baseline fail.
newIssuesOnly: true,
baseline: ".hydration-proof/baseline.json",
// The ratchet: the numbers may only go down.
budget: {
error: 0,
warning: 12,
routes: { "/checkout/**": { error: 0, warning: 0 } },
codes: { HP1004: 3 },
},
history: true,
},
// Who gets pinged, from CODEOWNERS unless the route says otherwise.
owners: {
routes: { "/checkout/**": ["@acme/payments"] },
codeowners: true,
},
});Detect hydration errors in CI has the workflows to run it in.
Owners
Findings show who owns them. Route owners come from owners.routes; for other
findings, the CODEOWNERS entry of the finding's source file is used.
hydration-proof looks for .github/CODEOWNERS, CODEOWNERS,
docs/CODEOWNERS and .gitlab/CODEOWNERS, or the path you give in
owners.codeowners. Set it to false to turn the lookup off.
The terminal prints the owners under each finding, report.json has them in
owners, and the HTML report can be filtered by owner.
Trends
ci.history: true appends one line per run to .hydration-proof/history.ndjson
(or to the path you give). Each line holds the date, the commit and branch, the
duration, the number of pages and failed pages, and the findings by severity
and by issue code. Ignored findings are not counted.
The HTML report shows the errors and warnings of the last 30 runs. Keep the
file between CI runs, for example with actions/cache or by committing it on
the main branch; otherwise the trend starts over every run.
Options
| Option | Type | Default | Description |
|---|---|---|---|
| failOn | Severity | "never" | "error" | Lowest severity that makes the run fail. |
| maxWarnings | number | — | Fail when more warnings than this are found. |
| baseline | string | .hydration-proof/baseline.json | Baseline file of accepted issues. |
| newIssuesOnly | boolean | — | Only fail on issues that are not in the baseline. |
| budget | BudgetConfig | — | Hydration error budget: how many findings of a severity (in total, per route glob or per code) are allowed before the run fails. |
| history | boolean | string | — | Append a line per run to a history file for trends: true for .hydration-proof/history.ndjson, or a path. |
| Option | Type | Default | Description |
|---|---|---|---|
| routes | Record<string, BudgetLimits> | — | Limits for the findings of routes matching each glob. |
| codes | Record<string, number> | — | Limits per issue code, e.g. { HP1004: 3 }. |
| error | number | — | Inherited from BudgetLimits. |
| warning | number | — | Inherited from BudgetLimits. |
| info | number | — | Inherited from BudgetLimits. |
| Option | Type | Default | Description |
|---|---|---|---|
| routes | Record<string, string | string[]> | — | Route glob → owners, e.g. { '/checkout/**': ['@acme/payments'] }. |
| codeowners | boolean | string | — | Owners of source files from CODEOWNERS: true (default) looks in .github/, docs/ and the repository root; or a path. |
Related
- Detect hydration errors in CI: workflows, sharding and
--changed - Ignore known hydration mismatches one at a time
- Reports: the
new,baselineandownersfields - The
cioption in the configuration reference - What is stable between releases