How I improved light house score of Angular web app

·

3 min read

A bit about myself at first. I have been a React developer since the beginning of my web development journey. When I started my first full-time job as a Frontend Developer in a company that primarily used Angular, I did not know Angular.
I am still not confident in my Angular skill. Please read the blog keeping this in mind.

The initial performance score (mobile device) was 30-40. This caused SEO-related problems. Improving the performance was our top priority.

While diagnosing the web app, we witnessed that a huge bundle was there along with the lazy loaded bundle (route bundle).

Spoiler alert - the huge bundle was the shared module which contains all the shared components (some components were used in a single place), directives, and pipes. Lesson learned - premature abstraction is almost always bad. E.g. we should put the component/directive/pipe in the shared module only when it is used in more than one place.

My research on the angular module and its effect on bundle size.

  1. Modules are the high-level building blocks of Angular projects.

  2. Ng Modules are mainly used to register components, directives, and pipes.

  3. There are two types of Ng Modules -

    • Eagerly loaded - modules are always bundled in the main bundle.

    • Lazy-loaded - modules have their own bundle.

  4. The home page module is always eagerly loaded.

  5. Eagerly loaded modules are always defined in the App Modules import array or in a transitive module.

  6. Shared Module

    • If a shared module is only imported in a lazy-loaded module, it is bundled in the lazy bundle.

    • If the shared module is imported in both, an eagerly loaded module and a lazy-loaded module it is only bundled in the main bundle.

    • If the shared module is imported in two lazy-loaded modules (but not in an eagerly loaded module) a common bundle is generated for the shared code and loaded together with the first lazy-loaded module.

  7. Note related to importing module -

    • If you import a module, all components are bundled, even if not all are used.

    • The smaller the modules the better Angular can optimize the bundles.

The problem in the architecture of the web app

By now you may have already guessed the culprit. Yes, the huge shared module was imported into the home page module. Not even 10% of the stuff from the shared module was being used on the home page.

Other problems

We were not taking advantage of the image srcset attribute which allows us to use multiple sources of images of different sizes and use the image source according to the device size. For example, we can use high-quality images for desktop and relatively smaller images for mobile. This solution is not angular-specific, meaning you can use it for all web projects.

This blog explains image optimization in greater detail.

There was CLS(Cumulative Layout Shift) issue which mostly occurs when the image takes time to load and suddenly flashes once it is loaded causing the layout to shift. This can be easily fixed by giving the height and width attribute to the img tag.

Solution

It is very tricky to design the architecture of the module which will sustain over time. One rule I follow is to keep the shared module as lean as possible. Break the shared module into smaller modules and make sure that there isn't much-unused stuff from the shared module in the module imported.

We were using Angular 12, maybe tree shaking is improved in a later version of Angular. That will solve this problem completely without much effort from the developer.

This improved the performance score. It was finally around 90.