writing

Enforce a bundle budget in CI (so size can only go down)

A performance budget is only real if the build fails when you cross it. How to set a bundle budget and wire it into CI in a few lines.

A performance budget you do not enforce is a wish. The only budget that holds is the one that fails the build when a pull request pushes you over it. Here is how to make bundle size a CI gate.

1. Pick a budget you can defend

Start from where you are, not an aspirational round number. Drop your current stats into the free analyzer to see today's total, then set the budget a little above it — tight enough to catch creep, loose enough not to block every PR. A landing page might target < 150kb of gzipped JavaScript; an app page < 300kb. Adjust to your reality.

2. Emit stats your CI already has

Your bundler already produces the data:

  • webpack — webpack --json > webpack-stats.json (or BundleAnalyzerPlugin in json mode)
  • esbuild — metafile: true
  • rollup / vite — rollup-plugin-visualizer with template: 'raw-data'

No new build step — you are capturing output you already generate.

3. Push the stats and let the gate fail over budget

With dendrobundle, the push records the snapshot and, when the project is over its configured budget, the alert fires and the result is visible on every build. The CI step is one curl call:

curl -sS "https://dendrobundle.com/api/push?branch=$BRANCH&commit=$SHA" \
  -H "Authorization: Bearer $BUNDLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d @dist/webpack-stats.json

See the CI integration guide for GitHub Actions and GitLab CI snippets, and the supported formats reference.

4. Make the number visible

Drop the README badge into your repo so the current size — green under budget, red over — is the first thing a contributor sees. Visibility is half the enforcement.

Why this works

Budgets fail open by default: without a gate, size drifts up one harmless-looking PR at a time. A CI check flips the default — now size can only go down unless someone consciously raises the budget. That is the whole trick, and it is why bundle size maps so directly onto Core Web Vitals: you are enforcing the input that moves the metric.