A Single-line, Whole Page, Speed up Trick

A hand holding a stop watch, representing an audit for a webpage load speed

The cover image for this post is by veri_ivanova

With a one-line change to the HTML for a client’s website, we decreased average load times by 1,320ms.

The Setup

A client approached us last week with a simple request:

Make our website load faster

- A client

I remember reading in the DevOps Handook about how Amazon had calculated that each 100ms of latency could cost them a million dollars in lost revenue. This one fact is why it’s easy to see that the Amazon homepage tends to load very, very quickly.

These kinds of requests usually come in when sites have unused CSS & JS and leverage heavy images. This wasn’t the case. They did have a lot of CSS and JS, but it was fairly well optimised. Because of this, we were instructed not to touch them. We were permitted to alter the HTML and server-side code, though.

The website uses the JAMStack - which we’re pretty big fans of - so there wasn’t much to do on the server except to check that the Cache-Control policy was set to sane values and ensure that everything was minified whilst building. The client told us that everything was being minified during the build, so we checked the Cache-Control header. This is rather simple to do and can be done either in your browser’s dev tools or by using cURL. Here’s a redacted cURL response for the RJJ website:

λ curl -I rjj-software.co.uk
HTTP/1.1 301 Moved Permanently
Cache-Control: public, max-age=0, must-revalidate
Location: https://rjj-software.co.uk/
Referrer-Policy: strict-origin
X-Clacks-Overhead: GNU Terry Pratchett
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Robots-Tag: all
X-Xss-Protection: 1; mode=block

We use the Cache-Control header value as recommended by Netlify (as that’s where the site is hosted), which might not be suitable for everyone. The client uses a Cache-Control header value which makes sense to them and didn’t want it to change.

They had taken care of splitting their CSS & JS to above and below the fold, which is always great to see (and fantastic for SEO, folks). But, even though it was minified, the below the fold CSS was huge (around 300+ KiB). This lead to a large download, which caused the painter and renderer - because they are different processes - to stall whilst the CSS file was downloaded and parsed. Once it was downloaded it wasn’t a huge problem, but most of their customers were new - and they wanted “better SEO”.

What We Did

Because we weren’t permitted to change the CSS file for below the fold - see What We Would Do Differently for what we advised going forward - we had to take a different tactic. Some of the more experience web devs who are reading this might be screaming that we should have taken a look at the CSS, but we didn’t for two reasons:

  1. We were told not to
  2. This nugget of information from Gerald M. Weinberg:

If what they have been doing hasn’t solved the problem, tell them to do something else.

- Gerald M. Weinberg

But what else could we do?

We had permission to alter the server-side code and the HTML. We trusted that their JAMStack host was taking care of the server-side stuff, and assumed (and tested for) there was a CDN in place (there was). So that just left the HTML.

So we decided to use a meta tag for preloading content. This tells the browser to fire off a request to download a piece of content before it’s needed.

Usually when a browser sees something like:

<link rel="stylesheet" href="/css/below.min.css">

it will immediately put in a request for the css file, await the file being downloaded, alter the presentation of the DOM with it (render), then update the screen (painter). But, if the file is pretty large and complex, then the browser has to wait for it to be downloaded before it can do all of that. By telling the browser to preload a resource before it’s required the browser will likely have the file to hand when it comes across the link tag (as shown above).

Here’s a fuller example of the preload tag in action:

<!doctype html>
  <html lang="en-gb">
    <head>
      <!-- preload link -->
      <link rel="preload" as="style" href="below.min.css">
      <!-- meta tags here -->
      <style>
        /* above the fold CSS goes here */
      </style>
      <!-- other CSS files here -->
      <!-- below the fold stylesheet -->
      <link rel="stylesheet" href="/css/below.min.css">
    </head>
    <body>
      <!-- body things -->
    </body>
  </html>

The inlined style block allows for the above the fold content to be styled as it arrives - we’re not a big fan of inline styles, because they can be difficult to keep in sync with the rest of the CSS - and the styles for below the fold are provided in the below.min.css file.

When the browser sees the preload link ( <link rel="preload" as="style" href="below.min.css"> ) , it will send off a request to download the file but keep rendering the page as-is including the inlined styles as it goes. This means that the above the fold content is rendered and painted correctly as soon as the browser can - usually immediately after the HTML is downloaded. Eventually, the browser will get to the stylesheet link ( <link rel="stylesheet" href="/css/below.min.css"> ) but by that time the file is downloaded and parsed: ready to pass to the renderer and painter.

What We Would Do Differently

After submitting the change to the client, they saw a huge decrease in average page load time - around 1,320ms on average per page. They were incredibly happy with this and started seeing a difference immediately.

I then took a look at the unminified below the fold CSS file, and my initial thoughts were confirmed: they had used one CSS file for their entire site. A better way to deal with this would be to have separate files per page and alter the page templates to only include the CSS files required for each page. I can see why they wanted a quick-fix, though. Re-writing all of the page templates to include references to the required CSS files, and splitting those contents across the relevant files can take time. It’s also not easily regression testable using tools - though it can be done.

I’ve advised them that this is only a temporary fix and that they should look to splitting their CSS files. Then again:

If what they have been doing hasn’t solved the problem, tell them to do something else.

- Gerald M. Weinberg

We were hired to solve a problem, which is what we have done. Hopefully, since the heat is off for this client, they can now take a look at the bigger issue (splitting CSS files out) and can spend some engineering time on that.

Summary

Preloading content can be a helpful, albeit short-term fix for a website which is suffering from long painting and rendering times, especially if there are a lot of CSS rules involved.

We wouldn’t recommend using this as a long-term fix though, as it will likely be better to modularise your CSS and only include that which is needed for each page.

Solving the problem first, then going back to recommend a slightly different course of action is often quite a useful thing to do for clients. This allows them to solve the immediate issue, which is likely causing them headaches (at best) or loss of sales (at worst). By taking care of that, you ease the pressure which allows everyone to take a step back and look at the long term fix.

Just don’t let it become the Brent of the over-arching solution.