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 |
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 |
npx hydration-proof test --reporter list,html,json,junit,sarifWhen 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 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:
✖ /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$idishttps://hydration.jscrate.dev/schema/report.json). WithinschemaVersion: 1it only changes in compatible ways: fields are added, never removed. - Fingerprints. Every issue has a
fingerprintthat 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.fingerprintVersionsays how the fingerprints were computed. - CI data. Findings carry their owners (
owners), whether the baseline knows them (baseline,new) and, in a monorepo, theirproject. The run records the git commit and branch. Withci.history,historyholds the previous runs. - Pages. Each page records where its route came from (
source:config,discovered,manifest,sitemap,crawlornot-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 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 runs, what each probe 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. Theclassnameishydration-proof.<scenario>[.<mode>]and thenameis 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.timeis the page's test time in seconds. - A failed page has a
<failure>. Itstypeis the first issue code, itsmessagelists 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>whosetypeis 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
log for GitHub code scanning and other static-analysis dashboards.
- Every issue code is a rule. Its
idis the code (HP1001) and itsnameis the PascalCase issue name (TextMismatch), with the description, the fix and a documentation link. The default level follows the severity:error,warning, ornotefor 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
externalsuppression, so no alert opens for them. invocations[0].executionSuccessfulisfalseonly 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:
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-proofCode 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,::warningor::noticeannotation, 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:
artifacts:
when: always
paths:
- .hydration-proof/report
reports:
codequality: .hydration-proof/report/gl-code-quality.json
junit: .hydration-proof/report/junit.xmlFile 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:
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 with these reporters
- Baselines and budgets: the
newandbaselinefields - Read and merge reports from Node
- Security and redaction
- All issue codes