How to Optimize the Loading of Your Web Application

Written by amirankou | Published 2024/03/25
Tech Story Tags: front-end-development | core-web-vitals | loading-time | lazy-loading | largest-contentful-paint-lcp | critical-rendering-path-crp | web-optimization | asynchronous-loading

TLDRImproved load time enhances the user experience, keeps them engaged, and makes them less prone to shifting to another site.via the TL;DR App

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.

Importance of Application Loading Speed in User Experience

First things first - let's explore the reasons why we should care about loading optimization:

  • Improved load time enhances the user experience, keeps them engaged, and makes them less prone to shifting to another site. There are a lot of similar applications, and if you want to engage a user in your application, you have to have a fast-loaded application;
  • Sites with faster loading times have higher conversion rates, impacting sales and registrations in e-commerce and other online businesses. Some companies researched how loading speed impacts sales for a business, and the results were quite impressive. Even a one-second delay can result in a 7% reduction in conversions, and 40% of users will abandon a website if it takes more than three seconds to load;
  • Search engines like Google consider page load speed as a ranking factor in their algorithms, affecting the site visibility in search engine results. You have to be fast if you want to be at the top of Google search. This is one of the many factors impacting the website rank.

Basic Concepts

Overview of Web Vitals

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.

Understanding Largest Contentful Paint (LCP) and why it matters

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.

Quick Look into the Critical Rendering Path (CRP)

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:

  1. When a user opens a web page, the browser starts by fetching the HTML content;
  2. The HTML is then parsed into a tree-like structure called the Document Object Model (DOM). This represents the structure of the webpage;
  3. Parallel to this, the browser fetches and parses style information, which includes CSS linked within the HTML, building the CSS Object Model (CSSOM) - which is a map of the site's visual rules;
  4. Once both DOM and CSSOM are built, the browser combines them into a Render Tree. This structure only includes visible elements and their styles;
  5. Based on the Render Tree, the browser then performs the Layout process, also known as reflow, calculating where each element should appear on the screen;
  6. The final step is the Painting, where the browser turns each node in the Render Tree into actual on-screen pixels;

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;

Strategies & Techniques

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.

Discussion of different caching methods

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:

  • Browser Caching. This stores static files, like CSS, JavaScript, and images, right in the user's browser;
  • Server-Side Caching. This involves serving pre-generated responses to identical API requests straight from the cache rather than executing database queries each time;
  • Database Caching. This involves temporarily storing the results of a database query so that if identical queries are made, the result can be quickly retrieved from the cache rather than executing the query all over again;

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.

Using CDN for faster content delivery

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.

Explaining asynchronous loading

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.

Image and Video Optimal Formatting and Loading

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.

Importance of Minifying CSS, JavaScript, and HTML

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.

Bundle reduction and dependencies management

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:

  1. Code Splitting. Here, you divide your code into smaller chunks which are then loaded on demand or in parallel. By only loading the minimum necessary code for a particular page, you can drastically speed up the initial load time. This technique is also called lazy loading;
  2. Tree shaking. This technique helps you eliminate dead or unused codes from the final bundle. Modern bundlers offer built-in support for tree shaking. However, it mainly works with ES6 module syntax — so make sure you’re using import and export, not require;
  3. Only import what you need. Try to only import the functions or components you need, rather than the entire library;
  4. Regularly update dependencies. Updates often include optimizations and the removal of unnecessary or deprecated features;
  5. Audit your dependencies. Use tools such as Webpack Bundle Analyzer to determine which dependencies are adding significant weight to your bundles, and consider whether they are truly necessary;
  6. Use lighter alternatives. Use lighter alternatives of the libraries you use. For example, Preact is a lighter alternative to React, and day.js is a compressed version of moment.js, which is much bigger. For smaller projects or individual components, this approach can significantly reduce bundle size;
  7. Make sure that there are no duplicate dependencies in your bundle.

My personal experience and results

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.

Possible questions you might ask

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.

Conclusions

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:

  1. Analyze your project. The Lighthouse extension and Webpuck bundler analyzer can help you;
  2. Apply the most suitable techniques for your case. We discussed some of them but the world of optimizations does not only consist of them;
  3. Continuously measuring. Store the core metrics for your project and regularly check them. It's very important to collect them from real users rather than local testing.

Remember, optimizing application load speed is an ongoing process and requires continuous monitoring.


Written by amirankou | Passionate about Front-End development
Published by HackerNoon on 2024/03/25