Skip to main content

Requirements

  • Next.js 15 or 16, App Router
  • Node 20+
  • A production build. crust refuses to measure next dev - dev output is unminified, unbundled and HMR-laden, so any number taken from it is fiction.
Install @moumensoliman/crust as a development dependency, or use npx directly. The package name differs from the executable; the product and executable are both named crust.
Do not install the unscoped crust package—it is unrelated. The package is @moumensoliman/crust; the installed executable is crust.

1. Set it up in one command

init finds the app, records the first snapshot, writes starter budgets you can argue with, and generates a CI workflow pinned to this crust version. It reports what it decided at every step, and replaces nothing without --force - --dry-run shows the plan first. See the CLI reference for the seven steps and the generated files. In a monorepo, point it at the app: npx @moumensoliman/crust init --cwd apps/web. With several apps and no pointer, it lists them and stops instead of measuring the wrong one. The rest of this page is what init does one command at a time, and what the output means.

2. Build, then analyze

The default view answers what changed, why, how strong the evidence is, and whether CI should fail. Add --routes for the full route inventory, --verbose for coverage counts and analysis gaps, or --report for the HTML report. The snapshot is written to .perf/ using one file per build. Two branches can record builds without editing a shared history file. See snapshot files for what to commit and what remains generated.

Reading the columns

  • First load is the JavaScript downloaded on the first visit, including shared root chunks.
  • Shell is the share of visible rendered output outside pending Suspense boundaries.
  • Mode is static, partial, isr, dynamic, or handler.
  • Indented ↳ lines name the reason for a mode or hole.
  • ✂ names a component that was postponed out of the shell.

3. See it visually

A single self-contained HTML file with no server or external requests. It opens with Fix first — the same ranked findings the terminal printed, with the rest of the list the terminal deferred here — then shared-cause blast radius and the route table, with route, component and source search, filters and grouping, and complete cause chains.

4. Compare production builds

First record the base branch, then rebuild and diff after your change:
crust diff accepts a build id, a git SHA, a tag, a branch name, or HEAD~3. History is walked topologically, so a snapshot recorded later on another branch is never mistaken for an ancestor. For a branch name such as main, crust resolves the merge-base commit rather than comparing with the latest build at the branch tip.Name two refs - crust diff v1.2.0 release/next - and both sides come from the store: nothing is rebuilt, and neither ref has to be checked out. In CI, the GitHub Action fetches shared baselines from perf-history.

Compare two branches in one command

The pair above needs a snapshot on each side, which the first comparison of two branches never has. --build records them for you:
Each tip is built in its own detached git worktree, both snapshots land in the same .perf/, and the output is the diff above. Your checkout does not move - run it from a dirty working tree on a third branch. See the CLI reference for --parallel, monorepo --cwd, and how a differing lockfile is handled. The two builds must use the same bundler, Next.js major, and snapshot schema. If they do not, crust names the incompatibility and refuses to assign a cause. The decision is shared with crust ci: BLOCK, REVIEW, CLEAR, or CANNOT DECIDE. Improvements appear alongside regressions, and attribution beside the verdict tells you how much of the cause explanation the build supports.

Unlock per-file attribution

Route sizes, rendering modes, dynamic-route blame and shell composition all work out of the box. Tracing bytes to a specific file needs source maps in the production build:
next.config.ts
Most projects do not have this on, so this is usually the difference between “this route is 2.8 MB” and “packages/features/src/course/… is 340 kB of it”.
Without maps crust still reports every route total and the full shell analysis - it says the attribution is unavailable rather than guessing. Turn maps on in a dedicated analyze build if you would rather not ship them.

Next steps

Understand regressions

Automatic checks, explicit ceilings, comparability, and intentional exceptions.

Add the CI check

Regression enforcement, budgets, a PR comment, and history on an orphan branch.

Ask an agent

Point your coding agent at the same evidence over MCP, or run one tool with crust ask.