Understanding Resource Priorities Andy Davies ∙ Sep 2020 https://www.flickr.com/photos/volvob12b/8096363022

Ever wondered why pages load in the order they do?

Ever wondered why pages load in the order they do? …I often do too!

Prioritisation helps fetch the content in the most efficient order

Several factors affect how resources are prioritised - Browser

Network Conditions

Server / CDN

Protocol

Content Type

Markup and Page Construction

The browser is where it all starts… HTML is parsed incrementally, but… Synchronous script elements block the parser until - script has loaded and executed - pending stylesheets have loaded Stylesheets block rendering of content below them

Some resources are discovered late Fonts and background images - when the render tree is built Script injected resources - when the script executes @imported stylesheets - when the outer stylesheet is parsed

Render or parser blocking resources get higher priority Stylesheets Blocking Scripts Fonts Preloads Images Visible Images Non-Blocking Scripts Prefetches High Low Priority

Some content is useful even when we only have part of it HTML Can incrementally parse the HTML looking for new elements, resources to fetch etc. Bitmap Images Size information can be extracted, can be rendered progressively (even if that’s by scan line)

For other content we need it all Styles Whole stylesheet needs to be downloaded to build CSS Object Model Scripts Whole script is needed before it can be executed (can be parsed incrementally in some browsers) Fonts Whole font is needed before it can be applied

What does this look like in an actual browser?

Chrome prioritises resources in the <head>*

Chrome prioritises resources in the <head>* Requests for resources referenced in the <body> are delayed until ones in <head> complete “The Stair Step”

Chrome prioritises resources in the <head>* Parser and render blocking resources at the bottom of the body get higher priorities than you might expect <script src=”js/main.js”></script> <link type=”text/css” rel=”stylesheet” href=”css/bottom.css”/> </body>

Chrome prioritises resources in the <head>* Non-parser blocking scripts in the head are loaded as part of the second phase <script src=”js/dummy-js-async.js” async></script> <script src=”js/dummy-js-defer.js” defer></script> </head>

Firefox does things a little differently

Chrome adapts its approach for slower networks Fonts are loaded with an IDLE priority and don’t block rendering

Prioritisation in HTTP/1.x vs HTTP/2 HTTP/1.x Browser builds a list of resources in priority order 6 TCP connections per origin (can be more or less than 6) When a connection becomes free… requests next highest priority resource in list

Prioritisation in HTTP/1.x vs HTTP/2 HTTP/1.x Browser builds a list of resources in priority order 6 TCP connections per origin (can be more or less than 6) When a connection becomes free… requests next highest priority resource in list Browser is in control

Prioritisation in HTTP/1.x vs HTTP/2 HTTP/2 Requests are sent to the server Relative priority and dependency information attached to request Priority information can be updated Each connection has it’s own set of priority information and is unaware of others Browser expect servers to fulfil requests in the priority provided

Content from different origins will compete for the network https://www.flickr.com/photos/39908901@N06/7834345230

Browsers adopt different approaches to H2 prioritisation Chromium Safari Firefox (Linear List) (weighted) (Groups) https://github.com/quicwg/wg-materials/raw/master/interim-19-05/priorities.pdf

And some servers / CDNs ignore it…

And some servers / CDNs ignore it…

And some servers / CDNs ignore it…

Pat Meenan created a test case and we catalogued the results https://ishttp2fastyet.com

The Good…

Almost everyone else has broken HTTP/2 prioritisation !

And it has real world impact Fonts are stuck behind lower priority requests

Inert, high priority fetch

<link rel=”preload” href=“…” as=”font” crossorigin/> …to the rescue?

Ideally the fonts would be fetched here Safari does this but Chrome doesn’t due to AppCache delay

Preload is a tradeoff

Preload is a tradeoff

Preload is a tradeoff

Using a known good CDN or server is always better

When we explicitly increase the priority of one resource we implicitly decrease the priority of others

Being selective about what you preload

You may also see it used in ‘hacks’ that boost priority

<link rel=”preload” as=”script” href=”a.js” /> <script async src=”a.js”></script>

Priority Hints is a proposal to allow finer control of priority https://wicg.github.io/priority-hints/

Sometimes seemingly simple changes can have performance implications

<link rel=”prefetch” href=“…” as=”image” Inert, low priority fetch for resources likely to be used in the future

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. https://www.w3.org/TR/resource-hints/

Prefetch requests dispatched early with low priority Server with poor prioritisation responds and delays responses for higher priority requests Mix of Safari and poor server prioritisation delays key content (By default prefetch isn’t enabled in Safari ATM)

Firefox waits until onload before dispatching prefetches

Chrome delays prefetches but still danger of contention

Chrome delays prefetches but still danger of contention

Safari issue affects prefetch directives inserted early in markup Directives inserted later e.g. by guess.js or instant.page are less likely to be affected

What if a Service Worker made your site slower?

Competition can come from surprising sources!

Competition can come from surprising sources!

Competition can come from surprising sources!

1

1 2

1 2 3

Service Worker Registration Service Worker is registered in the head of the page

<meta http-equiv=”Expires” content=”-1” /> <script> if (‘serviceWorker’ in navigator) { navigator.serviceWorker.register(‘/sw.js’, {scope: ‘/’}).then(function(reg) { if (reg.installing) { console.warn(‘[SW] installing’); } else if (reg.waiting) { console. warn (‘[SW] installed’); } else if (reg.active) { console. warn (‘[SW] active’); } }).catch(function(error) { console.error(‘[SW] Registration failed’, error); }); } </script> <link rel=”StyleSheet” type=”Text/css” href=”Css/shared.css?v=48”>

HTML Page Service Worker Service Worker requests compete with other requests for network bandwidth

An alternative approach This example delays creating the Service Worker until load event fires for window if (‘serviceWorker’ in navigator) { window.addEventListener(‘load’, function() { navigator.serviceWorker.register(‘/sw.js’).then(function(registration) { // Registration was successful console.log(‘ServiceWorker registration successful with scope: ‘, registration.scope); }, function(err) { // registration failed :( console.log(‘ServiceWorker registration failed: ‘, err); }); }); } https://developers.google.com/web/fundamentals/primers/service-workers/

An alternative approach This example delays creating the Service Worker until load event fires for window if (‘serviceWorker’ in navigator) { window.addEventListener(‘load’, function() { navigator.serviceWorker.register(‘/sw.js’).then(function(registration) { // Registration was successful console.log(‘ServiceWorker registration successful with scope: ‘, registration.scope); }, function(err) { // registration failed :( This might be ok for a traditional page console.log(‘ServiceWorker registration failed: ‘, err); }); }); But is the load event an appropriate tigger in the context of a Single Page App? } https://developers.google.com/web/fundamentals/primers/service-workers/

There are other ways we can influence priority too

Bundling and inlining can override priorities base64 encoded image within a script “A low priority resource within a high priority one”

https://www.flickr.com/photos/lantzilla/29842854

Ultimately, there is only a single ‘last mile’ connection http://www.flickr.com/photos/7671591@N08/1469828976

Self-host 3rd-party libraries and unshard domains https://www.flickr.com/photos/justinknol/6402733575

Where possible stick to the default priorities https://www.flickr.com/photos/elsiehui/15599408558

If you want to intervene in the priority… test, test and test again

See something that doesn’t make sense… Ask Why? https://www.flickr.com/photos/benjreay/14713228051

Raise bugs. Request Features https://www.flickr.com/photos/eltpics/7754659726

Emily Hayman, SmashingConf London 2018

@AndyDavies hello@andydavies.me https://noti.st/andydavies http://www.flickr.com/photos/auntiep/5024494612