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.
Table of Contents
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
| Technique | What it does | Cost to add |
|---|---|---|
| Lazy routes | Ships feature code only when visited | One line per route |
@defer blocks | Delays heavy components until visible | A template wrapper |
OnPush change detection | Checks components only when inputs change | One decorator option |
Tracking in @for | Reuses DOM nodes on list updates | Already required |
| Image optimisation | Serves correctly sized modern images | Markup 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 serveindex.html, which the router needs. - Server-side rendering: enable it with
ng add @angular/ssrand 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 testandng 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.

