Skip to main content
crust analyzes a production build of a Next.js App Router project, explains the rendering and cache story of every route, and stores a snapshot so the next build can be compared with evidence. Next DevTools explains the current page. crust explains what became worse, why, and whether the PR should merge.

What crust answers

For a single build:
  • 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 three findings are most worth investigating first?
Across builds:
  • Did a route become less static?
  • Did a cached read become uncached?
  • Did a component leave the static shell?
  • Did first-load JavaScript grow, and which module caused it?
  • Is the regression certain enough to fail CI?

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:
The call site named there is three frames below the page, in a file the route never mentions.
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

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.

Add CI

Zero-config regression checks, optional ceilings, and a PR comment that updates in place.

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
Project-specific ceilings remain explicit. Configure maximum first-load bytes, maximum percentage growth, and minimum shell ratios in .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.

How it differs

Route-level regression blame and shell composition, together, is the gap.

Support

Next.js 15 and 16, webpack and Turbopack. Outside that range crust refuses to run rather than emit numbers it cannot stand behind. See support and non-goals.