Next 15 & 16webpackTurbopack

The production-build diff that helps you decide what can ship

Compare two Next.js App Router production builds. crust leads with the decision, groups every affected route by cause, and traces regressions to the component, import and source line that introduced them.

Catch the regression a size budget cannot see

No build error. No byte growth. A route still fell from static to partial.

production build
RouteFirst loadShellMode/143.2 kBstatic/products/[slug]168.4 kBpartial/dashboard152.7 kBpartial
Pull request

crust: BLOCK · /products/[slug] is no longer static

attribution 94%
rendering: static → partial
static shell: 100% → 45%
Do this: cache that read so the route can prerender again
Cause: uncached fetch at lib/http.ts:3
Introduced by: <ProductGallery>
verified evidence · 1 failing check · exit 1

A use cache directive was removed three call frames below the page. Conventional bundle checks passed because the JavaScript was identical. crust compared the emitted shells, followed the source chain, and named the edit that changed the merge decision.

A cause chain from route to the source line that introduced the regressionROUTE/products/[slug]COMPONENT<ProductGallery>BINDINGgetProduct()IMPORTlib/http.tsCALL SITElib/http.ts:3uncached fetchCache that read and the route can prerender again — verified evidence
Each hop is derived from the one before it. When a hop cannot be established, crust reports it as unknown rather than naming a plausible file — a wrong blame costs more than no blame.

How it works

Production artifacts prove what changed. Source explains why. History makes it comparable.

Every production build becomes one comparable record

A production build is read into one snapshot, stored on a branch in your repository.next/app-build-manifest.json.next/prerender-manifest.json.next/server/app/**.next/static/chunks/*.js.mapcrustFIXED RULESsnapshotONE RECORDperf-historyYOUR REPOSITORY
Nothing is instrumented, re-run or scored. The same build always produces the same snapshot, and the snapshot lives on a conflict-free history branch beside your code.

Any two records become one decision

Two stored snapshots are compared and produce one decisioncfdcf500main4a802397featurecrust diffBASE → HEADBLOCK/products/[slug] is no longer staticATTRIBUTION 94%
Any two refs — branches, tags, commits or build ids. Because both sides come from the store, neither one is checked out and neither one is built a second time.

1. Build what ships

crust reads completed .next output, not HMR-heavy development bundles or a generic lab score.

2. Record the evidence

Route modes, shell HTML, chunks, source maps, cache decisions and source relationships become one compatible snapshot.

3. Choose two builds

Compare branches, commits, tags or build ids without checking either ref out or rebuilding it.

4. Get the decision first

BLOCK, REVIEW, CLEAR or CANNOT DECIDE leads the output, followed by the few changes that drove it.

5. Follow cause to source

One package, client boundary, barrel or call site is stated once with its blast radius and strongest source location.

6. Enforce what is proven

CI blocks strict regressions and your chosen ceilings, records the finding, and never turns missing evidence into a guess.

One decision from every build signal

Rendering, caching, shell composition and client cost support the same verdict.

One package regression grouped once, with the nine routes it affectsPACKAGEdate-fnsadded · +48.2 kB first load9ROUTES AFFECTED/products/[slug]+48.2 kB/checkout+48.2 kB/analytics+48.2 kB/account+48.2 kB/orders/[id]+48.2 kB/search+48.2 kB/cart+48.2 kB/wishlist+48.2 kB/settings+48.2 kB
Packages, client boundaries, barrel imports and call sites are grouped once. One decision with a nine-route blast radius, not nine copies of the same finding.

Two-build comparison

Compare a base and head by branch, tag, commit or build id. Both sides come from stored production evidence.

Decision before inventory

The verdict, worst changes, evidence coverage and likely next action appear before route tables and raw totals.

Rendering regressions

Catch a route becoming less static, losing cache coverage or shrinking its emitted shell even when bundle bytes do not move.

Client-cost regressions

Trace first-load growth to packages, complete client-boundary subtrees, barrel drag and first-party files.

One cause, every route

Group a provider, package, import style or call site once, then show every route sharing its blast radius.

Improvements included

See routes that became more static, regained caching or shed client JavaScript—not only what failed.

Complete cause chains

Follow route → component → binding → import → call site, with verified, inferred or unknown evidence.

CI with useful defaults

Mode drops, newly uncached reads and vanished shells fail without configuration. Size ceilings remain your decision.

Measured trust

Record whether authors agreed with each blocking finding and report the disagreement rate over reviewed findings.

Repository-owned history

Keep snapshots and findings on a conflict-free history branch. No account, upload or hosted dashboard is required.

Conservative enough to keep enabled

Missing evidence lowers confidence; it never becomes confident-looking blame.

Unknown is a result. If a cause cannot be established, crust reports unknown instead of manufacturing a plausible answer.
Coverage sits beside the verdict. A correct byte delta with weak attribution never looks more certain than its evidence.
A new route is not a regression. There was no baseline to become worse than; absolute budgets can still apply.
Unlike builds are not compared. Bundler, Next major and snapshot schema must agree before a delta can fail CI.
Strict direction is automatic. A static route becoming dynamic is a fact against your baseline and needs no threshold.
Product limits are explicit. First-load ceilings, growth percentages and shell floors remain in your budgets file.

Beyond the build

Useful secondary surfaces. None are required for analyze, diff or CI.

Self-contained report

One searchable HTML file. Opens with the ranked findings, then shared-cause blast radius, then route filters, grouping, cause chains and shell composition. No server or account.

Agent access

Serve the same snapshots to any MCP-capable agent over stdio, read-only. Eight tools, every answer naming the build it came from. Or run one from the terminal with crust ask.

Optional in-app panel

Route map, Web Vitals, long animation frames, image audit and streaming waterfall behind a build-time gate.

Staging measurements

Optional synthetic runs, a write-only ingest endpoint and compact route-level OpenTelemetry aggregation.

What it is not

The boundary keeps the core small enough to trust.

Not a one-build bundle explorer. Those tools show what a build contains. crust decides what changed between builds and why.
Not a runtime APM. No error tracking, distributed tracing or alerting platform.
Not a Lighthouse replacement. It measures what your build produced, not a generic lab score.
Not multi-framework. Next.js App Router only. Pages Router is detect-and-warn.
Not a hosted service. No accounts or SaaS dashboard. Your snapshots stay in your repository.
Not an AI tool. crust ships no model, embeddings or API key, and never generates a finding. The MCP server answers from stored snapshots; your agent does the talking, and every claim cites a build you can re-derive.
Not a bundler. It reads build output and never changes how the application builds.

Give your next pull request a build diff

Initialize once, keep the snapshots in your repository, and let every later build answer what changed, why, and whether it should ship.