Skip to content

Client performance budgets

npx warlock build reports each page’s first-visit JavaScript and CSS after the Vite client build. Existing apps only receive a report until you configure limits. New projects start with 150kb JavaScript and 50kb CSS limits.

warlock.config.ts
import { defineConfig } from "@warlock.js/core";
export default defineConfig({
build: {
budgets: {
page: { js: "150kb", css: "50kb" },
pages: {
"/admin/**": { js: "220kb" },
"/admin/reports": { js: "300kb", css: "75kb" },
},
mode: "error",
},
},
});

Sizes accept b, kb, and mb (case insensitive), with 1024 bytes per kb. Unspecified fields are unlimited; equality passes. page sets defaults. pages keys match route paths: * stays within a segment, ** spans segments, and ? matches one non-slash character. Later matching rules override earlier fields while preserving other defaults. Other glob syntax is rejected.

Configured budgets default to mode: "error", which fails the build and restores the previous dist. "warn" prints violations and completes normally. Each violation names the page, JS or CSS budget, actual compressed size and the largest contributing modules. API-only builds have an empty page report.

build.budgets.payload limits the hydration data a single rendered document embeds in its HTML: the page, layout and app data, translations, session, the other hydration slots and the page’s browser route table.

warlock.config.ts
export default defineConfig({
build: {
budgets: {
payload: {
page: "64kb",
pages: { "/admin/**": "128kb" },
mode: "warn",
},
},
},
});

page is the limit for every document, and it defaults to 64kb. pages uses the same route-path globs as the client budgets, and the later matching entry sets the limit. mode is "warn" (the default) or "error". "warn" logs a warning and "error" logs at error level; neither fails a request.

The measurement is a runtime one. A page’s payload depends on its loaders, which only a request runs, so warlock build --json does not report it. Documents are measured until their route is reported, once per process. When a route is over budget, Warlock logs one line for that route with the size of each section (page, layout, app, translations, routes, session, other) and the three largest data keys. The check adds one serialization of the payload to each document. Set page above what a healthy page embeds, or raise it for one route, rather than turning the warning off.

The end-to-end kit records the same measurement for a loaded document with page.measurePayload({ limit }) (see Browser end-to-end tests), which adds it to the run’s evidence under payloads.

The first visit includes hydration, the requested page, its layouts and root, setup registration modules, and the error boundary. From those roots the report walks the static imports and CSS references in Vite’s .vite/manifest.json. Each emitted file is gzipped individually and counted once per page. Shared chunks count toward every page that needs them. The report excludes HTML, fonts, images, HTTP headers and cache reuse.

Dynamic imports beyond the required page roots and their static dependencies are reported in separate JS/CSS columns, including their transitive imports. They do not count against first-visit limits. This includes other pages exposed by the same hydration registry, so deferred totals describe reachable assets, not a prediction of what a user will download.

The ten largest modules use Rollup’s chunk.modules[].renderedLength, recorded by a small build plugin. These are uncompressed rendered-byte estimates for JS chunk contributors, including zero-length stylesheet wrapper modules. They are diagnostic rankings rather than compressed per-module costs.

The table sorts by combined JS and CSS size, descending, with route names breaking ties. Deltas compare the same route and source path with the previous successful build’s build-budgets.json in build.outdir (normally dist). Keep that output directory between builds to retain the baseline. First builds and new pages display no delta. Failed builds preserve the earlier baseline.

Terminal window
npx warlock build --json > build-report.json

Stdout contains one JSON object: { success, budgets, error? }; progress is sent to stderr. budgets contains version: 1, compression: "gzip", pages and violations. Each page records its route (page), source, byte totals (js, css), dynamic totals, files, modules and optional delta. Violations contain page, type, numeric budget and actual, and modules. A budget failure returns a nonzero exit status while still printing the attempted report; an earlier build failure may have no budgets value.

The full module attribution is private build metadata in client/.vite/budget-modules.json. Production asset routes do not expose that folder. No extra dependency is required.