9 min read

We've previously covered ways to find resources that block rendering in a browser. Now we'll cover techniques for loading those resources without letting them block rendering. Before making changes, always run some website performance tests using your favourite testing tool to get a baseline so you can measure any improvements. We recommend testing with webpagetest.org.

Before we get into the techniques, it is worth defining the problem.

What is a Blocking Resource?

When a browser loads a page from a website it reads the HTML markup from top to bottom. If it encounters a CSS or JavaScript file while it's reading, the browser stops while it downloads and parses that file. While the browser is waiting it can’t process the rest of the HTML or display content to the user. Rendering is blocked.

Why is this a problem?

It would not be a problem if every page included only the information required to display it. Most websites do not work that way. They are typically built using prebuilt themes and third-party libraries that can contain lots of CSS and JavaScript. Only a tiny fraction of this code might be used on the current page, or it might only be needed for ‘below the fold’ content, or 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 keeps the browser blocked for longer than needed, slowing the load experience for the user.

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 the browser can display anything. Even a website that has extremely fast server response times can still seem very slow if it has blocking resources.

Take a simple example: 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, 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. The chat widget could appear after the rest of the content without affecting the user experience.

Things you should always be doing

Before targeting CSS- or JavaScript-specific techniques, cover the basics for both.

1. Minify all files

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

2. Ensure compression is enabled on your server

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

3. Self host files

If the CSS or JavaScript is on a third-party domain, i.e. not the domain name your website is on, you should strongly consider uploading the file to your website and serving it from 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 possibility 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 improve 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. Use preconnect in the head of the HTML, e.g.:

Establishing early connections can shave 100-500ms off third-party load times, which is worth having. You should only use preconnect on critical third-party resources. For all the others you can use dns-prefetch, e.g.:

This tells the browser to perform DNS resolution of the domain early, which is similar to looking up a phone number in the phone book. Pre-resolving DNS can save 20-120ms. That 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

It is 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 points will ensure that blocking resources are downloaded as quickly as possible. Now we can discuss some specific techniques that will help minimise blocking and get your page displaying quickly. Please be aware that this is not an exhaustive overview, but these techniques will probably get you 90% of the benefit with 10% of the work. Getting that last 10% requires deeper technical work. If you want to go there, Google’s web.dev website is a good resource.

Techniques for Optimising Blocking Resources

Javascript

There are a few different techniques for optimising the loading of JavaScript. First, let's look at the default loading behaviour. Credit for the images goes 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. For example:

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

This tells the browser to change the script loading behaviour.

Defer

Defer was the original browser-based support for improved JavaScript loading. It tells the browser to download the script asynchronously while it keeps reading the HTML, 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, e.g.:

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

Any attempt to defer that inline block will simply be ignored. This can cause problems if you have inline script scattered through your code and rely on a deferred third-party library, e.g. 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. Test this carefully, as browser support may be limited and it also places the script into strict mode, which may break it.

Async

The async attribute tells the browser to download the script now and execute it 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 while the browser reads the HTML. Moving the script to the bottom of the page shifts the sequence around.

Your first option should be to defer all scripts. Then, if some above the fold content is taking too long because it has a JavaScript dependency, e.g. 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 defer the rest. Many articles advocate extracting the precise CSS and then including it inline in the HTML document in a style block, i.e.:

<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. You can defer those files using this pattern:

<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 if JavaScript is turned off.

Once you've deferred non-core CSS files you can still check whether your core ones contain lots of unused rules. Google Chrome's coverage tool can show unused rules, but it doesn’t let you easily export used and unused rules. You have to go through it 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 one of the biggest improvements to user experience you can make to your site. With Google Web Vitals being introduced soon as a search signal, it is important to make sure your website loads as fast as possible so your users stay longer and buy more from your site.