Performance
Performance regressions in CI: the check that costs 70 ms
Seventy build files, 172 KB of compressed JavaScript and 36 KB of CSS: the budget check reads them in 61-77 ms and blocks the merge when the weight grows.
Key points
- A weight budget in CI costs 61-77 ms over 70 files, so it can run on every push.
- The build JavaScript measures 511 KB uncompressed and 172 KB after gzip.
- Two chunks exceed 44 KB compressed: they are the first to inspect when the total grows.
- A weight budget does not measure runtime: a CPU regression slips through anyway.
A performance regression is stopped in CI by a budget on asset weight, not by a Lighthouse test on every push: over 70 build files the check reads everything in 61-77 ms and fails when compressed JavaScript crosses the threshold. Today's build measures 172 KB of gzipped JavaScript and 36 KB of CSS.
The budget, with today's numbers
| Item | Measured | Threshold | What it blocks |
|---|---|---|---|
| Total JavaScript, gzip | 172 KB | 200 KB | one heavy import |
| Total CSS, gzip | 36 KB | 50 KB | a whole theme or framework |
| Single chunk, gzip | 44 KB | 50 KB | a library landing in the vendor chunk |
| Check time | 61-77 ms | 2 s | nothing: it is only the cost of the gate |
A 200 KB budget leaves 28 KB of room, roughly one heavy component or two small ones. It is not an arbitrary threshold: it is the measured number plus the margin you are willing to give the next feature. A budget with no margin breaks in the first week and gets switched off.
The check
const fs = require("fs"), zlib = require("zlib"), path = require("path");
const walk = (d, acc = []) => {
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
const f = path.join(d, e.name);
e.isDirectory() ? walk(f, acc) : acc.push(f);
}
return acc;
};
let js = 0, worst = 0;
for (const f of walk("client/dist")) {
const gz = zlib.gzipSync(fs.readFileSync(f)).length;
if (f.endsWith(".js")) { js += gz; worst = Math.max(worst, gz); }
}
console.log(Math.round(js / 1024) + " KB JS gzip, chunk peggiore " + Math.round(worst / 1024) + " KB");
if (js > 200 * 1024 || worst > 50 * 1024) process.exit(1);
The script needs only Node and a built client/dist folder: no dependency, no browser. It exits with code 1 when a threshold is crossed, and in a pipeline that stops the merge. Put it after the build step and before the deploy: a budget running against old code is useless.
What the budget cannot see
Weight is not time: a 44 KB chunk can become three times slower without changing size, if the work it does on first render changes. The budget catches the most common cause, the extra dependency, and lets the sneakiest regression through, the algorithmic one. For that you need field data or a trace in CI, which cost minutes instead of milliseconds.
The limitation to name
The 172 KB is the sum of every chunk, not what a single page downloads: the real load depends on routing and code splitting. A per-route budget is more precise and harder to maintain, because you must know which chunks each page loads. gzip is not brotli either: brotli gives lower absolute numbers, and the comparison between two builds still holds.
Where to continue
The EneUI catalogue indexes 111,985 components, 1,556 blocks and the record splite, a component that installs with its own dependencies and shows up in the budget at once. For thresholds in the pipeline read Lighthouse CI with thresholds; for the weight of UI libraries, UI library bundle size; to ship less code, code splitting per route; and for the comparison between architectures, frontend frameworks compared.
Frequently asked questions
A weight budget or Lighthouse CI?
A weight budget costs milliseconds and catches the most common cause, an extra dependency. Lighthouse CI costs minutes and sees time too, but is noisy on shared runners. Together they cover different causes.
How much margin above the measured value?
Between 10% and 20%, enough for a small feature and not enough for a whole library. A budget with no margin fails on every change and gets switched off.
Should the budget be per file or per page?
Per page is more precise, because it measures what the user downloads. Per file is simpler and more stable: you compute it from the build without knowing which chunks the routing loads.
Gzip or brotli in the budget?
Whichever your clients really get. Brotli compresses better and gives lower numbers; what matters is using the same algorithm for the threshold and the measurement, otherwise the build-to-build comparison breaks.
How do I stop the budget blocking an urgent hotfix?
With an explicit, recorded exception, not by switching the check off. Raise the threshold in a separate pull request with the date and the reason, and lower it again after the release.