Quick answer: Build for production with ng build, which minifies, tree-shakes and fingerprints assets into dist/<project>/browser. Improve Angular performance with lazy-loaded routes, @defer blocks, OnPush change detection and trackable @for loops. Deploy the built folder to any static host — Firebase, Netlify, Cloudflare, S3 — or a Node server for SSR.The sections below rank Angular performance work by impact: build output first, then deferring components, then change detection, because most Angular performance gains come from doing less work rather than faster work.

Build once, serve anywhere

ng build
# output: dist/my-app/browser/

ng build --configuration production

The production build is the default in current Angular. It emits a browser/ folder of static files: an index.html, hashed JavaScript and CSS bundles, and any assets. Because the result is static, you can host it on any static host. Configure environment-specific values with ng build --configuration staging referencing a file in src/environments/.

Angular performance levers in order of impact

TechniqueWhat it doesCost to add
Lazy routesShips feature code only when visitedOne line per route
@defer blocksDelays heavy components until visibleA template wrapper
OnPush change detectionChecks components only when inputs changeOne decorator option
Tracking in @forReuses DOM nodes on list updatesAlready required
Image optimisationServes correctly sized modern imagesMarkup change

Lazy loading is covered in the routing guide; the other two deserve a closer look.

Defer heavy Angular components

The @defer block splits a component into its own chunk and loads it only when a condition is met — on viewport entry, on idle, or on interaction.

@defer (on viewport) {
  <app-heavy-chart [data]="data()" />
} @placeholder {
  <p>Chart loads when scrolled into view.</p>
} @loading (minimum 300ms) {
  <app-spinner />
}

Anything below the fold — charts, maps, comment sections — is a candidate. The user gets a working page immediately, and the heavy code arrives only if they scroll to it.

Reduce change detection work

By default Angular checks a component whenever anything above it changes. Marking a component OnPush narrows that: it is checked when an input changes, an event fires inside it, or a signal it reads changes.

import { Component, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-row',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `{{ item().name }}`,
})
export class RowComponent {
  item = input.required<Item>();
}

OnPush pairs naturally with signals: because the template reads a signal, Angular knows precisely when to re-check. In current Angular many components already behave this way, so the biggest gains come from signals and immutable inputs rather than sprinkling OnPush everywhere.

Deploying

  • Static hosts (Firebase Hosting, Netlify, Cloudflare Pages, S3 + CDN): upload dist/my-app/browser. Add a rewrite rule so unknown paths serve index.html, which the router needs.
  • Server-side rendering: enable it with ng add @angular/ssr and run the generated Node server; useful when you need SEO or faster first paint.
  • Continuous deployment: a GitHub Actions workflow that runs npm ci, ng test and ng build, then uploads the output, keeps every deploy reproducible.

One rule saves an hour of confusion: any static host must rewrite all routes to index.html. Without it, refreshing on a deep link returns a 404 because the file does not exist on the server.

That completes the series — you now have the whole path from a running project to a deployed app. For hands-on help shipping one, Ampersand Academy teaches Angular one-to-one.

Frequently asked questions

What is the output folder of ng build?

The production build writes static files to dist/your-project/browser, including index.html and the hashed JavaScript and CSS bundles.

Why does refreshing a deep link return a 404 on my host?

The static host looks for a file at that path, which does not exist. Add a rewrite rule that serves index.html for unknown routes so the Angular router can handle them.

What does the @defer block do?

It splits a component into its own chunk and loads it lazily when a trigger such as viewport, idle or interaction occurs, with placeholder and loading states.

Should I use OnPush change detection everywhere?

Use it where components are pure and input-driven, especially in long lists. Signals already reduce unnecessary checks, so focus on signals and immutable inputs first.

Do I need a Node server to host an Angular app?

No. A standard Angular build is static and can be hosted on any static host or CDN. A Node server is only needed if you enable server-side rendering.

Last updated on · Written by