Tips for improving web performance

Viktoriia Poligenko
Devstark
Published in
16 min readJun 24, 2024

The performance of web applications directly impacts the user experience. Slow-loading pages and apps can frustrate users, leading them to abandon your product (popular opinion is that 50% of users close the page after 3 seconds of loading). Additionally, web performance plays a significant role in search engine rankings. Google, for instance, considers site speed in its algorithms, affecting your visibility online. Faster loading times can also boost conversions and revenue.

To enhance a web page or web app’s performance and improve user experience, we need to optimize it. Optimizing a web application can involve many techniques. This article will focus on several reasonably simple ways to improve your web page or app’s performance. We’ll discuss using Web Open Font Format 2, CSS features, appropriate resolution and media formats, compression, third-party scripts, and tools for bundling, tree shaking, minification, and code splitting to your advantage so your web pages and applications can be as fast as possible.

Using the Web Open Font Format 2

The primary purpose of most pages on the Internet is to provide access to content or some kind of interaction with content. This content or interaction can differ, but most include a text component or directly represent text, like this article.

Working with typography on the web is one of the primary things that allows you to make text readable, formatted, and user-friendly. The stylistic design of the text is taken over by CSS, where we can control the indentation, size, etc.

Using text stylization alone is only sometimes sufficient. In most cases, web application designers select specific font families to make text more expressive and visual. For example, web versions of print publications prefer serif fonts, emphasizing their focus on tradition and corresponding to our visual expectations of print publications.

One of the main font formats is TrueType, which you can find on most pages on the Internet, regardless of the font family that determines its appearance. Like any information delivered through networks, fonts will be transferred to the client from the server if such a family is not in the client’s set. This circumstance leads us to the need to monitor the size of memory occupied by fonts because one of the key points in working with optimization is optimizing the delivery of content over the network, and here, less is better.

Along with TrueType fonts, there are many other formats. When looking for possible alternatives to reduce memory footprint, you can find a specialized format for the web, Web Open Font Format 2, which appeared in 2009 but did not have widespread browser support until 2018.

Currently, Web Open Font Format 2 is supported by all browsers except Opera Mini. The full picture is here: https://caniuse.com/woff2. Such broad support makes it entirely safe for use. Now, let’s see what this format can offer us when optimizing web pages and applications.

For a clear example, we will turn to Google Fonts, where we can download the first font we come across, in our case, Roboto. A font file with a typical style takes up 168KB. Let’s convert it to WOFF2 format and see what changes. To do this, I will use the WOFF2 console utility, which you can find in almost any package manager.

The results are impressive:

WOFF2 made it possible to compress the original font by 2.5 times and allowed us to declare apparent advantages in its use. There is no visual difference between TrueType and Web Open Font Format 2 in web pages, but there is a difference in size, and it is significant. By using an optimized font format for the web, we are guaranteed to improve application performance in terms of typography delivery.

You can also automatically convert fonts into WOFF2 directly in Google Fonts. Inside Google Fonts, select your preferred font and its weight, and press “Get embed code” in the top right corner. Then, from the sidebar, copy the source code URL.

Paste the link in the search bar and hit “Enter.” On the new page, find the font language you need. Copy the src URL and paste it in the search bar again.

The download will start automatically.

Using CSS Features

Whatever issue you might come across as a developer, you are likely not the first one to encounter this problem. Someone has already solved many similar problems, and they are all in the public domain. Many of these solutions are packaged in libraries that are easy to connect to a project and use.

This speeds up development, but it also adds scripts that have to be transferred to the client for subsequent execution. As a result, as often happens, we use only a tiny part of what these scripts are capable of.

For example, creating carousels with media content is one of the most common tasks. The solution for this has existed for a long time and is very popular — swiper.js. Just look at the download statistics for this package from NPM to understand how popular this solution is.

More than 2 million downloads per week.

The library script can be connected via CDN, but let’s look at the load this library will bring.

Using the curl console utility, we can find information on the downloaded package by its URL specified in the documentation. The volume of downloaded data is—177.358 KB. Using gzip will improve the situation significantly, and the data size will be—48.116 KB.

Let’s see if CSS can solve such problems, and yes, it definitely can.

Examples of such implementation can be found at https://gui-challenges.web.app/media-scroller/dist/. The source code is here: https://github.com/argyleink/gui-challenges/blob/main/media-scroller/style.css.

Even though the CSS in the above repository contains more than one carousel implementation, we can still compare its size and see that the original data size is 3,471 KB, and the compressed one is only 1,195 KB. It’s a solution that is 40 times lighter than the standard one! There may be more than one such library in a project — swipes, parallax effects, scrolling effects, etc.

It’s clear that CSS can solve many problems that, in practice, are usually solved by JavaScript, positively affecting application optimization in terms of delivering files over the network. However, the main idea behind this feature is that if an animation can be done in CSS, then it’s favorable to do so in CSS. If it is too complicated to implement in CSS, then popular solutions should be used. This solution will only work well for simple use cases. If you need a full-blown slider with scroll buttons and pagination, then this solution would not be a good fit.

Using AVIF

Images and text are the bulk of the content, if not the primary content, that users can access when using websites or web applications. On average, the volume of images can reach up to 50% of the volume of the entire application or web page transmitted over the network. As with any other file, an image can come in many formats, which differ in the degree of compression and the quality of the image itself after compression.

Using JPEG allows you to display good-quality images, but this format is not always optimal for the web. At some point, the WebP format appeared, designed to optimize images for the web. WebP format works great and is widely supported by all browsers. Check out the full browser support of the WebP format here: https://caniuse.com/webp.

WebP remains a good solution for web applications and pages, but it’s not the only one. It makes sense to pay attention to Aviff, which only this year began to be supported by the Edge browser and, along with it, received full support for all browsers of current versions.

To evaluate this format's capabilities, you can use this service to work with images: https://squoosh.app/. It will demonstrate the format's leading quality.

For the demonstration, you will need a JPEG image.

The image presented above takes up to 2.02 MB of free space, which is obscenely high for today’s fast web applications.

If we compress this image using WebP, it will have an apparent positive effect. The memory occupied by the image has decreased by 84% and began to occupy 313 KB—this is an excellent result!

AVIFF shows even better results. The image is compressed by 96% and takes up 84.3 KB of memory, which is 12% better than WebP.

The AVIF format also supports HDR, which is great for websites with tough guidelines for media files, such as galleries, where image brightness and color are an important factor.

Use of media content of the appropriate resolution

As mentioned earlier, media information is one of the main content components displayed on web pages. The vast majority of this media info will be comprised of images. The image format affects the compression ratio and the amount of memory the image takes up. Still, there are other ways to optimize the delivery of images over the network.

Most web pages and applications are designed to provide a consistent user experience across multiple devices. Different devices have different characteristics (platform, browser, screen). Adapting a web application or page to other devices, is an excellent opportunity to use HTML constructs to create responsive images. Don’t just adjust an existing image to a specific size, but use images of a resolution that will fit the screen of your device.

For working with images of different formats, we will need a container:

<picture>
<picture>
<! - For mobile devices →
<source media="(max-width: 768px)" srcset="image_size_414x896.avif">
<! - For tablets and notebooks →
<source media="(min-width: 769px) and (max-width: 1200px)" srcset="image_size_768x1024.avif">
<! - For desktop computers →
<source media="(min-width: 1201px)" srcset="image_size_1200x800.avif">
<! - Fallback image →
<img src="image.avif" alt="Fallback image">
</picture>

This way, we will use images that suit the specific screen and won’t send large images to small screens. Considering that mobile devices often use mobile Internet connections with low data transfer speeds, transferring an image of a suitable size will positively affect the speed of operation as it takes up less memory.

The presented design could be more efficient. It only shows the capabilities of the container itself. The rest depends on the purposes and devices for which the page or application is intended.

Also, it’s worth mentioning that an img tag has a srcset attribute. This attribute allows you to juggle different sizes depending on the screen size and pixel density, making this approach a bit more flexible.

<img
width="100%"
srcset="
/cdn-cgi/image/fit=contain,width=320/assets/hero.jpg 320w,
/cdn-cgi/image/fit=contain,width=640/assets/hero.jpg 640w,
/cdn-cgi/image/fit=contain,width=960/assets/hero.jpg 960w,
/cdn-cgi/image/fit=contain,width=1280/assets/hero.jpg 1280w,
/cdn-cgi/image/fit=contain,width=2560/assets/hero.jpg 2560w
"
src="/cdn-cgi/image/width=960/assets/hero.jpg"
/>

Using a CDN (Cloudflare or any other would do) would also be a good move in terms of image optimization. CDNs have an image proxy that allows you to receive an image of an appropriate size, not only depending on your device type but also considering your internet connection speed. CDNs offer a monthly payment for the service of converting files on the fly and cashing them. But there is also another option for those who don’t want to pay a monthly subscription fee. You can use Cloudflare API just once to receive all necessary sizes for all your images and save them on your server, then paste them into srcset, and voila! This approach would be a more budget-friendly option.

Using compression

After optimizing typography and images, it makes sense to use utilities to compress and decompress files and transfer them over the network to save bandwidth and reduce transfer time. Gzip (GNU zip) is suitable for these purposes — a utility developed based on the DEFLATE compression algorithm. Gzip has now become one of the most widely used file compression methods in the Unix environment and on the Internet in general. Using Gzip in web servers to compress web pages before serving them to browsers will reduce page load time.

For example, let’s take the most popular JavaScript library — JQuery. To illustrate the capabilities of gzip, we can neglect the options for loading the library itself (full, minified, etc.). In the example below, you can find two console commands, the result of which will be the output of the size of the transmitted information to a specific address

(  https://code.jquery.com/jquery-3.7.1.js  ).
echo "$( curl -s <https://code.jquery.com/jquery-3.7.1.js> | wc -c )"
285314
echo "$( curl -s <https://code.jquery.com/jquery-3.7.1.js> | gzip | wc -c )"
84055

In the first case, the size of the transmitted information is 285314 bytes without using GZip. In the second, we can already see how effective GZip is because the size of the same information has decreased to 84055 bytes.

As can be seen from the example, in this particular case, the volume of transmitted information was decreased by 3.4 times, which is quite impressive and will positively affect the application’s speed, especially for slow internet connections. On the client side, GZip is well-supported by all modern browsers, excluding Internet Explorer. You can check the full extent of GZip support by browsers via this link.

You can see Gzip in action in the following Nginx config file code:

http {
gzip on;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
server {
listen 80;
server_name example.com;
location / {
root /var/www/html;
sindex index.html;
}
}
}

# Gzip
- gzip on - turns Gzip compression on;
- gzip_comp_level 6 - assigns a level of compression. Recommended levels are 1–9;
- gzip_types - appoints MIME-types that will be compressed;
- gzip_min_length 256 - minimum length of the response for compression;
- gzip_proxied any - turns on compression for all proxy queries if needed;
- gzip_vary on - and the Vary title for proxy and cash.

Brotli is another great option for compressing files. It is a lossless compression algorithm that compresses data using a combination of a modern variant of the LZ77 algorithm, Huffman coding, and second-order context modeling. Its compression ratio is comparable to that of the best currently available general-purpose compression methods. Brotli is 15 to 25 percent more efficient than Gzip.

You can see Brotli in action in the following Nginx config file code:

http {
brotli on;
brotli_comp_level 6;
brotli_static on;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
server {
listen 80;
server_name example.com;
location / {
root /var/www/html;
index index.html;
}
}
}

# Brotli
- brotli on - turns Brotli on;
- brotli_comp_level 6 - level of compression, from 1 to 11 is recommended;
- brotli_static on - uses files that were compressed beforehand if they exist;
- brotli_types - assigns MIME-types.

Third-party scripts optimization

Of course, imagining any modern web application without interactivity is difficult. JavaScript, frameworks, and thousands of different libraries allow us to add interactivity to our apps. Practically all these tools are made by enthusiasts and distributed under a free license. Some are well written and optimized, some not so much.

Solving optimization problems using third-party scripts that aren’t well-written can be difficult. You must open an issue in a specific repository or get into the source code, solve the problem, and wait for the pull request to be approved. This process is not always fast, easy, or even possible. In addition to these problems, most web applications use third-party scripts for analytics, advertising, and tracking, which can block the main thread (when the browser processes user events and renders content). For example, Google Analytics can increase page load time by hundreds of milliseconds.

Optimizing third-party scripts is easier with libraries that allow you to move resource-intensive scripts to web workers from the main thread.

To solve this issue, you can consider using Partytown. This solution allows you to execute third-party code in a different thread, allowing to keep the resources of your main code.

Another script-related optimization move is using the async and defer attributes. These boolean attributes can be added to HTML script tags to enhance the loading and execution of JavaScript files. They help improve web page performance by stopping JavaScript from blocking the HTML parser. When JavaScript blocks the parser, the browser pauses HTML parsing to load and run the script. Using async or defer allows you to control how JavaScript files load and execute, letting the rest of the web page render without delay.

The defer attribute tells the browser to postpone running a script until the entire DOM is fully loaded. This way, the rest of the web page can be displayed without waiting for the script to run. When a script tag includes the defer attribute, the browser continues to parse the HTML and build the DOM while the script loads in the background.

The async attribute signals that a script is independent and can be loaded and executed without stopping the rest of the page. With the async attribute, the browser continues to parse the HTML and build the DOM while the script loads in the background. Once the script is ready, it runs immediately without waiting for other scripts or the DOM to be fully loaded.

Using service workers for caching can also be a powerful way to optimize your web page’s performance. A service worker intercepts network HTTP requests and uses a caching strategy to decide which resources to return to the browser. While both the service worker cache and the HTTP cache serve the same general purpose, the service worker cache provides more advanced features. It allows for precise control over what is cached and how caching is managed.

Using Bundlers

Bundling reduces the size of the code, simplifies managing dependencies, ensures the correct loading order of files, and improves caching. These optimizations result in a faster and smoother user experience, which is crucial because user devices’ capabilities and internet speeds vary.

Bundling makes it easy to combine multiple files into a single file. You can create CSS, JavaScript, and other bundles. Fewer files mean fewer HTTP requests, which can improve first-page load performance.

Minification

Minification is a method used in web development to reduce the size of source code files without affecting their functionality. It removes extra elements like white spaces, line breaks, comments, and block delimiters, as well as reduces the length of variables.

Before minification:

// a comment on code
const obj = {
prop1: "Foo",
prop2: "Bar",
getBaz: () => {
return "Baz";
},
};

After minification:


const o={p1:"Foo",p2:"Bar",gB:()=>"Baz"};

All comments, extra spaces, and line breaks are removed in the minified version, along with a useless return statement that was removed. The minified code is compressed into a single line to reduce file size. By minifying HTML, CSS, and JavaScript code, we can improve website loading speed and create smaller files, leading to faster downloads and web page rendering.

Tree shaking

Tree shaking is an optimization technique in modern JavaScript and TypeScript development to remove code that is not being used, also known as dead code elimination. This method visualizes your application and its dependencies as a tree structure. Each node represents a dependency that provides specific functionality for your app. In modern applications, these dependencies are included using static import statements like this:

// Import all the array utilities!
import arrayUtils from "array-utils";

When an app is new, it may have few dependencies and uses most, if not all, of them. However, as the app grows, more dependencies are added. Over time, you may not need some of these older dependencies, but they remain in the codebase. This results in the app shipping with unused JavaScript. Tree shaking solves this problem by having static import statements include only specific parts of ES6 modules:

// Import only some of the utilities!
import { unique, implode, explode } from "array-utils";

In development builds, all code is imported regardless of how it’s imported. However, in production builds, Webpack can be configured to remove exports from ES6 modules that were not explicitly imported, reducing the size of the production builds.

Code splitting

Code splitting is a technique to break down your application’s code into smaller chunks instead of serving it as a single, large bundle. This approach (automatically handled in most Frameworks) loads only the necessary chunks for a particular page, reducing the amount of code users must download and execute during their initial interaction with your application.

It’s a valuable technique that can reduce a page’s initial JavaScript payloads. It allows you to split a JavaScript bundle into two parts:

1. The JavaScript needed at page load, which can’t be loaded at any other time.

2. Remaining JavaScript that can be loaded at a later point, often when the user interacts with a given interactive element on the page.

Code splitting can be achieved using the dynamic import() syntax. Unlike <script> elements that request a given JavaScript resource during startup, dynamic import() makes a request for a JavaScript resource later during the page lifecycle.

In the example below, the generate-report.js module is downloaded, parsed, and executed only when a user clicks the “Generate Report” button. This ensures that the JavaScript resource responsible for generating the report is only involved with the page when it is most likely to be used:

document.getElementById('generateReportButton').addEventListener('click', async () => {
const { generateReport } = await import('/generate-report.js');
generateReport();
});

You can automate all the above-mentioned optimization techniques by using a bundler. A bundler uses various plugins and configurations to transform, compress, and optimize your code and assets. Vite, Webpack, Parcel, and Rollup are the most popular bundlers, though Rollup is mainly used for libraries.

Webpack is a mature, serious tool, but it’s a bit tough to tune. On the bright side, it’s very flexible and supports tree-shaking, chunks, and HMR. Vite is known for its intuitive and fast assembly and has all the necessary tools, including HMR. Parcel has an easy learning curve, is quick, and is multi-threaded, but since we mostly use Webpack and Vite, there are some cool plugins we’d like to share.

For Webpack modification, there is TerserWebpackPlugin and CssMinimizerWebpackPlugin. The first one deletes all unnecessary symbols from the JS code, and the second one minifies CSS. In terms of tree-shaking, the babel-loader deletes unused chunks of code from ES6 modules, and the actual tree-shaking is built into the bundler. SplitChunksPlugin is used to split code into chunks. Another great plugin for Webpack is — Webpack-bundle-analyzer. It will help you take a deeper look at what’s inside your bundle, find out what modules weigh the most and which ones got there by accident, and optimize it.

In our opinion, the minimum plugin pack for Vite consists of Vite-plugin-legacy, which automatically generates polyfills and transpiles code for web browsers by chunks, payload, etc., and Vite-plugin-dynamic-import which, as the name implies, is used for dynamic import to optimize loading. Just like Webpack, Vite has built-in tree-shaking.

These plugins are a must-have for Webpack and Vite. The rest are project-specific. Bundling assets streamlines the deployment process and delivers more efficient and optimized web applications to end-users.

Conclusion

Ensuring a smooth user experience is a key goal for every web app. Various methods exist to improve web application performance so you can create the best possible web app for your users. The techniques described in this article can help you improve your page’s speed, reduce server load, and perfect the overall user experience when interacting with your web application or page. Implement them wisely, and may your web app be the fastest!

These optimization tips were brought to you by Devstark’s frontend engineer, Evgenii Sergeev.

If you’d like to know more about Devstark, visit our website: www.devstark.com

--

--