# Baselines, budgets and owners

> Adopt hydration-proof on an app with existing problems: a hydration error baseline and budget let CI fail only on new findings, with owners and trends.

Source: https://hydration.jscrate.dev/docs/baselines
Last updated: 2026-09-18

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:

```bash
npx hydration-proof baseline   # records the current findings
```

Commit the file it writes:

```sh
git add .hydration-proof/baseline.json
```

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

```bash
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:

```json title=".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:

```bash
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:

```ts title="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](https://hydration.jscrate.dev/docs/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.

```ts title="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](https://hydration.jscrate.dev/docs/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](https://hydration.jscrate.dev/docs/ci): workflows, sharding and `--changed`
- [Ignore known hydration mismatches](https://hydration.jscrate.dev/docs/ignoring) one at a time
- [Reports](https://hydration.jscrate.dev/docs/reports): the `new`, `baseline` and `owners` fields
- [The `ci` option](https://hydration.jscrate.dev/docs/configuration#ci) in the configuration reference
- [What is stable between releases](https://hydration.jscrate.dev/docs/compatibility)
