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?
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
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.
- 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.
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 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.
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.
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.