# Monorepos

> Run hydration tests in a monorepo with one command: list each app under projects, keep a config per app, pick one with --project and get one combined report.

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

To run hydration tests in a monorepo, put a hydration-proof config at the
repository root that lists your apps under `projects`. Each app keeps its own
config, with its own server, routes, scenarios and CI policy.
`npx hydration-proof test` then tests every app, one after another, and writes
one combined report.

## How to run hydration tests in a monorepo

A repository with three apps looks like this:

```text
repo/
  hydration-proof.config.ts             lists the projects
  apps/web/hydration-proof.config.ts
  apps/admin/hydration-proof.config.ts
  apps/docs/config/hydration-proof.ts
```

1. Give every app a config of its own. Running `npx hydration-proof init` in
   an app's folder writes one from that app's `package.json` and route folders.
2. List the apps in a config at the repository root:

   ```ts title="hydration-proof.config.ts"
   import { defineConfig } from "hydration-proof";

   export default defineConfig({
     projects: [
       // A path is enough when the folder has a hydration-proof config.
       "apps/web",
       // A name makes --project admin and the report grouping read better.
       { path: "apps/admin", name: "admin" },
       // Or point at a config that is not in the default place.
       { path: "apps/docs", name: "docs", config: "config/hydration-proof.ts" },
     ],
   });
   ```

3. Run the tests from the root:

   ```bash
   npx hydration-proof test
   ```

Why not one config with every route? Because the apps have different servers.
hydration-proof starts an app (building it first when there is no build yet),
tests it, stops it, and only then moves on to the next one.

## Each project

Each entry in `projects` is a folder path, or an object:

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `path` (required) | `string` | — | Folder of the project (with its own hydration-proof config). |
| `name` | `string` | the folder name | Name shown in reports. |
| `config` | `string` | found automatically | Config file inside the folder. |

Project names must be unique. When two folders have the same name (say
`apps/web` and `packages/web`), set `name` on one of them.

## Configure each app on its own

A project config is an ordinary config. Everything about how an app is tested
comes from it: the server, routes, scenarios, ignore rules, the baseline and
the CI policy.

```ts title="apps/admin/hydration-proof.config.ts"
import { defineConfig } from "hydration-proof";

export default defineConfig({
  server: {
    command: "pnpm start --port {port}",
    build: "pnpm build",
    port: 3002,
  },
  routes: { discover: true, dynamic: { "/users/[id]": ["1"] } },
  scenarios: [
    {
      name: "admin",
      cookies: [{ name: "session", value: process.env.ADMIN_SESSION ?? "" }],
    },
  ],
  // The legacy app may keep a shrinking number of warnings.
  ci: { budget: { error: 0, warning: 8 } },
});
```

A per-project `ci.budget` lets the app that is already clean stay at zero while
a legacy app works its numbers down. See
[baselines and budgets](https://hydration.jscrate.dev/docs/baselines) for the other ways to adopt the tool
on an app that already has findings.

The root config decides where the combined report goes and in which formats
(`outputDir`, `reporters`). Command-line flags such as `--mode both` or
`--fail-on warning` apply to every project.

### Does each app need its own port?

Only if you set one. Without `server.port`, every app gets a free port and
there is nothing to do.

If a config sets `server.port`, give every app a different one. Outside CI,
hydration-proof reuses an app that already answers on a configured port
(`server.reuseExisting`), so two apps on the same port can end up testing the
same server.

## Test one app with --project

```bash
npx hydration-proof test --project admin
npx hydration-proof test --project admin --project docs
```

`--project` takes a project's name or its path, and can be repeated. An unknown
name stops the run with exit code `2` and lists the projects that exist.

## Read the combined report

The root writes one report for all apps, in the formats its `reporters` list.
Findings are grouped by project, so a finding is never ambiguous about which
app it came from. Screenshots are copied next to the combined report, and each
app also keeps its own report in its own output folder.

The run fails when any project fails. If an app cannot be built or started, the
other apps are still tested, and the run exits with that app's code (`3`), which
takes precedence over findings (`1`). [Exit codes](https://hydration.jscrate.dev/docs/cli) lists them all.

To combine reports yourself, for example from one CI job per app, use
`hydration-proof merge-reports` or `mergeReports()` from the
[Node API](https://hydration.jscrate.dev/docs/node-api).

## Owners from CODEOWNERS

Findings show who owns them. hydration-proof looks for `CODEOWNERS` at the
repository root (the nearest folder with `.git`): `.github/CODEOWNERS`,
`CODEOWNERS`, `docs/CODEOWNERS` or `.gitlab/CODEOWNERS`. Every app finds the
same file, so findings are attributed to the right team without any
configuration. Route owners (`owners.routes`) go in each app's config.

## Test only what a pull request changed

```bash
npx hydration-proof test --changed
```

`--changed` follows the import graph from the files a pull request touches to
the routes that render them, across package boundaries. In a monorepo that is
the difference between testing three apps and testing four pages. By default it
compares with the pull request's base branch, or `main`.

A shared package that every app imports still selects every route that uses
it. That is correct: a change there can break all of them.

## Related

- [Detect hydration errors in CI](https://hydration.jscrate.dev/docs/ci): workflows, sharding and annotations
- [Baselines and budgets](https://hydration.jscrate.dev/docs/baselines) for apps with existing findings
- [Configuration reference](https://hydration.jscrate.dev/docs/configuration)
- [CLI commands and flags](https://hydration.jscrate.dev/docs/cli)
- [Adapters](https://hydration.jscrate.dev/docs/adapters): how each app is built and started
