Skip to main content
Place the file at the workspace root. It adds project-specific ceilings; automatic rendering-mode, cache, and shell-disappearance regressions do not require this file.
.perf/budgets.json

Fields

number
Ceiling on first-load JS, in bytes, for any route without a specific entry.
Record<string, number>
Per-route ceilings, keyed on the URL pattern as printed by crust analyze (/products/[slug], not the file path). Overrides the default.
number
Maximum tolerated growth versus the baseline, as a fraction. 0.05 is 5%. Requires a baseline; ignored when there is none.
number
Minimum share of a route that must be static, 0–1, for any route without a specific entry. Only applies to routes that emitted a shell.
Record<string, number>
Per-route shell floors. Overrides the default.
string[]
URL patterns exempt from automatic rendering-mode, cache, and shell-disappearance regressions. Use this when making a route less static is intentional. Explicit firstLoadBytes, maxGrowth, and shell-ratio limits continue to apply.

Choosing the axes

All three answer different questions:
Bytes alone is the common mistake. A PR that removes a cache directive can halve your static shell while the bundle stays byte-identical — and a bytes-only check passes it.

Blame

Each breach carries the largest contributor it can name — your module, a dependency, or the call site that postponed a component:
When no source map is available, blame falls back to the unattributed total and says so rather than naming the largest thing it happens to know about.

Behavior without a comparable baseline

firstLoadBytes and minShellRatio describe the current build and still run without a baseline. maxGrowth requires a delta and is skipped. The same rule applies when the bundler, Next.js major, or snapshot schema changed: absolute ceilings still run, while growth and automatic regression checks do not. See regressions and certainty for the full comparison model.