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.
Strategies for web performance optimization
Code Splitting
Code splitting is simply breaking down code into independently loadable components. Today’s web apps often have a significant footprint in terms of CSS and Javascript. But not all of these assets are required for the initial page load and some may not be needed at all. By splitting code into bundles you can optimize with selective preloading or lazy loading.
Bundlers like Webpack modularise 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
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.
Using these techniques, lazy loading can be applied to images as well as media resources for video clips and animations. Javascript modules can similarly be deferred, as well as CSS. Not only does this help to speed up network performance, but it also prevents the browser from rendering elements before they are needed.
Preloading
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 preload
attribute. For example, to preload a Javascript module:
<code><link rel="preload" as="script" href="myscript.js>
</code>">
Code language: HTML, XML (xml)
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.
Optimizing Images
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
As well as image compression, textual assets like CSS and Javascript can be minified and compressed to improve network transfer rates. This involves a reversal of the usual thinking around code styling: when writing code, it is important to consider readability and maintenance. That means meaningful variable names, good use of whitespace and judicious use of comments. However, when it comes to serving these assets, you should jettison readability in favour of size.
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.
Caching
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.
A more recent innovation is the use of service workers. These are Javascript assets that sit between web servers and browsers, acting as a proxy that allows offline access and boosts page performance. They include a programmable Cache interface, which is entirely separate from the HTTP cache used directly by browsers. That means the service worker cache allows more fine-grain tuning for particular applications. The types of assets to be cached as well as their update strategies can be configured to offer a fast and accurate experience as well as near-seamless offline capabilities.
Conclusion
Web performance optimization is vital for any website, app or service. There are many approaches, configurable to suit your desired outcomes. These include long-standing and basic techniques like image compression and browser caching as well as more recent innovations like Javascript service workers and the bundled asset management that allows code splitting, lazy loading and preloading optimizations. We’ve also seen the benefits that even a simple technique like minification can bring.
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.