How crust builds the comparison
crust does not crawl a deployed URL, run Lighthouse, or estimate fromnext dev. It joins each
completed .next build with the application’s source graph and stores a compatible snapshot.
crust diff [base] [head] then derives one decision from rendering, caching, shell, and client-cost
movement.
Two named refs are compared from stored snapshots, so nothing is rebuilt and neither side has to be
checked out. When a ref has not been measured yet, crust diff <base> <head> --build records it -
each tip built in its own detached git worktree, both snapshots written to the project’s .perf/,
your checkout untouched.
It reads:
- route, prerender, client-reference, build, and resolved-config manifests
- emitted HTML from
server/app/**/*.html - production JavaScript from
static/**/*.jsand its source maps - pages, layouts, components, and first-party imports under
app/orsrc/app/
Function-level analysis
crust parses source without executing it. It looks for:cookies,headers,draftMode,connection, and pagesearchParamsfetch()caching,no-store, default uncached reads under Cache Components, and'use cache'- route exports such as
dynamic,revalidate,runtime, andfetchCache, plusinstantandprefetchon Next 16.3 - Suspense and client boundaries, rendered components, imports, exports, aliases, and barrel re-exports
export *, unresolved aliases, and components passed through props fall back to
conservative module-level evidence or remain unknown.
Every cause is labeled verified, inferred, or unknown. File-level byte attribution needs
productionBrowserSourceMaps: true; route totals and shell analysis do not.Compared with Next.js Bundle Analyzer
The experimentalnext experimental-analyze
is an interactive Turbopack module explorer. Use it to inspect the client and server module graph of
one build, find large dependencies, and follow their import chains. Next.js also provides
@next/bundle-analyzer
for webpack bundle inspection.
crust complements it by reading completed webpack or Turbopack builds, recording compatible
snapshots, and comparing them automatically. It analyzes rendering modes, cache decisions,
static-shell changes, shared causes, and client-JavaScript growth, then turns regressions into CI
findings and merge verdicts.
The Next.js analyzer answers “what is in this bundle?” crust focuses on “what changed since the
baseline, why did it change, and should this PR merge?”
What crust answers
Across two builds:- Should this change be blocked, reviewed, cleared, or left undecidable?
- Did a route become less static, lose caching, or shrink its shell?
- Did first-load JavaScript grow, and was it a package, client boundary, barrel, or file?
- Which routes share the same cause and source location?
- What improved?
- How much of the explanation is backed by attributed evidence?
- What is the likely next action?
- Is this route static, partially static, ISR, dynamic, or a route handler?
- Which dynamic API, uncached read, or route configuration produced that mode?
- How much of the rendered route is in the static shell?
- Which first-party modules and packages make up its first-load JavaScript?
- Which client boundary or barrel import is responsible for that cost?
- Which shared layout, provider, package, or call site affects the most routes?
- How much of the build did crust classify, measure, and attribute?
- Which three findings are most worth investigating first?
The failure it exists to catch
Under Cache Components, removing a cache directive deep in a utility silently shrinks the static shell. No build error and no bundle growth:Under the older
experimental_ppr model a dynamic API outside a Suspense boundary threw a build
error, so you found out immediately. Cache Components inverts the default: absence of caching is
what postpones, and it fails quietly.What it does
Compare two builds
Branches, tags, commits, and build ids with one decision, grouped causes, and improvements -
building both refs on demand when neither has been measured.
Analyze a build
Prioritized findings, route modes, first-load JS, shell composition, and source-level reasons.
Understand regressions
What fails automatically, what needs a budget, and when crust refuses to compare.
Shell engine
Predicted versus actual static shell, and the call site that broke it.
Trace client JavaScript
Source-map attribution, client-boundary cost, barrel drag, and import chains.
Add CI
Zero-config regressions, optional ceilings, measured finding trust, and an updating PR comment.
Ask an agent
Serve stored evidence to the coding agent you already run - no model, embeddings, or API key.
What is enforced
With a comparable baseline, crust fails CI without a budget file when:- a rendering mode moves down the staticness scale
- an uncached read appears where the baseline had none
- a route that emitted a static shell stops emitting one
.perf/budgets.json.
An added route is not a regression,
unknown is never assigned a direction, and builds made with
different bundlers, Next majors, or snapshot schemas are not compared. Absolute ceilings can still
apply because they describe the current build rather than a delta.What crust delivers
- Production-build explanations connected to components, imports, and call sites
- Cause-chain evidence, analysis coverage, shared-cause grouping, and actionable client cost
- Configuration changes kept separate from application regressions
- Compatible local history with route-level regression blame
- Static-shell composition verified against emitted HTML
- First-party client-JavaScript attribution
- Evidence-backed merge decisions and explicit project budgets