maxGrowth is
configured. A ceiling describes one build in isolation: a route may not exceed 250 kB or fall below
a 60% shell ratio.
That distinction determines which checks need configuration.
The comparison decision
crust diff and crust ci summarize the same evidence with one of four decisions:
- BLOCK - an automatic regression or configured budget was breached
- REVIEW - meaningful movement needs judgment but is not enforceable
- CLEAR - the comparison found no blocking regression
- CANNOT DECIDE - missing or incompatible evidence makes a reliable delta comparison impossible
diff reports that decision without enforcing it. ci exits non-zero only for blocking breaches.
The verdict is followed by regressions and improvements, their strongest grouped cause, attribution
coverage, and the likely next action.
Absolute ceilings describe the current build, so they still run without a comparable baseline. A
ceiling breach is therefore BLOCK even when route-to-route deltas cannot be decided.
Automatic regression checks
As soon as a comparable baseline exists,crust ci fails without a budget file when:
- a route moves down the rendering-mode scale
- a new uncached-read reason appears
- a route that previously emitted a measurable shell emits none
ROUTE_HANDLER is not on this scale because a handler has no page shell. unknown is not on the
scale because placing it anywhere would turn missing evidence into a claim.
Explicit ceilings
These rules require.perf/budgets.json because crust cannot know the right limits for another
project:
- maximum first-load bytes globally or per route
- maximum percentage growth against a baseline
- minimum static-shell ratio globally or per route
When crust does not call a regression
A newly added route
Its baseline size is mathematically zero, but calling its entire bundle “growth” would make every new page a regression. New routes are reported as additions and can still breach an absolute first-load ceiling.An unknown rendering mode
If either side isunknown, the change is shown but has no direction and does not fail the
rendering-mode check.
Incomparable builds
crust does not compute enforceable deltas when any of these changed:- bundler
- Next.js major version
- snapshot schema version
Build configuration changed
crust records configuration that materially changes emitted output.crust analyze, crust diff, and
the PR comment all report these changes separately from route deltas, whenever a compatible local
baseline exists:
- Cache Components
- bundler, Next.js version, and Node major
- browser source-map emission
partialPrefetchingand theinstantInsightsvalidation level, on Next 16.3- relevant experimental flags
- per-route
dynamic,revalidate,runtime,fetchCache,instant, andprefetch
dynamic, revalidate, runtime, or fetchCache
still fails - it is a real staticness downgrade - but the comment names the declaration instead of
sending the reviewer to look for a cause that is not in the code.
Instant Navigations
Next 16.3 adds thepartialPrefetching config flag, an instantInsights validation level, and the
instant and prefetch route segment exports. crust records all four and reports them here, as
declared intent. partialPrefetching is kept raw, so 'unstable_eager' stays distinct from
true. The segment exports are inherited from layouts like every other segment key.
They report as configuration rather than as per-route regressions because a flag flip is one
decision, not one defect per route.
crust does not claim to have measured instant navigation, and cannot. Two next@16.3.0 builds of
one app differing only in partialPrefetching emit an identical artifact tree, a byte-identical
server/prefetch-hints.json, and per-segment payloads within a few bytes of each other; instant
appears in no build manifest. Next enforces the contract by failing the build, so a route that
stopped being instant does not produce a comparable snapshot to regress against - it produces a red
build.
One transition is named weakened: dropping from an experimental-error validation level to a
warning level. At an error level, a route that breaks its instant contract fails the build. Below
it, the same route ships. That change is silent in the build output and survives a green CI run,
which is the reason this axis is worth reporting at all.
Byte noise
Chunk filenames are content-hashed, so tiny byte changes can appear even when behavior did not. crust records exact totals but does not classify a first-load delta of 512 bytes or less as a regression in diff severity or terminal grouping. This display floor is not a CI threshold; JavaScript growth is enforced only throughmaxGrowth.
Intentional downgrades
UseallowRegression when making a route less static is deliberate:
.perf/budgets.json
firstLoadBytes, maxGrowth, and shell-ratio ceilings continue to apply.
How a cause is selected
For each regressed route, crust prefers evidence in this order:- a newly postponed component with a call site
- a newly introduced uncached read
- the reason for a rendering-mode drop
- the largest changed first-party module
unknown, with the missing evidence stated