<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 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.
Resource Hints in head of Page - h2o tested with Firefox / Dulles / Cable
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.
Resource Hints in head of Page - h2o tested with Chrome / Dulles / Cable
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).
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'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.
Resource Hints in head of Page - Netlify tested with Chrome / Dulles / Cable
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.
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.
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).
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.
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