# Reports: terminal, HTML, JSON, JUnit, SARIF

> Each hydration error report (SARIF, JUnit, JSON, HTML, GitHub, GitLab) that hydration-proof writes: what it contains, where it goes and how CI reads it.

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

hydration-proof writes each hydration error report (SARIF, JUnit, JSON, HTML,
GitHub annotations, GitLab Code Quality) from the same findings, into
`.hydration-proof/report/`. Choose them with `--reporter` or the `reporters`
option. Every format carries the stable issue codes and fingerprints, so CI
tools recognize the same problem from one run to the next.

## Which hydration error report: SARIF, JUnit or JSON?

| Reporter | Writes                           | Use it for                                                |
| -------- | -------------------------------- | --------------------------------------------------------- |
| `list`   | The terminal                     | Reading results as the run goes                           |
| `json`   | `report.json`                    | Scripts, merging shards, the [Node API](https://hydration.jscrate.dev/docs/node-api)   |
| `html`   | `report.html` and `screenshots/` | Debugging a finding                                       |
| `junit`  | `junit.xml`                      | Test results in any CI system                             |
| `sarif`  | `report.sarif`                   | GitHub code scanning and other static-analysis dashboards |
| `github` | Nothing: workflow commands       | Annotations and the job summary on GitHub Actions         |
| `gitlab` | `gl-code-quality.json`           | The Code Quality widget in GitLab merge requests          |

```bash
npx hydration-proof test --reporter list,html,json,junit,sarif
```

When `reporters` is not set, the default is `list`, `json` and `html`. On GitHub
Actions (`GITHUB_ACTIONS=true`), `github` is added automatically unless you pass
`--reporter`. `hydration-proof init` writes `reporters: ['list', 'json']` into
the config it creates. Reports go to `.hydration-proof/report/`; change it with
`outputDir` or `--output`. [Running in CI](https://hydration.jscrate.dev/docs/ci) has the reporter set for
each CI system.

## Terminal (list)

One line per page while testing. Failing pages show their most important
findings with the element, the server and client values, the likely cause, the
source location and the first fix. A summary follows:

```text
  ✖ /dashboard 1.1s  1 error
    HP1001 Text differs between server and client  (timezone difference, 95%)
      #last-login  in LastLogin
      server: "Signed in at 5:00 AM"
      client: "Signed in at 10:00 AM"
      app/dashboard/LastLogin.tsx:14:10
      → Pass an explicit timeZone to the formatter (the same on both sides), or format the date after mount.
```

## JSON (json)

`report.json` contains everything: run information, a summary, every page and
every issue. It is what the other tools build on.

- **Schema.** The format is described by
  `node_modules/hydration-proof/schema/report.json` (its `$id` is
  `https://hydration.jscrate.dev/schema/report.json`). Within
  `schemaVersion: 1` it only changes in compatible ways: fields are added,
  never removed.
- **Fingerprints.** Every issue has a `fingerprint` that stays the same across
  runs as long as the problem is the same: the same code, route pattern,
  element and attribute. Ignore rules and baselines use it.
  `fingerprintVersion` says how the fingerprints were computed.
- **CI data.** Findings carry their owners (`owners`), whether the baseline
  knows them (`baseline`, `new`) and, in a monorepo, their `project`. The run
  records the git commit and branch. With `ci.history`, `history` holds the
  previous runs.
- **Pages.** Each page records where its route came from (`source`: `config`,
  `discovered`, `manifest`, `sitemap`, `crawl` or `not-found`) and, when the
  tool started the app, the error and warning lines the server printed while
  the page loaded (`serverLogs`).
- **Redaction.** Values removed by [redaction](https://hydration.jscrate.dev/docs/security) are counted in
  `summary.redacted`.

## HTML (html)

`report.html` is a single file you can open locally or keep as a CI artifact.
Its Content Security Policy starts with `default-src 'none'`, and the page makes
no network requests. Screenshots are image files in `screenshots/` next to it,
so keep the folder together.

It shows:

- a summary and filters: page status, severity, cause, kind, scenario, owner,
  text search and ignored findings;
- per page, the findings with server and client values side by side
  (differences highlighted), the element on each side, the component, the
  source code, evidence and fixes, and the server log lines;
- screenshots of the hydrated page and of the server HTML without scripts,
  with the affected elements outlined;
- a timeline of the page: scripts and RSC requests loading, React loading,
  hydration commits for the root and each Suspense boundary, React errors,
  scripts changing the DOM, streamed content and the navigation check;
- the other environments of a route for [matrix](https://hydration.jscrate.dev/docs/environment-matrix)
  runs, what each [probe](https://hydration.jscrate.dev/docs/probes) changed, and flaky findings for
  repeated runs;
- with `ci.history`, the errors and warnings of the last 30 runs.

`screenshots` sets which pages get screenshots: `'failures'` (the default with
the HTML reporter), `'all'` or `'off'`. Deep links such as
`report.html#page=…&issue=…` open one finding.

## JUnit XML (junit)

`junit.xml` works with any CI system that reads JUnit results, including
GitLab, CircleCI, Jenkins, Azure Pipelines and Buildkite.

- One `<testsuite>` per scenario, or per scenario and build mode with
  `--mode both`.
- One `<testcase>` per page. The `classname` is
  `hydration-proof.<scenario>[.<mode>]` and the `name` is the page path with
  its query string. A path tested twice in one suite gets ` (2)`, ` (3)` and so
  on, so every pair is unique. `time` is the page's test time in seconds.
- A failed page has a `<failure>`. Its `type` is the first issue code, its
  `message` lists the error codes, and its body lists each error with the
  element, both values, the likely cause, the source location, the first fix
  and the documentation link.
- A page that could not be tested has an `<error>` whose `type` is the outcome.
- A page with only warnings or info findings passes. Its findings go to
  `<system-out>`, with the URL, the outcome and any ignored findings. Server
  log lines go to `<system-err>`.

Test cases follow the page status and ignore `--fail-on` and `ci.maxWarnings`.
With `--fail-on warning` the job fails while pages with only warnings still
pass in JUnit. The exit code always applies the policy.

Values are quoted with escapes such as `\n` and `\u0000`, so invisible
differences stay visible.

## SARIF (sarif)

`report.sarif` is a [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/)
log for GitHub code scanning and other static-analysis dashboards.

- Every issue code is a rule. Its `id` is the code (`HP1001`) and its `name` is
  the PascalCase issue name (`TextMismatch`), with the description, the fix
  and a documentation link. The default level follows the severity: `error`,
  `warning`, or `note` for info.
- Each finding is one result, with the source file and line, the route
  pattern, scenario and build mode as a logical location, and the fingerprint
  in `partialFingerprints["hydrationProof/v1"]`, so code scanning recognizes
  an alert across runs.
- Ignored findings are included with an `external` suppression, so no alert
  opens for them.
- `invocations[0].executionSuccessful` is `false` only when the tool itself
  could not run (exit codes 2, 3, 4 and 70).

To upload it to GitHub code scanning, give the job the `security-events: write`
permission and use `github/codeql-action/upload-sarif`:

```yaml title=".github/workflows/hydration.yml"
permissions:
  contents: read
  security-events: write

steps:
  # checkout, setup-node, npm ci, install
  - run: npx hydration-proof test --reporter list,html,json,github,sarif
  - uses: github/codeql-action/upload-sarif@v3
    if: ${{ !cancelled() && hashFiles('.hydration-proof/report/report.sarif') != '' }}
    with:
      sarif_file: .hydration-proof/report/report.sarif
      category: hydration-proof
```

Code scanning is on for public repositories; private ones need GitHub Advanced
Security. Pull requests from forks get a read-only token, so the upload step
fails there.

## GitHub Actions (github)

The `github` reporter prints workflow commands and writes no file.

- **Annotations.** Each problem becomes an `::error`, `::warning` or `::notice`
  annotation, with the page, scenario, element, both values (up to 200
  characters each), the likely cause and its confidence, the first fix and the
  documentation link. When the source location is known, it is attached to
  that file and line, so it shows in the pull request's changed files. The same
  fingerprint in several scenarios, modes or URLs of one route becomes one
  annotation.
- **Limits.** GitHub shows at most 10 annotations of each level per step. The
  most important come first, and a final notice says how many were left out.
- **Job summary.** A Markdown summary with a pass or fail headline, totals, the
  pages with problems and a section per problem. GitHub rejects summaries over
  1 MiB, so long ones are shortened, with a note, to stay under that size.

## GitLab Code Quality (gitlab)

`gl-code-quality.json` is a Code Quality report that GitLab shows in merge
requests. It has one entry per finding that is not ignored:

| Finding                                | GitLab `severity` |
| -------------------------------------- | ----------------- |
| Error with a confidence of 0.9 or more | `critical`        |
| Any other error                        | `major`           |
| Warning                                | `minor`           |
| Info                                   | `info`            |

`check_name` is the issue code, and `fingerprint` is unique within the file and
stable across runs. Declare the file, and the JUnit file, as reports:

```yaml title=".gitlab-ci.yml"
artifacts:
  when: always
  paths:
    - .hydration-proof/report
  reports:
    codequality: .hydration-proof/report/gl-code-quality.json
    junit: .hydration-proof/report/junit.xml
```

## File paths in CI formats

Paths are relative to the repository root: the closest directory above the app
that contains `.git`, or `GITHUB_WORKSPACE` for GitHub annotations and
`CI_PROJECT_DIR` for GitLab. A source file in another workspace package of a
monorepo is still linked. A finding with no source location, or one outside
the repository, is attached to line 1 of the config file (or of `package.json`)
in SARIF and GitLab, and the message names the real source.

## Source locations

Findings point at the code that rendered the element when that can be proven:

- **Development builds** (`--mode development`): the exact file and line where
  the element was created.
- **Production builds with browser source maps**: the component that rendered
  the element, found in the loaded scripts and mapped back through the source
  maps. In Next.js, turn them on:

```ts title="next.config.ts"
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  productionBrowserSourceMaps: true,
};

export default nextConfig;
```

Otherwise the finding says why there is no location, in
`sourceUnavailableReason`: for example, the scripts have no source maps, or
only framework code could be mapped (elements rendered by Server Components
have no client code).

## Related

- [Detect hydration errors in CI](https://hydration.jscrate.dev/docs/ci) with these reporters
- [Baselines and budgets](https://hydration.jscrate.dev/docs/baselines): the `new` and `baseline` fields
- [Read and merge reports from Node](https://hydration.jscrate.dev/docs/node-api)
- [Security and redaction](https://hydration.jscrate.dev/docs/security)
- [All issue codes](https://hydration.jscrate.dev/docs/issues)
