Angular performance can be significantly improved with eight targeted techniques: Lazy Loading, OnPush Change Detection, Signals, SSR/SSG rendering, image optimization, Critical CSS, font preloading, and chunk analysis. The most common performance problems in Angular apps stem from oversized initial bundles, uncontrolled change detection cycles, and missing rendering strategies. With these measures, SME websites achieve measurably better load times and PageSpeed scores without completely rebuilding the architecture.
Reducing Bundle Size: Lazy Loading and Code Splitting
The initial JavaScript bundle is the single biggest performance lever in Angular applications. By default, Angular loads all registered components on the first page visit – even though users never see 80% of them. Lazy Loading via code splitting solves this problem structurally.
- loadComponent: Instead of static imports, use loadComponent: () => import('./pages/contact/contact').then(m => m.ContactPage). Angular automatically splits the code into separate chunks.
- Standalone Components: Each component carries its own dependencies – no SharedModule pulling everything into the initial bundle. Direct imports enable precise tree-shaking.
- Monitor chunk sizes: ng build --stats-json + webpack-bundle-analyzer shows which chunks are too large. Target: initial bundle under 200 KB gzipped.
- Preloading strategy: PreloadAllModules or a custom strategy ensures lazy chunks are loaded in the background before the user navigates.
Change Detection and OnPush Strategy
Angular's default change detection checks the entire component tree for changes on every event. In large apps with many components, this leads to hundreds of unnecessary DOM checks per second. OnPush reduces this to the necessary minimum.
- ChangeDetectionStrategy.OnPush: The component only re-renders when an input reference changes, a signal value updates, or an async pipe emits a new value.
- Angular Signals (from Angular 16): signal(), computed(), and effect() replace RxJS BehaviorSubjects for local state. Signals trigger change detection precisely – only affected components are updated.
- computed() for derived state: Instead of calling a method in the template, computed() caches the result and only updates when the source data changes.
- effect() in the constructor: Register side effects in effect() inside the constructor – never in ngOnInit, since effect() internally uses inject().
- No function calls in templates: someMethod() in a template is re-executed on every change detection cycle. Use computed() or getters with memoization instead.
SSR and SSG: When Is Each Worth It for Angular Apps?
The rendering model largely determines Time to First Byte (TTFB), SEO indexability, and perceived loading time. Angular Universal offers three options that suit different use cases.
- SSG (Static Site Generation) via ng build --prerender: Pages are rendered as static HTML at build time. TTFB under 50 ms is achievable. Ideal for all content pages (blog, glossary, city pages).
- SSR (Server-Side Rendering): Pages are rendered on the server per request. Only necessary for truly dynamic data (logged-in user status, real-time prices).
- CSR (Client-Side Rendering): Only suitable for purely interactive app areas behind a login. To be avoided from an SEO perspective for publicly accessible pages.
- Hybrid model: app.routes.server.ts allows per-route selection between prerender, server, and client. Content pages on prerender, dynamic dashboards on server.
- Hydration: Angular 17+ Universal Hydration prevents Flash of Unstyled Content during SSR-to-CSR transition. Activate withEventReplay() in app.config.ts.
The build process with ng build --prerender generates a separate index.html for each prerendered route – Apache serves these directly without Angular server logic. For SME websites with 50–100 pages, this is the optimal combination of SEO performance and low server overhead.
Optimizing Images and Assets
Images account for the largest bandwidth consumption on most websites and are the most common cause of poor Largest Contentful Paint (LCP) scores. Proper caching and optimized asset delivery are among the measures with the best effort-to-benefit ratio.
- WebP as the standard format: WebP is 25–35% smaller than JPEG at equivalent visual quality. Use WebP for all non-transparent images.
- NgOptimizedImage (Angular 15+): Enforces width/height attributes, automatically sets loading='lazy' for below-fold images, and supports srcset for responsive images.
- Preload above-fold images: The hero image gets the priority attribute – Angular sets fetchpriority='high' and a <link rel='preload'>. Only applies to the actually visible LCP element.
- Lazy loading for below-fold assets: Images below the viewport get loading='lazy'. Video embeds get preload='none' – saving several MB of bandwidth on initial load.
- {criticalCss} inlining: Inline the styles needed for above-fold content directly into the <head>. Prevents render-blocking from external stylesheet requests.
- Font subsetting and preload: Self-host Google Fonts and subset to required character sets. <link rel='preload' as='font'> for the primary font in index.html.
- HTTP caching headers: Serve static assets (JS, CSS, images) with long cache times and content hashes in filenames.
- Remove unused CSS: Component-scoped SCSS prevents global stylesheet bloat.
Summary
- Bundle size: loadComponent for all routes, Standalone Components, target under 200 KB gzipped initial.
- Change Detection: OnPush on all components, Signals instead of RxJS for local state, no function calls in templates.
- Rendering strategy: SSG (ng build --prerender) for content pages, SSR only for truly dynamic data.
- Images: WebP, NgOptimizedImage with priority for the LCP element, lazy loading for all below-fold assets.
- Assets: Self-host and preload fonts, HTTP caching with long durations, inline Critical CSS.
- Monitoring: Integrate Lighthouse CI into the build pipeline, check Core Web Vitals after every deployment.
