Website Optimisation - Eliminating Blocking Resources

Dan   

We've previously described some methods to discover resources that block rendering in a browser. Now we'll cover some techniques you can use to load resources that will prevent them from blocking. Before you start, you should always run some website performance tests using your favorite testing tool to get a baseline so you can measure any improvements. We recommend testing with webpagetest.org.

Before we get into how to eliminate blocking resources we should answer the question...

What is a Blocking Resource?

When a browser loads a page from a website it starts reading the HTML markup in the page from top to bottom. If it encounters a CSS or JavaScript file while it's reading, the browser will stop and wait while it downloads and parses that file. While the browser is waiting it can’t do anything else, like processing the rest of the HTML or displaying content to the user, instead it is blocked.

Why is this a problem?

It wouldn't be if we lived in a perfect world, where we only included exactly the information required to display a page. However, websites are typically built using prebuilt themes and third party libraries that can contain lots of CSS and Javascript. Only a tiny fraction of these might be used, or it might only be needed for ‘below the fold’ content, or it might be needed on another page of the site. Note: Below the fold means the content of a page that doesn't initially fit on the screen. Any unnecessary information means the browser is blocked for longer than needed, slowing the load experience for the user, likely frustrating them.

CSS and Javascript files are usually at the very top of the HTML document before any of the content. This means they have to be downloaded and parsed before displaying anything. Even a website that has extremely fast server response times can still seem very slow if it has blocking resources.

Let's give a simple example, you have a website that includes a live chat widget. The javascript for the widget is included at the very top of the page in the section. The browser has to stop and download and parse the CSS and Javascript for the widget (usually downloaded from a third party domain) before ANY content is displayed to the user. Even though the chat widget could pop up after the rest of the content without affecting the user experience.

Things you should always be doing

Before we target any CSS or Javascript specific techniques lets cover the boxes you should tick for both.

1. Minify all files

Minification is the removal of all non essential characters in a file, eg irrelevant whitespace and code comments. It can make a substantial difference to the download size.

2. Ensure compression is enabled on your server

Make sure your server is configured to either use gzip or brotli compression when serving CSS and Javascript.

3. Self host files

If the CSS or Javascript is on a third party domain, ie not the domain name your website is on, then you should strongly consider uploading the file to your website and serving it off your domain. This might not always be possible, but if it is it eliminates the overhead of the browser opening a connection to another domain, and it removes the possibilty that the third party is not compressing, minifying, or using HTTP/2.

After the implementation of cache partitioning in the major browsers, there is no longer any potential advantage to using third party CDNs to serve assets like javascript, fonts, or CSS. Make a copy on your server and host them on your domain.

4. If you can't self host then preconnect to third party domains

If external assets are on third party domains, and you absolutely can't self host them, then you can get
improvements in loading by telling the browser to preconnect to the third party domain.

Using preconnect tells the browser to establish an early connection to the domain before it has discovered the asset while reading the HTML. You use preconnect in the head of the HTML, eg:

Establishing early connections can shave 100-500ms off third party load times, not to be sneezed at. You should only use preconnect on critical third party resources, for all the others you can use dns-prefetch, eg:

This tells the browser to perform dns resolution of the domain early, this is equivalent to looking up a phone number in the phone book. Pre-resolving dns can save 20-120ms, it sounds like small numbers but remember, differences as small as 100ms can have large measurable impacts on conversion rates.

5. Select only what you need from frameworks

Its common for users of frameworks like Bootstrap, or jQuery UI, to only use a fraction of the functionality provided. If this is you, then the first step is to cut out what you're not using. Bootstrap and Jquery UI are modular, you can either download everything in one bundle, or you can break it up to take just the bits you are using.

These four points will ensure that blocking resources are downloaded as quickly as possible. Now we can discuss some specific techniques that will help minimise any blocking and get your page displaying quickly. Please be aware that this is not an exhaustive overview, however these techniques will probably get you 90% of the benefit with 10% of the work. Getting that last 10% really requires a deep technical understanding, however if you want to go there then Google’s web.dev website is an excellent resource.

Techniques for Optimising Blocking Resources

Javascript

There are a few different techniques for optimising the loading of Javascript. First, let's have a look at the default behaviour for loading. Credit for the images go to Daniel Imms and his website Growing with the web. As stated earlier, the default behaviour is to stop parsing the HTML document when a script is encountered, download the script and execute it.

Javascript loading legend Default Javascript loading behaviour

Now let's look at how we can change this behaviour.

1. Moving scripts to the very bottom of the page, right before the closing tag.

This is the original optimisation technique, before defer and async were introduced. It works by moving scripts to the very end of the HTML document, where they are downloaded and parsed after everything else.

Javascript end of page load

In our example in the previous section, the chat widget would now load and pop up after the rest of the content has been displayed to the user.

2. Defer and Async

<script> tags that have a src attribute can be marked as either deferred defer, or asynchronous async, or both. By marking the script in the following manner:

<script src=”https://www.somedomain.com/somescript.js” defer async>

You are telling the browser to change the script loading behaviour.

Defer

Defer was the original attempt to have browser based support for improved javascript loading, it tells the browser to download the script asynchronously, like getting someone else to fetch another book for you so you can keep reading, and then execute it once it has finished reading the whole HTML document. Here's the loading behaviour with defer enabled.

Javascript loading defer

As you can see the script no longer blocks the browser from doing anything else while downloading. Defer preserves script execution order, this can be very important if a script depends on one declared earlier in the page.

As noted earlier, you can only mark <script> tags that have a src attribute as deferred. Inline scripts, eg:

<script type="text/javascript">
    console.log("Hi I'm some inline script!");
</script>

Will simply ignore any attempt to do so. This can cause problems if you have inline script peppered through your code and rely on a deferred third party library, eg jQuery. There are two potential solutions if you can't simply move the code to the bottom of the page:

  1. Move each inline code block into its own file and include it using the src attribute so you can defer it.
  2. Or you can try declaring the block as a module. Your mileage may vary as browser support may be limited and it also places the script into strict mode, which may break it. Trying and testing is key!

Async

The async attribute tells the browser to download the script now and execute as soon as it has finished. Like defer, the downloading is done asynchronously, unlike defer, the script is executed as soon as it is downloaded. Here is the behaviour:

Javascript loading async

As you can see the browser doesn't stop while downloading, but does pause once downloading is finished so it can execute the script. async doesn't preserve script order, which can potentially cause issues when there are script dependencies.

All three methods have pros and cons. Defer and async result in faster page loads overall as the scripts are downloaded in parallel to the browser reading the HTML, simply moving the script to the bottom of the page simply shifts the sequence around.

Your first go to should be to defer all scripts, then, if some above the fold content is taking too long because it has a javascript dependency, eg a carousel in the hero section you can try making the necessary scripts async instead.

Optimising CSS

The key to fast CSS loading is to prioritise the CSS needed for the immediate above the fold content and then deferring the rest. There are many articles out there advocating for extracting the precise CSS and then including it inline in the HTML document in a style block, ie:

<style type="text/css">
     .accordion-btn {background-color: #ADD8E6;color: #444;cursor: pointer;padding: 18px;width: 100%;border: none;text-align: left;outline: none;font-size: 15px;transition: 0.4s;}.container {padding: 0 18px;display: none;background-color: white;overflow: hidden;}h1 {word-spacing: 5px;color: blue;font-weight: bold;text-align: center;}
</style>

However you have to do this on every possible landing page where the critical CSS might be different per page. Inlining CSS also adds to the download size for each page, and forces the browser to reparse the CSS rules for every load rather than being able to use a cached file for repeat views.

We advocate for putting the critical CSS into its own file and loading that normally, and then deferring any other non critical CSS. Identifying the critical CSS is the main task. The first thing to do is to look at the included CSS on your website and see if you can identify any non core files. They can then be deferring can be done using this neat trick.

<link as="style" href="/styles.css" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>

The as="style" lets the browser download the file asynchronously, the onload changes the type to stylesheet telling the browser to parse the file. Finally we include a <noscript> to enable the CSS to load normally in the event javascript is turned off.

Once you've deferred non core CSS files you can still see if your core ones still contain lots of unused rules. Google Chrome gives you a not very convenient way of extracting these rules using its coverage tool, unfortunately it doesn’t let you easily export used and unused rules. You have to go through it either yourself, programmatically or use a third party tool to make two files, the critical CSS, and the non-critical CSS which can be deferred.

Conclusion

Eliminating, or at least minimising, blocking resources can be the biggest boost to user experience you can make to your site. With Google Web Vitals being introduced soon as a search signal its more important than ever before (if that's possible) to make sure your website loads as fast as possible so your users stay longer and buy more from your site.

Website Performance Website Optimisation

© PEAKHOUR.IO PTY LTD 2024   ABN 76 619 930 826    All rights reserved.