Production and Performance
|
This section documents modern, standalone Angular — signals, the built-in This content was generated with the assistance of AI and should be verified against angular.dev before being relied on in production. Angular ships a major release roughly every six months and its APIs continue to evolve: the examples here target the current major release; where a consulted source disagrees with the current documentation, the documentation wins and the difference is noted. This section’s bibliography lists the reference material consulted while preparing these pages. |
Shipping an Angular app well means an optimized production build with enforced size budgets, attention to the Core Web Vitals, and — where SEO or first paint matter — server-side rendering with hydration. See ng build and Performance.
Production builds
ng build uses the production configuration by default: ahead-of-time template compilation, minification,
tree-shaking, dead-code elimination, and output hashing (main.<hash>.js) for long-term caching. Output
lands in dist/<project>/.
ng build # production by default
ng build --configuration development
ng build --source-map # emit .map files for debugging
ng build --stats-json # for bundle analysis (esbuild --analyze, source-map-explorer)
ng deploy # via a deploy builder added with `ng add`
Configurations live in angular.json under
projects.<name>.architect.build.configurations. Each one can set optimization flags and a file
replacement, so environment-specific values are swapped at build time:
{
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"budgets": [
{ "type": "initial", "maximumWarning": "500kB", "maximumError": "1MB" },
{ "type": "anyComponentStyle", "maximumWarning": "4kB", "maximumError": "8kB" }
]
}
}
}
Bundle budgets fail the build when a bundle grows past maximumError — the cheapest guard against
accidental bloat such as a mis-imported library. Budget types include initial, all, anyComponentStyle,
bundle, and allScript. See Workspace configuration.
Deployment: ng deploy runs a deploy builder contributed by a provider package
(ng add @angular/fire, angular-cli-ghpages, and others). Without one, copy dist/<project>/browser/ to any
static host and add a SPA fallback that serves index.html for unknown paths. See
Deployment.
Core Web Vitals
| Metric | Measures | Angular levers |
|---|---|---|
LCP — Largest Contentful Paint |
how fast the main content loads |
|
INP — Interaction to Next Paint |
responsiveness to input |
|
CLS — Cumulative Layout Shift |
visual stability |
explicit |
NgOptimizedImage — replace src with ngSrc and Angular sets loading / fetchpriority, generates a
srcset, requires an explicit width / height (or fill), and warns on oversized files:
<img ngSrc="/assets/hero.jpg" width="1200" height="600" priority alt="Product hero" />
<img ngSrc="/assets/thumb.jpg" width="200" height="200" alt="Thumbnail" />
@defer loads a block’s JavaScript only when a trigger fires (on viewport, on interaction, on idle),
shrinking the initial bundle — recap on Control flow and @defer.
Route-level code splitting with loadComponent / loadChildren keeps each route’s code out of the initial
download — recap on Routing. See
Performance.
SSR and hybrid rendering
Add SSR with ng add @angular/ssr (or ng new --ssr). It scaffolds a server.ts request handler, a
main.server.ts, and an app.config.server.ts. Rendering can be mixed per route: client-side,
server-side (SSR) per request, and prerendered (SSG) at build time.
// app.config.ts — hydrate the server-rendered markup on the client
import { ApplicationConfig } from '@angular/core';
import {
provideClientHydration, withEventReplay, withIncrementalHydration,
} from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideClientHydration(withEventReplay(), withIncrementalHydration()),
],
};
provideClientHydration() makes the client reuse the server-rendered DOM instead of re-creating it — no
flicker and no duplicate data fetch. withEventReplay() records clicks and keystrokes that happen before
hydration completes and replays them once it does.
Incremental hydration hydrates @defer blocks lazily, each on its own trigger, using the hydrate
keyword: the server renders the content, and the client ships and activates its JavaScript only when needed.
It requires opting in with withIncrementalHydration() (as above); without it the hydrate triggers are
inert and the block behaves as a plain @defer.
@defer (hydrate on viewport) {
<app-reviews [productId]="id()" />
} @placeholder {
<p>Reviews</p>
}
Triggers: hydrate on idle, hydrate on viewport, hydrate on interaction, hydrate on hover,
hydrate on immediate, hydrate on timer(…), hydrate when <expr>, and hydrate never for content that
stays permanently static. See Incremental hydration.
Prerendering (SSG) renders routes to static HTML at build time — set a route’s renderMode to
RenderMode.Prerender in the server routes config and list its parameter values. The app-shell pattern
prerenders a minimal layout (header, nav, spinner) as the first paint while the full app bootstraps:
ng generate app-shell. See Server-side rendering and
Hydration.
The SSR request flow
Cross-links: Control flow and @defer, Lifecycle and change detection for zoneless change detection, and CORS for cross-origin data fetching on the server.