A presentation at SmashingConf Freiburg (Online) by Andy Davies
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
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
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
Have you ever looked at the Network Panel in DevTools, or a waterfall in WebPageTest and wondered what determines the order of the resources, and how you can influence it?
This talk explores how browsers prioritise resources, how some of them adjust for network conditions, what part servers play and how the decisions we make as developers influence their behaviour, and our visitors’ experience.
We’ll also dig into the tradeoffs and pitfalls that you may come across with tools and techniques such as JS bundlers and standards such as Resource Hints.