Web performance optimization has always been critical for websites and apps. Whether providing services, entertainment or information, activities are impacted when performance is slow or inconsistent. Today’s sites have greater sophistication and more capabilities than ever before. Users expect all of this to work seamlessly, but if it doesn’t, in a competitive marketplace, there are plenty of alternative options. So what can you do to ensure your web performance stays at the cutting edge?
What is web performance?
Web performance is determined by several factors. Let’s consider some of the key metrics.
- Load time. Probably the most basic metric for web performance, load time measures the delay between an initial page request and the content being fully displayed in the browser. It is often reckoned to be an important factor in search engine rankings.
- First contentful paint (FCP). Getting something up on the screen fast is an important indicator of progress. FCP is a measure of the time between initial loading and some part of the page content being rendered.
- Time to interactive (TTI). TTI measures the time between FCP and the page’s becoming interactive. A page can look completely loaded but it may take longer for controls to become fully interactive. TTI is therefore a measure of responsivity.
- Cumulative Layout Shift (CLS). This is a measure of the point at which a page layout becomes stable. Page layouts shuffling around while loading is a common user irritation, particularly with interactive elements. So CLS is a key usability metric.
There are other metrics we may consider, but this trio of speed, responsivity and usability provides a good background for considering optimization. With these metrics in mind, let’s consider the main techniques for improving performance.
Bundlers like Webpack modularize code and use dependency graphs to determine when and where assets are needed. This gives the option to use dynamic imports, which respond to function calls within modules. Bundle splitting is a complementary approach to code splitting though less granular. It involves configuration-led separation rather than client-side loading decisions. A common use case is to separate third-party dependencies from your application code. That way, each need only be loaded when there are changes.
Code splitting also enables tree shaking, a server-side analysis which removes unused code from your production bundle. Most bundlers now do this by default, reducing code size without changing behaviour.
Lazy loading strategies separate the critical from the non-critical assets and only serve the latter when needed. It is a technique which is closely tied to code splitting.
Clearly, a central question for any lazy loading strategy is the determination of what its triggers are. This can be as simple as specifying
loading="lazy" in your
img tags, which will defer loading images until they are required. You can also configure lazy loading of other assets by directly watching for elements coming into view. The Intersection Observer API is a typical example. It asynchronously monitors the intersection of a specified element with the viewport, at which point you can trigger your asset requests.
Whereas lazy loading defers actions until needed, preloading gets them done in advance. Some assets are not required immediately on page load, but will almost certainly be needed at some point. In such cases, it can be valuable to preload them. This is easily achieved using the
<code><link rel="preload" as="script" href="myscript.js></code>">
Since HTTP/2, you can also use server push to send assets to the client before they are directly requested. Of course, care is needed with these techniques to avoid bandwidth competition with other resources. Preloading should be used sparingly since modern browsers already optimize the load order of resources.
Image optimization is one of the best-known forms of web performance improvement. Yet it can be too easily overlooked by designers who may have become complacent about network performance or know too little about image formats. Before anything else, consider whether an image is needed at all. CSS3 now has sophisticated effects like shadows and gradients that designers formerly used GIFs or PNGs to achieve. For more complex vector imagery, SVG is also a good idea. Not only are these options more lightweight in terms of bytes, but they also usually give better quality, by adjusting to any required resolution.
For photographic imagery, the compression level is key. Designers must balance image quality against size. Good judgement and a careful eye are needed, as well as an awareness of the variations in presentation on different devices and resolutions. Also, make sure you are using the best format for the type of imagery. Photographic content has traditionally been rendered as JPEGs, but these days, WebP and AVIF offer better-quality reproductions with smaller file sizes. Compatibility can be an issue though – all modern browsers now support WebP, but AVIF is yet to be properly implemented on some.
Minification and Compression
Minification removes excess whitespace, comments and redundant block delimiters. Even the removal of newline characters can have a measurable impact on load times. Minification is easily achieved – you’ll find a range of command-line minification tools that you can work into your toolchain, such as JSMin and UglifyJS. UglifyJS can also change things like variable names to save even more space, as well as obfuscate code to prevent re-use.
Another option is gzip compression to reduce the size of text-based assets. Typically this is enabled as part of your web server configuration. For example, with Apache, you can use the mod deflate module.
Finally, let’s not forget caching. Caching can improve web performance by lowering server load and reducing network transfer loads. Caching can be both server-side and browser-based. Your browser web caches store recently requested assets to avoid downloading them anew each time they are requested. Cached items are refreshed either after they have expired, when the last-modified header shows they are stale or in response to a manual refresh by the user. Frequently changing or sensitive resources may also be set as ‘no-cache’ to avoid out-of-date information.
Though we’ve covered the basics, optimization doesn’t end here. If you’re keen to explore the topic further, you may also like to look at more server-side technologies like database tuning and infrastructure config, including using CDNs to leverage techniques like distributed cache.