DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for How I optimized my Angular website
Abdjalil Brihoum
Abdjalil Brihoum

Posted on • Updated on

How I optimized my Angular website

Introduction

Building applications/websites with Angular always comes with a downside: the bundle size.
The latter has a direct impact on the loading speed & the user experience of our projects.

Angular bundle size

Even if we finally reduced the bundle size, there are other boxes to check to have the ideal website.

Personally, I have four steps to follow when building apps/websites.

  1. Designing
  2. Coding
  3. Making the website responsive
  4. Optimizing

On this post, we will focus on the last step.


How I optimized my Angular website

I'll start with the problems I've faced, then how I've addressed them.

1 - Visual Problems

The following link is a showcase of my website after the 3rd step.

From this video, I can extract four visual problems :

1.1 - Visual problem 1

The website looks broken for a split second, then loads ordinarily

Angular styles late loading

1.2 - Visual problem 2 & 3

The font took ages to load, same thing goes with the pizza picture

No font nor pizza picture

1.3 - Visual problem 4

The speed of loading images is super slow.


2 - Invisible problems

Let's open the dev-console and see what's happening under the hood.

I can take out two issues from this video

2.1 - Invisible problem 1

dev-tools

The website took 4.57s to fully load, with 98 requests and 5.4 MB of resources. To put those numbers into perspective, a 3g internet will take about ~24s to load all the resources.

2.2 - Invisible problem 2

Broken loading tree

The pizza picture took ~1.07s (0.689s + 0.387s) to be displayed, this means the user was seeing a broken slider for 1 second. The same goes with the font.

You may ask : Why did he pick the pizza picture from all the other pictures ?
I'll answer you with another question :
What picture do you see first when you visit the website ?


Lighthouse Score

Lighthouse Score

As I expected, the LCP (largest Contentful paint) and the CLS (Cumulative Layout Shift) are bad, because of Invisible problem NΒ°2 and Visual problem NΒ°1 respectively, surprisingly the First Contentful Paint is good.

Bundle size

Bundle size

Not that bad, but we can do better.

ℹ️ Note : before explaining and fixing said problems, lets first optimize the bundle size.


Improving the bundle size

Before starting, I would like to highlight something :

  • ⚠️ Never import third party CSS inside any of your Angular components, instead use styles.css.

There are lots of ways on how to reduce the bundle size, but that's not the subject for today, here I'm showcasing how 'I' optimized my Angular website.

1 - Lazy load

The first thing I personally do is to Lazy load noncritical third party libraries, this means libraries that are not required the second the website loads, therefore their load can be delayed until all the more important resources are loaded. I'll give you an example to clarify more :

  • I have a plugin called lightGallery, the latter is only required when a user wants to open an image gallery. Logically, we can delay its load until all of the website more critical resources (like the pizza picture & important CSS) are downloaded.

  • I also have Bootstrap installed. Its JavaScript is only required when we need interactivity in our project, like for example : Opening a modal, Using a collapse or a carousel… So we can delay its load too.

1.1 - Lazy loading : LightGallery

In the following video, I'll explain in detail the process :

The code I used in the video :

// main.component.ts
let src = "https://jsdelivr.com"
window.onload = () => {
  let script = document.createElement("script")
  script.src = src
  script.async = true
  document.head.appendChild(script)
}
Enter fullscreen mode Exit fullscreen mode

1.2 - Lazy loading Bootstrap

The same process goes with Bootstrap, remember jsdelivr ? Search for 'bootstrap' and :

jsdelivr

Copy the link and replace the old with the new one.

main.ts

ℹ️ Ps : Remember to remove any other imported Bootstrap JavaScript

Bootstrap

1.3 - Bundle size

Bundle size

βœ… Just like that, we eliminated (125.01 kB)

2 - Removing unused modules

My website is a single-page website, even though, Angular routing is installed. To fix this, all I need is to comment out AppRoutingModule on my app.module.ts

app.module.ts

Now, I need to replace <router-outlet></router-outlet> with my parent component selector, which is app-main

app-main

2.1 - Bundle size

Bundle size

βœ… We eliminated a total of (201.31 kB) from the initial build.

You can check the website after reducing the bundle size.

About lighthouse, the score has improved a little, but the website still has all the problems mentioned earlier. So, let's fix them.

Lighthouse


Explaining Visual problem NΒ°1

Like I mentioned before, this website uses Bootstrap, and styles.css contains Bootstrap's CSS. The reason of this problem is that Angular started printing the website before styles.css finished downloading, this means we had no stylesheet for Bootstrap until styles.css finished downloading.

styles.css

To confirm this, we can try to block styles.css from downloading at all and see if we have the same results.

styles.css

β„Ή And yea, the same result.


Solving Visual problem 1

To solve this problem, all my critical CSS needs to be ready when Angular start printing. Critical CSS means :

The CSS responsible for the content that's immediately visible when we open a website.

or :

The CSS of the first page you see when opening a website.

In my case :

  • Bootstrap's CSS
  • SiwperJS's CSS.

But because I have buttons in the first page that are styled with some custom CSS, this CSS is also considered critical.

Buttons

Also, the animation I'm using on my website are considered critical too, the video below explains everything :

To resume, we have as Critical :

  • Bootstrap CSS,
  • SwiperJS CSS,
  • Custom CSS,
  • Animations CSS,

Now, let's get back to work.

First, I created a SCSS file named bootstrap.scss, and I imported inside it only the Bootstrap component I need

bootstrap.scss

ℹ️ Note : you can import all of bootstrap if you you want to, later I'll explain how we can remove unused CSS using PurgeCSS.

And I did the same thing with SwiperJs, Animations, and my custom CSS

css

Next, I created a file named combined.scss and imported all the SCSS files I just created

combined.scss

To clarify more, that's the list of files :

list

ℹ️ Note : don't forget to remove the old imported CSS, ex : don't import Bootstrap on both styles.scss and combined.scss.

After that, I jumped to angular.json, and under styles[] :

{
  "projects": {
    "app": {
      "architect": {
        "build": {
          "options": {
            "styles": []
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

I added the following :

{
  "input": "[YourPath]/combined.scss",
  "inject": false,
  "bundleName": "combined"
}
Enter fullscreen mode Exit fullscreen mode

angular.json

Then, I opened my index.html and added the code below on the top of the <head> tag

<!-- index.html -->
<link rel="preload" href="combined.css" as="style" />
<link rel="stylesheet" href="combined.css" />
Enter fullscreen mode Exit fullscreen mode

index.html

What I've done here, is that the second a user visits the website, the first resource to be added to the download queue is combined.scss, this means the browser will start downloading my website resources with combined.scss on the top of the list, thus when Angular starts printing, the critical CSS are already loaded and ready to use.

Preload

Source : https://developer.mozilla.org

Bundle size

Bundle size

After building, I had this Lazy Chunk Files section, with my combined.css file there, in reality, I'm preloading, not lazy loading it.

You can also notice that the size of styles.css dropped significantly

Now, thanks to PurgeCSS, I'll try to reduce the size of combined.css by removing unused CSS.


Installing PurgeCSS

On my command prompt :

# command prompt
npm i -D purgecss
Enter fullscreen mode Exit fullscreen mode

After that, I created a file named purgecss.config.js on the root of my project.

purgecss.config.js

And inside it I have the following lines :

// purgecss.config.js
module.exports = {
  content: ["./dist/**/index.html", "./dist/**/*.js"],
  css: ["./dist/**/combined.css"],
  output: "./dist/[FOLDER]/combined.css",
  safelist: [/^swiper/],
}
Enter fullscreen mode Exit fullscreen mode

β„Ή Note : Remember to replace [FOLDER] (within the output property).

β„Ή Note : you may notice that I have set safelist to [/^swiper/], That's because I don't want PurgeCSS to remove any SwiperJS CSS. Because SwiperJS will add some CSS classes after the page execution, thus PurgeCSS can't know about them, and end up removing them.

Next, I opened package.json, and edited build from :

"build": "ng build"
Enter fullscreen mode Exit fullscreen mode

To :

 "build": "ng build && npm run purgecss "
Enter fullscreen mode Exit fullscreen mode

Then I created a new script named purgecss :

"purgecss": "purgecss -c purgecss.config.js",
Enter fullscreen mode Exit fullscreen mode

To clarify, that's how package.json should look like

package.json

⚠️ Note :

To build, use npm run build instead of ng build, So PurgeCSS can kick in.

Result after solving Visual problem 1

Before and after using PurgeCSS on combined.css :

combined.css

Now, let's take a look on the loading tree :

loading tree

Like I explained before, combined.css is now the first file on the download queue.
The downside of this method is that now we have two stylesheets (styles.css & combined.css), this means one more request to the server, and a couple of milliseconds wasted. Later I'll explain how I fixed this small issue.

Lighthouse

Lighthouse

Even if lighthouse is telling me : 'your website is perfect', he is not 100% correct,
What about the Visual problem 2 & 3 & all the invisible problems?

β„Ή Note : The website after this method


Explaining Visual problem 2 & 3

The cause of this problem, is the loading tree, or the order of the resources in the download queue.
Like you maybe already know, browsers have a limit of parallel requests.

Chrome limit

Loading tree
Source : blog.bluetriangle.com

Because of that, I need to prioritize my resources. In other words, I need the font & the pizza picture downloaded before other low-priority resources.

loading tree

Solving Visual problem 2 & 3

This problem is easy to fix, all I need is to preload (like we did earlier) the font, and the pizza picture.

We start with the font, but first, I need to know the name of the font i used on the first page (DayburyRegular), after that, inside the <head> of my index.html I need to add :

<!-- index.html -->
<link
  rel="preload"
  href="[YourPath]/DayburyRegular.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>
Enter fullscreen mode Exit fullscreen mode

The same process goes for the pizza picture, so inside the <head> of index.html I also need to add :

<!-- index.html -->
<link rel="preload" href="[YourPath]/pizza.webp" as="image" />
Enter fullscreen mode Exit fullscreen mode

It should look like this :

index.html

⚠️ Note :

Go to the CSS file containing your @font-face :

fonts.css

If the path of your font is different from the path you put on your index.html, change it to the same as index.html, Even if they lead to the same file, they must be written the same.

fonts.css

Otherwise the browser will re-download the font (and i don't know why).

Fonts

Now, I created a file named pre-fonts.scss, and inside it, I transferred my font from its old SCSS file to this newly created one.

fonts

After that, I imported pre-fonts.scss into combined.scss, so its preloads with the other styles.

combined.css

Result after solving Visual problem 2 & 3

Download tree

Now, the font and pizza picture won't get late to the party again.

β„Ή Note : The website after the fix


Explaining Visual problem 4

Like I explained on the video, when we lazy load resources that are not visible on the viewport, the other resources (that are visible) will load faster, because now we are downloading for example 10 images, instead of 100.

ℹ️ TL;DR : We only need to load pictures that are actually needed and visible.

Solving Visual problem 4

The solution is to implement Lazy Loading. There are many methods and techniques, but my personal choice is to go with lazysizes By Alexander Farkas.


But before, because we have all our critical CSS inside combined.css, let's take a look at my styles.css

styles.css

Poor file, looks so empty.
Since all the CSS inside this file are noncritical, why not just lazy load them with the same method I used when I Lazy loaded LightGallery.

So, inside angular.json, under styles[] I need to modify :

"styles": [
    {
      "src/styles.scss"
    }
]
Enter fullscreen mode Exit fullscreen mode

to

"styles": [
    {
      "input": "src/styles.scss",
      "inject": false,
      "bundleName": "styles"
    }
]
Enter fullscreen mode Exit fullscreen mode

angular.json

Now, I need to to load styles.css after all the resources finished downloading ( like I did when I Lazy loaded LightGallery)

So, inside main.component.ts, under ngAfterContentInit() and within window.onload I added :

var link = document.createElement("link")
link.rel = "stylesheet"
link.type = "text/css"
link.href = "styles.css"
document.head.appendChild(link)
Enter fullscreen mode Exit fullscreen mode

main.ts


Now let's get back to the problem. I would be very pleased to explain to you how I lazy loaded my website, but this post is already long enough, plus, that's not the main subject for today. So I'll jump directly to the results, however, I'm planning on writing a detailed step by step guide, and link it right here.

Result after solving Visual problem 4

result

Lighthouse

Lighthouse

Mission accomplished βœ…

ℹ️ Note : While fixing my visual problems, all the invisible problems are solved too :

You can visit the website after this last step.

The Take

  • When your website starts printing, make sure that all of your critical CSS is ready.

  • If necessary, preload some of your resources (the main ones), for a better UX (like we did with the pizza picture & the font).

  • Always, like always lazy load your images, and if possible, lazy load your uncritical JS & CSS.

  • Try to install the minimum of third-party libraries, and uninstall the unused ones.

  • Always open your dev-console, and analyze and prioritize the order of your resources.


You may find mistakes related to my English, maybe I was wrong about some of the things I said, or the way I explained them. Your suggestions and advices are always more than welcome.

ℹ️ Note : I tried to be beginner friendly as possible, which is why you find me a little repetitive and boring in some cases.

Top comments (5)

Collapse
 
zohar1000 profile image
Zohar Sabari

That's a tremendous work, it was facsinating to read. I saved the link, kudos!

Collapse
 
alaindet profile image
Alain D'Ettorre

Amazing content, thank you very much

Collapse
 
retry2z profile image
Hristiyan Hristov

Can you show us the mobile tests cz there I face more problem in optimizing already at 100 in desktop.

Collapse
 
brihoum profile image
Abdjalil Brihoum

Lighthouse on mobile simulate a mid-tier mobile (Moto G4), a 6 years old phone with 2gb of ram & a Snapdragon 617.

And as we know, Websites under Angular renders in the client side, means rendering pages directly in the browser using JavaScript. And because the simulated phone is mid-tier, it takes time to download, execute, and render the page. The solution for your problem is to implement server side rendering. Google Angular universal to know more.

Collapse
 
retry2z profile image
Hristiyan Hristov

Yea got SSR even more split desktop and mobile versions to clean code what I send to user I make very nice interesting approach to handle this. And still I see around 85+ for performance and thinking whaaat I can do more.

We want your help! Become a Tag Moderator.
Check out this survey and help us moderate our community by becoming a tag moderator here at DEV.