Skip to content
Angular ng deployment 4 min read

Building for Production

Before an Angular app reaches users it must be compiled, tree-shaken, minified, and bundled into static assets that a browser can load fast. The ng build command handles all of this, and on modern Angular (17+) it defaults to a production-optimized build powered by the esbuild-based application builder. Understanding what the build produces — and how to tune it — is the difference between a snappy app and a multi-megabyte download.

Running a production build

In Angular 17 and later, ng build runs in production mode by default. There is no longer a separate --prod flag — it was removed because production is the sensible default.

ng build

This compiles your app using the production configuration defined in angular.json, applies ahead-of-time (AOT) compilation, minifies the output, removes dead code, and writes everything to the output directory.

Output:

Initial chunk files   | Names         |  Raw size | Transfer size
main-7XQ2K4UJ.js      | main          | 142.06 kB |        41.21 kB
polyfills-FFHX3O3N.js | polyfills     |  34.52 kB |        11.28 kB
styles-5INURTSO.css   | styles        |   1.92 kB |       456 bytes

                      | Initial total | 178.50 kB |        52.94 kB

Application bundle generation complete. [3.214 seconds]

To build a specific named configuration explicitly, use --configuration:

ng build --configuration production
ng build --configuration staging

Use ng build to generate deployable artifacts. Use ng serve only for local development — its in-memory dev server is never meant for production traffic.

The dist output structure

By default the build writes to dist/<project-name>/. With the application builder, the output is split into a browser/ folder (and a server/ folder if you enable SSR):

dist/my-app/
├── browser/
│   ├── index.html
│   ├── main-7XQ2K4UJ.js
│   ├── polyfills-FFHX3O3N.js
│   ├── styles-5INURTSO.css
│   ├── chunk-AB12CD34.js
│   ├── favicon.ico
│   └── assets/
└── 3rdpartylicenses.txt

The browser/ directory is what you deploy to a static host or CDN. Lazy-loaded routes produce additional hashed chunk-*.js files that load on demand.

Output hashing and cache busting

Notice the hashes in filenames like main-7XQ2K4UJ.js. These content hashes change whenever the file’s contents change, which lets you set long-lived cache headers (Cache-Control: max-age=31536000, immutable) without ever serving stale code. When you deploy a new version, filenames change and browsers fetch the fresh files automatically.

Hashing is controlled by the outputHashing option:

ValueHashes appliedTypical use
allJS bundles and CSS (default for prod)Production
mediaReferenced assets like images/fonts onlyRare
bundlesJS and CSS bundles onlyRare
noneNo hashingLocal testing
ng build --output-hashing=all

Because index.html is never hashed (it is the entry point), the browser always fetches the latest index.html, which in turn references the newly hashed bundles.

Optimization flags and configuration

Production optimizations are declared per-configuration in angular.json. A typical production configuration looks like this:

{
  "configurations": {
    "production": {
      "optimization": true,
      "outputHashing": "all",
      "sourceMap": false,
      "namedChunks": false,
      "extractLicenses": true,
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "500kb",
          "maximumError": "1mb"
        },
        {
          "type": "anyComponentStyle",
          "maximumWarning": "4kb",
          "maximumError": "8kb"
        }
      ]
    }
  }
}

Key options:

OptionEffect
optimizationEnables minification, tree-shaking, and CSS optimization
sourceMapEmits .map files for debugging; usually false in production
extractLicensesCollects third-party licenses into 3rdpartylicenses.txt
budgetsFails or warns the build when bundles exceed size thresholds
outputPathOverrides the default dist/<project-name> directory

You can override any of these from the command line:

ng build --source-map --output-path=dist/preview

Bundle budgets

Budgets are a guardrail against bundle bloat. When a bundle crosses maximumWarning, the build prints a warning; crossing maximumError fails the build entirely. This is invaluable in CI to catch an accidentally imported heavy library before it ships.

Output:

▲ [WARNING] bundle initial exceeded maximum budget.
  Budget 500.00 kB was not met by 78.40 kB with a total of 578.40 kB. [plugin angular-cli]

Analyzing the bundle

When a bundle is larger than expected, generate stats and inspect what is inside. The application builder supports --stats-json:

ng build --stats-json
npx esbuild-visualizer --metadata dist/my-app/stats.json --open

This produces a treemap showing exactly which modules and dependencies contribute to each chunk, so you can spot a stray moment or duplicated library and replace or lazy-load it.

Best Practices

  • Keep sourceMap: false for the public production bundle, but consider hidden source maps for error-monitoring tools so stack traces remain readable without exposing source to users.
  • Always enable outputHashing: "all" and pair it with immutable, long-lived cache headers on hashed files while leaving index.html uncached.
  • Define and enforce bundle budgets so size regressions fail CI rather than surprising you in production.
  • Lazy-load feature routes with loadComponent / loadChildren so the initial bundle stays small and routes load on demand.
  • Run ng build --stats-json periodically and audit the treemap to catch accidental heavy imports early.
  • Deploy only the browser/ directory (plus server/ for SSR) — never ship source files, source maps, or node_modules.
Last updated June 14, 2026
Was this helpful?