Angular Performance: The Few Things That Actually Matter

A practical Angular performance checklist — bundle analysis, lazy loading, OnPush, signals, @defer, image optimization, and the core web vitals worth chasing.

Angular has a reputation for being heavy. That reputation is partly true and mostly out of date. Modern Angular apps can be just as fast as anything else — but you can build a slow one if you don't pay attention.

These are the handful of things I actually check when an Angular app feels slow. In the order I check them.

Look at the bundle, not the code

The first thing I do is run a production build and look at the chunk sizes. If your initial bundle is over 300KB gzipped, something is pulling in too much code at startup.

Use a bundle analyzer:

``bash ng build --stats-json npx webpack-bundle-analyzer dist/your-app/stats.json ``

This shows you which libraries are bloating the bundle. Common offenders:

  • Moment.js — use date-fns or native Intl instead
  • Full Lodash — import individual functions: import debounce from 'lodash-es/debounce'
  • A UI library you only use one component from
  • A polyfill set for browsers you don't actually support

Fixing the bundle usually wins more than micro-optimizing components.

Lazy load every route

If you're using standalone components — and you should be — every route should use loadComponent:

``typescript { path: 'reports', loadComponent: () => import('./reports/reports.component') .then(m => m.ReportsComponent) } ``

The route's code only loads when the user visits it. A marketing site might have ten routes — only the first one needs to be in the initial bundle.

If a route hasn't moved out of the main bundle after this change, look at what it imports. A "lazy" route that imports a 200KB chart library at module level isn't actually lazy.

OnPush change detection

By default, Angular runs change detection on every component, every time anything changes. ChangeDetectionStrategy.OnPush tells Angular to only check the component when its inputs change or a signal it reads updates.

``typescript @Component({ selector: 'app-blog-list', changeDetection: ChangeDetectionStrategy.OnPush, ... }) ``

On a small app, you won't notice. On a dashboard with a hundred components, this is the difference between snappy and laggy.

Better yet, build the whole app on signals from the start. Signal-based components are inherently OnPush-friendly and the change detection is finer-grained.

Track expressions on every loop

@for requires a track expression. Use the most stable identifier you have:

``html @for (post of posts(); track post.id) { <article>{{ post.title }}</article> } ``

Without a proper track, Angular rebuilds every DOM node on every change. With one, it only updates what actually changed. On a list of 50 items this barely matters. On a list of 500, it's the difference between smooth and choppy.

Defer what isn't visible

Use @defer for anything that isn't immediately on screen:

``html @defer (on viewport) { <app-comments [postId]="postId()" /> } @placeholder { <p>Comments loading…</p> } ``

That comment widget only loads its code when the user scrolls to it. Same for modals, footer sections, anything below the fold. The initial page becomes lighter for free.

Images, still

Just like the WordPress slow site post — oversized images are a common bottleneck for Angular too. The framework doesn't compress your uploads any more than WordPress does.

Use Angular's NgOptimizedImage directive:

``html <img ngSrc="hero.jpg" width="1200" height="600" priority /> ``

It enforces width and height, lazy-loads non-priority images, and warns at build time when you're shipping huge files.

SSR / prerendering for the first-load story

A client-side-only Angular app has a worse Largest Contentful Paint than the equivalent SSR app, because the user is staring at an empty shell while the JS downloads. SSR or prerendering fixes that — covered in Angular SSR for SEO.

For marketing sites, prerender. For apps, SSR the public pages and client-render the authenticated ones.

Things that aren't worth the trouble

  • Hand-tuned change detection (ChangeDetectorRef.detach). Use OnPush and signals. If you're reaching for detach, you're working around the wrong problem.
  • Replacing Angular's router for performance. The router is fine. The slow thing is your route's payload.
  • Switching to "lighter" frameworks because of bundle size. A well-built Angular app is small enough. If you're at 500KB gzipped for a simple site, the framework isn't the problem.

How I check a site

Three numbers in PageSpeed Insights:

  1. LCP under 2.5s — Largest Contentful Paint, what feels like "the page loaded."
  2. INP under 200ms — Interaction to Next Paint, how the site feels under the finger.
  3. CLS under 0.1 — Cumulative Layout Shift, whether the page jumps around as it loads.

If those three are green, the perf work is done. Don't chase a 100 score; it's diminishing returns past the green threshold.

If your Angular app feels heavier than it should and you want a second look, send me a message. Most performance work is finding the one or two things that are actually slow, not optimizing everything.

Want help shipping this on your own site?

Free 30-minute consultation. No pressure either way.

Start a project