Andy Davies

Independent Web Performance Consultant

Rel=prefetch and the Importance of Effective HTTP/2 Prioritisation

Many performance techniques focus on improving the performance of the current page, but there are some that help with the performance of the next page – caching, prefetching, and prerendering for example.

The Prefetch Resource Hint allows us to tell the browser about resources we expect to be used in the near future, so they can be fetched ready for the next navigation.

Several of my clients have implemented Prefetch – some are inserting the markup server-side when the page is generated, and others injecting it dynamically in the browser using Instant Page or similar.

A while back I noticed Chrome was making requests for prefetched resources much earlier than I expected and in some cases the prefetched resources were competing with other more important resources for the network.

As the specification makes clear this is something we want to avoid:

Resource fetches required for the next navigation SHOULD have lower relative priority and SHOULD NOT block or interfere with resource fetches required by the current navigation context.

So how do browsers behave and what are the implications of which server is in use?

Test Case

The tests in this post are based on a modified version of the Electro ecommerce template from ColorLib with the following prefetch declarations added in the <head> of the document:

1
2
3
4
<link rel="prefetch" href="https://www.wikipedia.org/img/Wikipedia-logo-v2.png" as="image" />
<link rel="prefetch" href="dummy-subresources/styles.css" as="style" />
<link rel="prefetch" href="dummy-subresources/scripts.js" as="script" />
<link rel="prefetch" href="dummy-subresources/image.jpg" as="image" />

The collection of test pages I used is available on Github.

All the browsers tested – Firefox, Chrome, new Edge and Safari – issue prefetch requests with a low priority, but when the requests are dispatched varies between browsers.

Chrome, new Edge and Safari dispatch the prefetch requests sooner than Firefox and rely on HTTP/2 prioritisation to schedule the requests appropriately against other resources.

As support for HTTP/2 prioritisation varies from very good to non-existent depending on the server, this approach can lead to prefetched resources competing for network capacity.

Servers with Good HTTP/2 Prioritisation

The first set of examples are served using h2o, a server that’s known to support effective HTTP/2 prioritisation, running on a $5/month Digital Ocean droplet.

  • Firefox

Firefox delays issuing the prefetch requests (37, 38, 39, 40) until the network is quiet. In the example below they’re actually dispatched after the load event but I’ve also seen them dispatched in the middle of page load when the network was quiet.

WebPageTest waterfall showing Firefox defering the request for prefetched resources Resource Hints in head of Page - h2o tested with Firefox / Dulles / Cable

  • Chrome and Edge

Chrome schedules the requests for prefetched resources alongside the those referenced in the body of the document, as part of it’s ‘second stage load’.

It delays dispatching the prefetch requests (30, 31, 32, 34) until after the other resources referenced in markup but the prefetch requests are still made before those for ‘late-discovered’ resources discovered, such as background images, and fonts.

The server correctly delays the responses for resources prefetched from the test page origin until the other higher priority resources have been served.

As HTTP/2 prioritisation only works across the same connection, if a resource is prefetched from another origin it can’t be prioritised against requests from other origins. And so the request to prefetch an image from Wikipedia (34) gets dispatched, and completes before other content that’s needed to render the page.

WebPageTest waterfall showing Chrome requesting prefetched resources late and relying on HTTP/2 prioritisation to schedule them correctly Resource Hints in head of Page - h2o tested with Chrome / Dulles / Cable

  • Safari

Prefetch is disabled by default in Safari 13 but can be enabled via Experimental Features.

Safari dispatches the requests as soon as the prefetch directives are discovered.

Resources prefetched from the same origin as the page (3, 4, 5) are correctly delayed by the server’s prioritisation, but as the request to Wikipedia (2) is on a separate connection it may contend with other more important resources (in this case Wikipedia’s CDN actually responds before h2o so no contention occurs).

WebPageTest waterfall showing Safari requesting prefetched resources early and relying on HTTP/2 prioritisation to schedule them correctly Resource Hints in head of Page - h2o tested with Safari / UK / Broadband

Servers with Poor HTTP/2 Prioritisation

Pat Meenan and I have been tracking how well HTTP/2 servers support prioritisation for a while, and the sad truth is that only a few servers and services prioritise effectively.

To test what happens with servers that have poor (or missing) HTTP/2 prioritisation, I hosted the page on both Netlify and Amazon Cloudfront.

The examples below use Netlify but Cloudfront showed similar behaviour.

  • Chrome and Edge

Chrome’s delay in dispatching the prefetch requests means the prefetched resources don’t contend with the other resources referenced in the page markup.

But as the server doesn’t respect the client-provided priorities, the low priority prefetched resources (30, 31, 32) delay higher priority background images and fonts (33, 34, 34, 36, 37), and the larger the prefetched resources are the longer this delay will be.

In this test only some of the contents of the prefetched resources are recieved before the fonts but in other tests I’ve seen whole resources download before the fonts.

WebPageTest waterfall showing Chrome requesting prefetched resources early and them competing with other resources due to poor server HTTP/2 prioritisation Resource Hints in head of Page - Netlify tested with Chrome / Dulles / Cable

  • Safari

Unfortunately the outcome in Safari is even worse than Chrome and Edge.

Safari’s choice to dispatch the prefetch requests as soon as they’re discovered, coupled with the poor server prioritisation, leads to the prefetched resources (2, 3, 4, 5) being fetched far too early and delaying critical content such as stylesheets.

WebPageTest waterfall showing Safari requesting prefetched resources early and them competing with other resources due to poor server HTTP/2 prioritisation Resource Hints in head of Page - Netlify tested with Safari / UK / Broadband

Summary of Behaviour

Firefox is the only browser that seems to have good behaviour regardless of server support for prioritisation.

When the server used supports effective prioritisation, then resources prefetched from the same origin are scheduled appropriately in Chrome, Edge and Safari and they don’t contend with other higher priority resources.

If the server doesn’t support effective prioritisation then the prefetched resources can have a negative impact on performance, particularly in Safari, but also in Chrome and Edge.

Chrome, Edge and Safari all dispatch prefetch requests to third-party origins too soon and these requests contend for network bandwidth.

Now we have an understanding of how browsers behave with both ‘good’ and ‘bad’ HTTP/2 servers, how we should implement prefetch in our pages?

Delaying Prefetch Hints

If you use a server or CDN that has effective HTTP/2 prioritisation (essentially Akamai, Cloudflare or Fastly) then you can rely on the prefetch resources being prioritised correctly and so it doesn’t really matter where you place the prefetch hints for resources from the same origin.

If you use another server or CDN, or you have cross-origin prefetches then you may need to delay when the browser discovers the prefetch hints.

Placing the prefetch hints at the end of the document appears to be one way of achieving this for same origin resources.

In the example below the prefetched resources (36, 37, 38, 39) are dispatched after the late discovered resources and so don’t contend for the network with them.

WebPageTest waterfall showing Chrome requesting prefetched resources late to overcome the issues with poor server HTTP/2 prioritisation Resource Hints at Bottom of Page - Netlify tested with Chrome / Dulles / Cable

Another option is to inject the prefetch hints after the page has rendered, perhaps when the load event fires, or in response to user interaction (as instant.page does).

Closing Thoughts

Writing this post made me a little bit sad…

Prefetch is a feature that’s supposed to help make our visitor’s experiences faster but with the wrong combination of browser and CDN / server it can actually make experiences slower!

If Chromium and WebKit followed Firefox’s lead and dispatched prefetches later then the issue of CDNs and servers with ‘flawed’ HTTP/2 prioritisation would have a reduced impact on these test cases as much (there would still be issues with fonts and background images being delayed though).

The relevant browser bugs for Chrome and WebKit are linked in Further Reading below.

If you’re using a CDN or server that has ‘flawed’ HTTP/2 prioritisation, then you should consider how to mitigate the issues shown above as, for a variety of reasons, I’m not sure the prioritisation issues are going to be fixed any time soon.

Further Reading

W3C Resource Hints Specification

HTTP/2 Prioritisation Tests

Prioritisation Tests for rel=prefetch

Chrome Issue 1031134: Resources prefetched for next navigation contend with resources for current navigation

Safari Bug 49238 - Link prefetch launches subresource requests too aggressively

Instant Page

Comments