Keep Angular bundle sizes honest

A one-time performance optimization doesn't mean much if the next commit silently undoes it. To keep an Angular app fast over time, you need a measurable target and a way to check it on every build. A JavaScript size budget is one of the most effective metrics to enforce.

Set a realistic number first

Before configuring anything, decide how much JavaScript your application can afford to ship. An online budget calculator can estimate a reasonable size based on your target Time to Interactive.

Budget calculator

The key point is having a specific number to work with, not a vague sense of "small enough."

Define the budget in angular.json

The Angular CLI supports built-in budget enforcement through the project's build configuration. The following budget is configured for a bundle named main:

"budgets": [{
  "type": "bundle",
  "name": "main",
  "maximumWarning": "170kb",
  "maximumError": "250kb"
}]

This config tells the CLI to:

  • Warn on the console if the main bundle exceeds 170 KB.
  • Fail the build entirely if the bundle goes past 250 KB.

Run ng build --prod against this configuration, and you'll see the error in the console:

Budget failure

In the sample app, the cause of the violation is an import from rxjs/internal/operators. That's a private path that clients of rxjs are not supposed to use, and it drags the bundle size up substantially. Switching to the public rxjs/operators import fixes the build.

One thing to keep in mind: the Angular CLI enables differential loading by default. The ng build command therefore produces two output builds:

  • One targeting browsers with ECMAScript 2015 support, with fewer polyfills and smaller bundles.
  • One for legacy browsers without that support, requiring more syntax-transpilation and polyfills, resulting in larger files.

The app's index.html references both builds, so modern browsers pull the leaner either ES2015 output while older ones get the fallback. Budgets are checked against the production builds.

Enforce the budget in CI

Because the Angular CLI performs its budget check during the build itself, continuous integration requires no extra wiring: a bundle over budget makes the process exit with code 1, which fails the pipeline. That's the fastest and simplest way to monitor size regressions.

Alternative tools like bundlesize or Lighthouse offer different tradeoffs. The Angular CLI's build-time check is a quick disk lookup on the generated assets, but it can't account for dynamically loaded chunks the way Lighthouse's LightWallet can. Lighthouse, however, needs the app to be deployed and opened in a browser to measure anything, which makes it slower and heavier. bundlesize behaves much like the CLI check, with the benefit of displaying results directly in the GitHub interface.

Putting a budget in place

  1. Pick a target resource size, either from a calculator or from your team's internal guidelines.
  2. Set that budget in angular.json under projects.[PROJECT-NAME].architect.build.configurations.production.budgets.
  3. Run your normal Angular CLI build, and the check happens automatically.
  4. Wire the build step into your CI to catch regressions on every pull request.