My journey on the current project started in October 2022 and this one is quite challenging. One of these challenges was the performance optimization area. Today, I would like to shine some light on this topic and share my experience. We will start from the basic concepts, look into the Web Vitals and core metrics for measuring the page loading, briefly walk through the topic of how a browser makes pixels on the screen from your code, and talk about optimization techniques.
First things first - let's explore the reasons why we should care about loading optimization:
The first thing to discuss is Web Vitals. It's a pack of three metrics that the Google team formed. They are considered to be the most important metrics and vectors for improvement in the performance landscape. These metric values are fair only for the 75th percentile of users. It means that most of your users meet these metrics. The first one - the Largest Contentful Paint. In our topic, it is the most important one, and we will explore it in detail a little bit later. The Interaction to Next Paint is a metric showing how fast your page responds to a user's actions - click with the mouse, tap on the touchscreen, press a key on a keyboard or onscreen keyboard. This is the time when the browser begins processing event handlers in response to that action. And last but not least is the Cumulative Layout Shift. This metric shows how is unstable your UI - how many element shifts you have. The values are calculated with a special formula, but we are not going to reveal it.
Okay, let's dive into the LCP metric and why it matters. It reports the render time of the largest image or text block visible in the viewport, relative to when the user first navigated to the page. To provide a good user experience, sites should strive to have the LCP of 2.5 seconds or less. LCP includes any unload time from the previous page, connection setup time, redirect time, and Time To First Byte, which can all be significant when measured in the field and can lead to differences between field and lab measurements. Let's see some examples of LCP calculation. You can see the loading page, and most of the time the LCP element is considered a big text block. However, after the image loading, the LCP element changed to the image.
The second example is pretty much the same - while the future LCP elements are loading, some other text blocks are considered the largest on the page.
In these examples, there are two similar situations - the LCP element loaded faster than other elements.
The Critical Rendering Path, or CRP, is the sequence of steps the browser goes through to convert the code of a web page into actual pixels on the screen. Understanding the CRP is essential for improving a page's load speed and overall performance.
So, let's break it down further:
Any slowdown or blockage in these steps can delay the rendering of the page, causing a poor user experience. That's why it's crucial to optimize the CRP;
Now, we will look into the strategies and techniques, that can help us optimize our application. These are not all techniques in the world - just a part of them.
We are starting from the cache. So, what exactly is caching? Think of caching as a high-speed memory storing temporary data that you're likely to need again. By storing this information in a cache - it drastically reduces the need to re-fetch the data from its source or reprocess a computation, hence saving precious time. There are several types of caching:
How do we implement caching? We begin by identifying which assets or computations are costly and don't change frequently, making them ideal candidates for caching. Then, we choose the caching technique best suited based on our findings.
Let's switch gears for a moment and discuss another effective strategy for speeding up web application performance - the use of a Content Delivery Network, commonly known as a CDN.
So, what exactly is a CDN? At its simplest, a CDN is a network of servers spread out geographically, designed to deliver web content to users more quickly and efficiently. The content that a CDN caches usually includes images, stylesheets, JavaScript files, and more.
So you might ask, how does a CDN actually work? The principle behind it is quite straightforward: the CDN will deliver the content from the server closest to the end user, significantly reducing the response time.
Imagine you have users accessing your application from different parts of the world, and your server is somewhere in the US, and your application is accessible from any part of the world. Without a CDN, every request has to travel all the way to the US. But with a CDN, if a user is accessing from Spain, their request will be served by a server closest to Spain, drastically reducing the latency.
Normally, when a browser processes a webpage, it goes line by line, executing each function or loading every resource as it comes across them. This method is what we refer to as 'synchronous' because everything is happening in a specified order, one after the other.
Asynchronous, on the other hand, fundamentally breaks this chain. It allows the browser to continue processing other elements of your webpage while it has kicked off resource-intensive operations, like fetching data from an API or loading images. Remember CRP? Actually, JavaScript is a render-blocking resource as a CSS, and this is why it's important to load JavaScript asynchronously. Here we have several examples of how can you connect our scripts to the webpage.
As a default behavior, our script interrupts the HTML parsing, hence the DOM construction. And if you remember the CRP process, the DOM construction must be completed if we want to get actual on-screen pixels - we need to do it as fast as we can to provide a good user experience. A browser meets the script and starts to load it, and right after it has been loaded - executes. So, for this problem, we have two solutions - async
and defer
attributes. As you can see, the script with both attributes loads asynchronously, but there is a pitfall - async
we still interrupt our HTML parsing with script execution, so for this purpose, it’s better to use defer
because the script will be executed only after the HTML parsing is finished.
Moving on to another essential aspect of web performance - the optimal formatting and loading of images and videos.
Multimedia content like images and videos can significantly impact the loading speed of your application due to its usually large file size. Therefore, optimizing these elements is crucial to enhance speed and performance. For images, we need to consider the most suitable format for a particular case. It can be JPEG, PNG, or SVG. For any media file, use compression tools to reduce file sizes without losing too much quality, or use files with less size. Do not forget to use lazy loading - it helps to not load files until they appear on the user's viewport.
Minification is a simple and effective method to increase your web application performance without affecting its functionality and it must be included as a part of an application build process.
Minification refers to the process of removing unnecessary or redundant data from your code files without affecting their functionality. This can include white spaces, line breaks, comments, and even variable names in some cases. So, why is minification so important? The primary benefit of minification is decreasing your file sizes. Smaller files mean less data needs to be transferred over the network, leading to quicker load times and less data usage for the end user. If your files are small, the browser can parse and compile them more quickly, improving the overall performance. In addition to this, it helps to reduce network bandwidth usage.
An important factor impacting the performance of your web application is the size of your JavaScript bundles. Your final bundle consists of your code and application dependencies/packages. And if you do not manage them properly, in the future, it becomes a big problem. This is an important part of the optimization of loading time. Here we have some techniques, which can help to reduce a bundle and helps to manage application dependencies:
import
and export
, not require
;Now, after all of the theory, I will present my personal experience in this field. For example, we will take a look at a project where I'm currently working on. When I entered the project, it was about October 2022, the LCP was 3.3 seconds at the 75th percentile. As we discussed earlier, it's not so good. Then, our management team set a goal to be in the green zone for LCP - it's up to 2.5 seconds. I took this task and started analyzing what was going on. The first thing that I revealed was the enormous size of a bundle - almost 8 MB. The reason was simple - there was no code splitting and lazy loading, it was just one big JS file with all project code. Only our project code was 6 MB and 2 MB for dependencies. After the optimizations, it was really hard, I reduced the initial script to 300 KB. What did we do with dependencies? We migrated some packages to more lightweight ones, some of them were deleted, and another part was removed from the initial script due to code splitting and tree shaking. So, it was reduced to 1.1 MB. Plus to this, we cached all static resources for several weeks, due to our releases and enhanced our geographical distribution. And one more thing, we did some code optimizations to reduce long tasks that block our main thread. Finally, after a long way, we got 1.9-second LCP for the 75th percentile of users.
Q: If I have a small project, that has already started, how much effort should I put into the possible optimizations bailouts in the future?
A: Even for small projects, it's beneficial to set a good foundation for possible optimizations in the future. However, the level of effort would depend on your project's complexity, business domain, and future growth projections. If there's potential for your project to scale or complex features to be added, it's better to consider optimization early on. Conversely, if the project is expected to remain small and simple, you might choose to apply basic optimization practices without heavily investing time upfront.
Q: How to sell the idea of optimization to a business?
A: You can emphasize the direct benefits to the business. For instance, website optimization can lead to faster loading times, which can enhance user satisfaction and experience, reduce bounce rates, and potentially increase conversions and business profits. Furthermore, it's beneficial to the business's SEO efforts as search engines factor in page load speed in their ranking algorithms. In addition to this, it's better to provide measurable results rather than hypothetical ones.
Q: How small my bundles should be to achieve code splitting benefits?
A: There isn't a one-size-fits-all answer to this as it can depend largely on your application's needs and structure. However, a general rule of thumb provided by the Webpack documentation suggests aiming for an initial bundle split point of under 244KB (compressed). But remember, the ultimate goal is to only load what's required at any given time, improving the initial page load.
Q: Which else tools can be helpful for analyzing my current statement?
A: I have mentioned two of them: Webpuck Bundle Analyzer and Lighthouse. But it's good to know that Chrome DevTools can also be useful. For a performance tab that gives you more insight into what's happening when your site loads. But use it only for investigations, not for the source of truth - real data should be collected from real users.
Today we have discussed a little part of the big picture of optimizations. We have seen some important metrics and ways to enhance the user experience and loading application. In conclusion to my speech, I would like to give some advice or some kind of a roadmap if you want to improve the loading speed of your application:
Remember, optimizing application load speed is an ongoing process and requires continuous monitoring.