Hydration Proof

Search documentation

Find a page or section

Baselines, budgets and owners

Start blocking new hydration problems today, and pay down the old ones over time.

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 findings

Commit the file it writes:

git add .hydration-proof/baseline.json

Then fail CI only on findings that are not in it:

npx hydration-proof test --new-only

The 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:

.hydration-proof/baseline.json
{
  "$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-baseline

Fixed 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:

hydration-proof.config.ts
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.

hydration-proof.config.ts
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.

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

OptionTypeDefaultDescription
failOnSeverity | "never""error"Lowest severity that makes the run fail.
maxWarningsnumberFail when more warnings than this are found.
baselinestring.hydration-proof/baseline.jsonBaseline file of accepted issues.
newIssuesOnlybooleanOnly fail on issues that are not in the baseline.
budgetBudgetConfigHydration error budget: how many findings of a severity (in total, per route glob or per code) are allowed before the run fails.
historyboolean | stringAppend a line per run to a history file for trends: true for .hydration-proof/history.ndjson, or a path.
OptionTypeDefaultDescription
routesRecord<string, BudgetLimits>Limits for the findings of routes matching each glob.
codesRecord<string, number>Limits per issue code, e.g. { HP1004: 3 }.
errornumberInherited from BudgetLimits.
warningnumberInherited from BudgetLimits.
infonumberInherited from BudgetLimits.
OptionTypeDefaultDescription
routesRecord<string, string | string[]>Route glob → owners, e.g. { '/checkout/**': ['@acme/payments'] }.
codeownersboolean | stringOwners of source files from CODEOWNERS: true (default) looks in .github/, docs/ and the repository root; or a path.