Testing priority hints with WebPageTest

Back in January 2019 the Chrome team published an Intent to Experiment: Priority Hints message to the blink developers Google group. This set out a plan to start an origin trial to allow developers to experiment with this new web platform feature and give feedback before eventual rollout across all origins.

Unfortunately Priority Hints are currently on hold as the Chrome team weren’t able to to see statistically significant performance wins with the current design. So does that mean you can’t play with this experimental web platform feature now? No, not at all! Read on to find out more how you can still experiment with it using both your browser, and more interestingly, WebPageTest with Cloudflare Workers!

What are priority hints?

Lets very briefly look at what priority hints are and their expected usage. So priority hints are a way for developers to tell a browser the relative priority of resources on a page. A developer can then increase or decrease the priority of an asset depending on how they judge it’s priority in the page load process. So for example:

<!-- The main image is in the viewport,
so considered important by the browser, but we disagree -->
<img src="/images/hero-image.png" importance="low" alt="Main hero image, but considered unimportant">

<!-- we want the browser to know to fetch this script,
but don't prioritise over other important assets -->
<link href="/js/required-later.js" importance="low" rel="preload"  as="script">

<!-- script set to async so it won't block the thread while JS is fetched
but we consider this script to be important so bump its priority for download -->
<script src="/js/async-script.js" importance="high" async></script>

Now of course you need to be careful when changing priorities. If you bump one asset’s priority level, you are lowering the priority of another. Or if you bump all assets to high, then really you have no prioritisation at all! There’s a very important note to be made at this point. These are hints to the browser. They aren’t instructions. Prioritisation in the browser is very complex and it will use heuristics to determine how best to load a page. These hints are a developer’s way to give it more information for consideration. But ultimately it is up to the browser if it actually decides to use the hint for network prioritisation. It may simply choose to ignore them and continue as is.

But one of the interesting use cases for priority hints is not boosting an assets priority with the high value, it’s actually lowering an assets priority using low. By doing so you are giving other assets on a page network priority. So say for example you had an image at the top of your page that you know will be in the main viewport. Browser heuristics state that this will automatically be a high priority asset. But what if that image doesn’t actually add value to a user’s journey. It’s a ‘nice to have’ (eventually), but concentrate on the rest of the page before loading it. That’s just a very simple example of where this functionality could be useful.

There’s a really informative blog post: “Get Ready for Priority Hints” by Jeremy Wagner and Yoav Weiss which goes into priority hints in much more detail.

So how do I use them?

Okay so enough waffle already, how do you try them today? It’s actually really simple, as they are sitting behind the ‘Experimental Web Platform features’ in Chrome flags (chrome://flags/#enable-experimental-web-platform-features).

Browser

In Chrome you can either enable the ‘Experimental Web Platform features’ flag manually, or use the command line interface to fire up a Chrome window with it already enabled. On OSX you simply enter this command into the terminal:

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --enable-experimental-web-platform-features

WebPageTest

Doing this via WebPageTest is just as simple as above. Just make sure you are running a test using a Chromium browser and enter --enable-experimental-web-platform-features into the ‘command line custom options’ under the ‘Chromium’ tab (see below):

WebPageTest 'Chromium' tab allows you to enter CLI commands to be passed through to the test, as displayed in the image.

The Chromium browser running the test will now have priority hints enabled (assuming it’s based on a build after M73).

Are priority hints enabled?

So how do you know if priority hints are enabled? Well I’ve created some incredibly simple demo pages that will allow you to test to see if they are. The pages contain a selection of images with their priorities modified using the importance attribute. You will quickly be able to spot if priority hints are enabled by looking at the network tab in DevTools and making sure the ‘Priority’ tab is visible.

In the image below, priority hints aren’t enabled. All images get a high priority and the preloaded image gets a low priority:

Image shows the priorities that the Chrome browser heuristics have determined is best to load the page, all images display high.

Now lets see what happens when the experimental flag (and client hints) and enabled:

Image shows the priorities that I have managed to update using the `importance` attribute.

The above image looks very different. Each of the images has a priority set depending on the image name (mirrored with the importance attribute) and the preload has been forced to be high priority. Note that images with importance="auto" return the default priority determined by the browser heuristics. A very unscientific and simple demo, but it allows you to test to see if client hints have been enabled.

Using it in the wild

So you may be asking: “Why would I be adding the importance attribute to my website when no browsers actually support them!”. And for that I completely agree, that’s not a good idea. But that doesn’t mean we can’t test in “production environments”. It’s time to roll out our favorite new friend, Cloudflare Workers (CFW).

I’ve blogged before about how I’ve started to use CFW to experiment with web performance changes. And since priority hints are easily applied using the importance attribute they seem like the perfect candidate for the HTMLRewriter runtime API. But what’s even better is we can easily combine a CFW with WebPageTest. By doing so, we can experiment with changing our production environment and measure the difference in performance the importance attributes would make to page loading.

Cloudflare Workers setup

With this setup I’m piggybacking off the code Andy Davies lists on his blog post.

Preloading Print CSS files

In this very simple example we take ‘IDLE’ priority CSS files (print stylesheets) and bump them up the priority order by preloading them. It’s then possible to tweak the priority of the preload using the importance attribute. Note: This is a completely contrived example to show the attributes in action. I wouldn’t recommend doing this in practice!

Default waterfall

Without preloading our print style sheets have an ‘IDLE’ priority and are seen at requests 11 and 12:

In this waterfall we see the print styles in their usual place with an IDLE priority attached.

Preloaded CSS

Now let’s use a Cloudflare Worker to inject a <link rel="preload"> right after the opening <head> tag:

In this waterfall we have preloaded the print CSS and it can be seen at requests 1 and 2.

In the example what is seen is very predictable. Our print stylesheets have been bumped up the network request order, now sitting at request 2 and 3. Also the priority for these CSS files is now set to HIGHEST.

Preloaded CSS with priority hints

Now let’s enable priority hints in Chrome and modify the preloads to add the importance="low" attribute:

Here we see the priority of the preload has been changed from HIGHEST to LOWEST.

In the example above we still have the print stylesheets sitting at requests 2 & 3, but the important difference is the priority of this preload has been set to LOWEST. We have successfully given the browser a hint that this preload exists but isn’t important. Now this is such a simple page and example that the change actually has no effect on the performance of the page (other than slowing it down slightly because we are preloading print CSS!). But it does show how easy it is to modify a page’s HTML using a Cloudflare Worker and experiment with priority hints.

If you are interested in the code used to create this very simple example I’ve dropped it all into a gist here.

Modifying a preloaded JavaScript bundle

In this example I look at the UK version of the Nike website. Running the site through WebPageTest and looking at the source I noticed that they are preloading two JavaScript files in order to boost its priority. The site is built using React, and as a whole has a lot of JavaScript, coming in at around 1.2 MB of compressed. So using a Cloudflare Worker and WebPageTest let’s investigate what happens when we alter the priority of these 2 preloads.

Vendor.js preload

There’s a file with vendor.js in the filename that comes in at 170 KB in size. The file looks to contain some helper libraries for classnames, Brightcove video, and a production version of React v16.12.0. Note: If any developers from Nike are reading this, you look to be including another version of React later in the page load (v16.13.0), so that’s a pretty good optimisation to look at right there!

By default this vendor.js file is given a medium priority, with a weight of 220. Note: Very roughly speaking, the higher the weight value, the higher the priority. With a standard preload this file downloads at request number 5 in the waterfall, as seen below:

Here we see the default waterfall for Nike, with the vendor JS with a priority weight of 220.

But as mentioned above with priority hints we have the ability to give the browser a hint that this preload should have a lower priority. So using our Cloudflare Worker to modify the HTML lets see what happens when we add importance="low" to this single preload. The resulting waterfall can be seen below:

Here's our modified waterfall where the vendor preload has been dropped in priority, weight has dropped from 220 to 147.

Priority weight drops from 220 to 147 and from request 5 to 17.

Client.js preload

Next on the list is the preload containing client.js in the filename. This file is 83 KB of compressed JavaScript and looks to be the core code for the Nike React app. By default this file gets a priority of medium, weighted at 220 and is seen at request number 6 in the waterfall:

Here we see the default waterfall for Nike, with the client JS with a priority weight of 220.

Again we use priority hints to add importance="low" and investigate what happens:

Here's our modified waterfall where the client preload has been dropped in priority, weight has dropped from 220 to 147.

Priority weight drops from 220 to 147 and from request 6 to 16.

Both together

So we’ve dropped the priority of each preload individually, so let’s see what happens when we do it for both at the same time:

Here we see both preloads drop in priority weight from 220 to 147. This results in them both dropping down the request order.

With both preloads having importance="low" added, we see the following:

  • vendor.js weight drops from 220 to 147. Request number drops from 5 to 15.
  • client.js weight drops from 220 to 147. Request number drops from 6 to 18.

Performance

So enough of looking at random waterfalls. We can see that the priority hints are working as expected. But what really matters is the performance a user sees. We now have 4 tests we can compare using WebPageTest:

  • Baseline: No HTML modifications, priority hints enabled but not used. Cloudflare Worker used as a basic proxy.
  • Vendor.js file preload set to low: HTML modification to a single preload, priority hints enabled and used.
  • Client.js file preload set to low: HTML modification to a single preload, priority hints enabled and used.
  • Both preloads set to low: HTML modification to a both preloads, priority hints enabled and used.

WebPageTest outputs a very interesting filmstrip and set of graphs:

Filmstrip showing all 4 tests side by side. Here we see the test with both preloads set to `low` performing best (visually)

In the image above we see the baseline at the top, and both now low priority preloads at the bottom. The difference in performance is quite striking. Pixels are being rendered to the screen a whole 2 seconds faster. Not bad for a 3G Fast connection (on Chrome desktop)!

Next let’s take a look at the timing information graph:

Here a complete list of timing information for each of the test shown in a bar chart.

The most important difference for me is the render metrics. Comparing the baseline to both preloads being set to low:

  • Speed Index changes from 5,630 to 5,172, or an improvement of 8%.
  • First Contentful Paint and First Meaningful Paint change from 3,650 to 1,972, or an improvement of 46%.
  • Visually Complete from 10,900 to 6,800, or an improvement of 38%.
  • Total Blocking Time from 564 to 328, or an improvement of 39%.

Now it isn’t all positive, we do see certain metrics get worse:

  • Time to Interactive changes from 6,686 to 8,392 or a regression of 26%.
  • Largest Contentful Paint changes from 6,737 to 6,949, or a regression of 3%.
  • Load Time (Fully Loaded) changes from 13,354 to 13,487, or a regression of 1%.

The visually progress graph really shows the difference in the way the page is rendered for each test:

Here we see the visual progress graph for all 4 tests compared.

The green line where both preloads are set to low clearly starts a lot sooner than the baseline (blue). The green line is also ahead of the rest all the way up to 99% complete. Also note the interesting JavaScript rehydration dip for all tests around the 9 second mark.

I find it quite incredible what dropping the priority of two JavaScript files (170 KB and 83 KB) can do for page performance. If you are prioritising perceived performance metrics this could be considered a win, although there are regressions in other metrics. But it’s also worth noting that this page is only preloading 2 JavaScript files out of the 59 (yes, 59!) JavaScript it loads in total. So with some further investigation into priority hints it may be possible to improve these other metrics too. This is left as an exercise for the reader.

If you are interested in the worker code for this experiment it can be found in this gist.

Looking for feedback

So although priority hints are currently on hold, the Chrome team are still looking for feedback for future iterations. There are a number of issues open in the github repo with feedback from the community and Addy Osmani has also stated the team are interested in how much control developers would like over resource loading. Is it:

  • More to do with “when” (e.g. <script load-after="first-paint">)
  • High-level influence (e.g. importance=low/high)
  • Granular (modulo H/2 priority gotchas) (e.g importance=3)

The team have seen priority hints come in most useful when there’s demonstrable network contention between resources and you want to re-order requests for more optimal use of available bandwidth (e.g. using importance="low" to mark certain resources less important, so use bandwidth elsewhere).

Summary

In this post we’ve looked over what priority hints are, their current status, and how to enable and experiment with them on your local browser. Taking this one step further you can also test on live sites using a Cloudflare Worker and WebPageTest. It just shows what a powerful combination these two tools are when used together!

I’m personally a fan of priority hints and I’d love to see them being developed in the future. I like the idea that I have some influence over the priority of assets loading in a page. The web is such a varied medium with so many edge cases that it seems impossible (to me anyway) for browser heuristics to cover all of these issues automatically. So giving developers the means to steer a browser in a certain direction sounds like a win to me.

So if you’ve spotted an issue that you think could be solved using priority hints, why not try them out in production using a Cloudflare Worker and WebPageTest and see what results come back. I’m sure the Chrome team would be very interested in hearing about it!


Post changelog:

  • 21/02/21: Initial post published. Thanks to Addy Osmani for the input around feedback and where the team have seen wins with priority hints.
  • 22/02/21: Minor change related to what ‘weight’ really means in terms of prioritisation in Chrome. It’s a massive oversimplification that I plan to address in the future with a new section. Thanks to Robin Marx for highlighting!
Loading

Webmentions