Improve LCP on angular app
Intro
Things that slow a web application are:
- requests
- Ressources loading (JS / CSS / images)
- API calls
- Files size
- JS / CSS files
- Medias (images / videos)
- Rendering
- Javascript execution
- CSS parsing
- DOM construction
In this listing there is some things we can do to improve the performance of our application, so improve the LCP.
In this article I will assume the application is already doing SSR (Server Side Rendering).
Requests
Ressources loading
CSS inline
The first thing that is pretty easy to do on angular is to make the css loading inline. This mean the css will be in the DOM, so when the server is sending the HTML to the client, the CSS is already there. So the browser will not have to make a new request to get the CSS file(s).
To do this, you can use the inlineCritical
option in angular.json
file.
{
"configurations": {
"production": {
"optimization": {
"styles": {
"inlineCritical": true // <-- add this line
}
}
}
}
}
Lazy loading
Some resources are not needed for the first render. Because they are not visible, or not mandatory.
For example, picture below the viewport. Or some widget from a third party.
These resources can be lazy loaded.
In angular there is multiple ways to do it.
There is a @defer
instruction that can be used in template. You can learn more about it here.
NB: Before angular 19.2 @defer
can block the stable
state of the application and cause unintended behavior.
You can see some details on an angular repo issue
For the pictures you can use the ngSrc
directive. This will lazy load the image when it is in the viewport.
Wait for the LCP to be done
If you want to run something after the LCP is done, you can wait for the application to be stable. It not exactly the LCP, but it is close enough.
This would be done like this :
constructor(
private appRef: ApplicationRef,
private cd: ChangeDetectorRef
) {}
ngOnInit() {
this.appRef.isStable.pipe( first((isStable) => isStable) ).subscribe((isStable) => {
// do something after the LCP is done
this.cd.markForCheck();
});
}
You will need to mark the component for check, because the isStable
observable is ran outside zone.js.
When the is stable is trigger you can run your third party code or whatever you want.
API calls
- Caching with redis
- Ran SSR
- Browser state
- limit the number of API calls
- if you have multiple API calls maybe merge them into one by changing the backend
- if things don’t need to be updated frequently, it’s not necessary to make it changeable in an admin panel, they could be hardcoded.
Files size
JS
- Making smaller bundle
- Standalone components
- Lazyloaded routes
- defer not needed components
- import/provide only what you need
- lighthouse bundle analyze
When the application load all the files if the javascript files are heavy it will be slower. So we need to have to make them as small as possible. In angular there is multiple steps.
When angular is building the application, it create bundles files. These files are containing all of the javascript code needed to run the application.
If you don’t do anything, all your components and module will be included.
In the recents version of angular, there is a new feature called standalone components
. This is a way to create components that are not part of a module. This means that you can use them without having to import the module.
In addition to that, if you lazy load your routes, this means that when you load your application, the bundle will only contain the route components and everything that the component needs.
Others routes components will be loaded when the user navigate to the route.
In the standalone components, you need to import only the things you need. For example if you need a Button from a library, if it’s possible you should only import the ButtonComponent and not a full module that would include other things that you won’t necessarily use.
An other way to reduce the size of the bundle is to use the defer
instruction.
This will not include the defered component in the initial bundle, so the additional bundles will be loaded when the defer
is triggered.
For example if you have a third party that is loading a lot of things. You can move all the code that trigger the third party to a new component.
Then you can use the defer
instruction to load the component later. So all the code won’t be triggered. Also if you have a component that use heavy library, and this component is not mandatory for the initial page, defering it will make a much smaller bundle.
Sometime also removing or replacing some libraries can help. For example if you have momentjs that is use only one time in the component, maybe using an alternative or using the native javascript can help.
To determing what is included in the bundle you can run the lighthouse using chrome developer tools.
Then there is a treemap button that will show you the size of each file in the bundle.
If the name are not clear, you can change you angular.json
configuration locally to disable some options.
You can put it in the production configuration, and then run you app in the production locally to run the lighthouse.
Just make sure to not push it to production, keep it only locally during you tests.
{
"production": {
"namedChunks": true,
"sourceMap": true,
"outputHashing": "none"
}
}
Media
The size of the medias are also important. If you can change the size of the image according of the height and width of the image on you page. Also you can compress the image. There is websites that work well for that like tinypng. You can also use the webp format. This is a new format that is usually better than the jpeg or png format. For the videos the equivalent format is webm.
Rendering
By removing unused CSS and JS, we can reduce the amount of work the browser has to do to render the page. Also by reducing all the files as explained above, it’s less code for the browser to interpret, so the rendering will be faster.