Performance

Angular Performance Tuning: Complete Guide To Bundle Optimization

Learn everything you need to know about Angular performance tuning: how to generate a bundle performance profile, and how to split up your application into smaller chunks via standalone lazy-loading and partial template loading.

Angular Performance Tuning: Complete Guide To Bundle Optimization — Performance article by Angular University on Angular In Depth
Angular Performance Tuning: Complete Guide To Bundle Optimization — Performance article by Angular University on Angular In Depth
On this page · 6 sections

If you have ever faced the issue of an Angular application that loads too slowly, this guide is exactly what you need.

We’ll demonstrate a methodical, data-driven strategy to solve this challenge.

Our first step will be to create a performance profile of your bundle, which will help pinpoint the main performance bottlenecks in your code.

Following that, we’ll explore several techniques to break your application into smaller pieces, utilizing the most advanced performance features Angular has to offer:

  • standalone lazy-loading
  • partial template loading

With that said, let's dive straight into this Angular Performance Tuning deep dive!

Table of Contents

This article will cover the following subjects:

  • Which metrics should you track?
  • Installing the source map explorer package
  • Simplify full application lazy-loading using standalone components
  • Partial template loading with @defer
  • Key takeaways

If you prefer a video format, you can watch the video version of this post here:

Angular Performance Tuning Crash Course

What type of metrics should you collect?

Let's begin with the most critical point!

The biggest mistake you can make when optimizing your application is to work without proper data, relying on intuition and guesswork.

The very first action should be to gather measurable data and performance metrics.

To achieve this, we will utilize the source map explorer package.

Consequently, your initial move should always be to produce a bundle profile report.

This report provides a visual breakdown of disk space usage, revealing which libraries and Angular components consume the most space in your JavaScript bundle.

This insight is crucial for identifying the largest portions of your application, allowing you to focus your optimization efforts effectively.

To get these metrics, you will need the following profiling tool.

Setting up the source map explorer package

The first step is to add the package below to your project:

npm install -g source-map-explorer

Once installed, the next step is to use it for generating the report.

Here is the npm script I typically use to create my bundle report:

{
    ...
  "scripts": {
    ...
    "bundle-report": 
       "ng build --configuration production  
       --source-map && 
       source-map-explorer dist/browser/*.js"
  },
}

This script operates in two stages:

  • First, a production build is required. This is done by running ng build --configuration production

  • After the uncompressed bundles are created on your local system, we execute the source-map-explorer package to produce the report.

I suggest adding a similar script to your project to make it easy to generate a bundle size report whenever needed.

Upon opening the report, you'll see a visualization similar to this:

Angular performance tuning of an application bundle

Don't be alarmed by these bundle sizes; the real production bundle is much smaller. Keep in mind that these are uncompressed files.

With this report, you can begin spotting components and libraries suitable for lazy-loading into separate bundles.

Look for large dependencies that are infrequently used, such as libraries for generating PDFs or creating charts.

These are used sparingly by users, making them excellent candidates for code splitting.

Make your application fully lazy-loaded

Now that you are aware of the space-hogging parts of your app, the subsequent goal is to lazy-load as much as possible.

The ultimate goal is to have each screen in your application loaded in its own separate bundle.

This is what we call a fully lazy-loaded application. How can you achieve this?

Currently, the best approach is to use the simplified lazy-loading capabilities provided by standalone components.

Let's demonstrate how straightforward it is to convert a route to lazy-loading using Standalone components.

Below is a route with a screen that currently is not lazy-loaded:

...
export const routes: Routes = [
  {
    path: 'courses/:courseUrl',
    component: WatchCourseComponent
  }
]
...

Notice that with NgModule-based components, achieving lazy-loading for this screen would be a complex task.

However, this application has already made the switch to standalone components!

Therefore, all we need to do is change the component property to loadComponent, like this:

  {
    path: 'courses/:courseUrl',
    loadComponent: () => 
      import('./watch-course/watch-course.component')
        .then(mod => mod.WatchCourseComponent),
  }

And that's all it takes!

This route is now lazy-loaded, and the WatchCourseComponent, including all its dependencies, are no longer part of the main bundle.

Consequently, if this screen required a heavy library for PDFs or charts, those substantial dependencies are now excluded from the main application bundle and will only load when needed.

Keep in mind, this works only because WatchCourseComponent is a standalone component.

For more information on standalone components and migration strategies, I've written a comprehensive guide on the topic:

Angular Standalone Components: The Complete Guide

You can now revisit your routing configuration, implement loadComponent throughout, and you will achieve a fully lazy-loaded application where every container screen has its own dedicated bundle.

Your main bundle will then primarily contain the Angular framework with few additional heavy third-party dependencies.

This alone should significantly enhance your application's performance, but we can push the boundaries further.

Partial template loading with @defer

Let's move on and introduce another performance optimization technique, which shares similarities with lazy-loading.

Returning to our codebase, we have already lazy-loaded this component, correct?

  {
    path: 'courses/:courseUrl',
    loadComponent: () => 
      import('./watch-course/watch-course.component')
       .then(mod => mod.WatchCourseComponent)
  }

However, let's assume the WatchCourseComponent is still relatively large.

This component functions as a course player that handles various lesson types:

  • audio lessons
  • digital downloads
  • assessments, and more.

All these are part of the watch course screen.

Suppose a specific course contains only video lessons.

In that case, it doesn't have audio lessons or other types.

If so, loading the code for audio lessons would be wasteful and premature.

Ideally, we should defer loading the audio lesson code until the user actually clicks on an audio lesson.

This strategy allows us to avoid loading code that may never be used during a user's session!

However, as it stands, the component will load either entirely or not at all.

We need a form of partial template loading that enables us to load parts of a screen only when necessary.

This is achievable through the powerful @defer feature of Angular:

@defer(when lesson.type == 'audio') { 

  @if(lesson.type == 'audio') {

    <audio-player [lesson]="lesson" />

  } 
}

The way this functions is that defer causes anything inside the defer block to be loaded as a separate bundle, similar to router-based lazy loading.

The defer block will lazy-load the code inside it, while the if block conditionally shows or hides the audio player. The trigger @defer(when lesson.type == 'audio') ensures the audio player is lazy-loaded only when the lesson type is audio.

The result is that the audio player's code is no longer bundled with its parent, the WatchCourseComponent.

With @defer, the audio player code is only fetched when navigating to an audio lesson.

Angular's new @defer feature is quite potent; for an in-depth explanation, I've prepared a detailed guide:

Angular @defer: The Complete Guide

The bottom line

To summarize, here is your strategy for tuning Angular application performance:

  • Step 1: Always begin by generating a report to visualize space usage in your app. The source-map-explorer package is perfect for this task.
  • Step 2: I recommend migrating to Standalone components and taking advantage of the streamlined lazy-loading feature, loadComponent. This makes lazy-loading every screen in your app incredibly easy.
  • Step 3: For any components that remain too large, employ @defer to enable partial template loading within those heavier components.

By iteratively applying these steps and consistently generating bundle reports to verify improvements and uncover new bottlenecks, you can make your application as lean as possible.

And remember, if you'd like to see these methods applied in a video format, here is the video version of this post, which demonstrates optimizing a real production application:

Angular Performance Tuning Crash Course

Please share any questions or comments below, I'd be happy to assist!

AU
Angular University

Writes about RxJS, Components, Signals. Active 2015–2026.

All 79 articles →