Understanding Resource Priorities

A presentation at SmashingConf Freiburg (Online) in September 2020 in by Andy Davies

Slide 1

Slide 1

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

Slide 2

Slide 2

Ever wondered why pages load in the order they do?

Slide 3

Slide 3

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

Slide 4

Slide 4

Prioritisation helps fetch the content in the most efficient order

Slide 5

Slide 5

Slide 6

Slide 6

Several factors affect how resources are prioritised - Browser

Network Conditions

Server / CDN

Protocol

Content Type

Markup and Page Construction

Slide 7

Slide 7

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

Slide 8

Slide 8

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

Slide 9

Slide 9

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

Slide 10

Slide 10

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)

Slide 11

Slide 11

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

Slide 12

Slide 12

What does this look like in an actual browser?

Slide 13

Slide 13

Chrome prioritises resources in the <head>*

Slide 14

Slide 14

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

Slide 15

Slide 15

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>

Slide 16

Slide 16

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>

Slide 17

Slide 17

Firefox does things a little differently

Slide 18

Slide 18

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

Slide 19

Slide 19

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

Slide 20

Slide 20

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

Slide 21

Slide 21

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

Slide 22

Slide 22

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

Slide 23

Slide 23

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

Slide 24

Slide 24

And some servers / CDNs ignore it…

Slide 25

Slide 25

And some servers / CDNs ignore it…

Slide 26

Slide 26

And some servers / CDNs ignore it…

Slide 27

Slide 27

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

Slide 28

Slide 28

The Good…

Slide 29

Slide 29

Almost everyone else has broken HTTP/2 prioritisation !

Slide 30

Slide 30

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

Slide 31

Slide 31

Inert, high priority fetch

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

Slide 32

Slide 32

Slide 33

Slide 33

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

Slide 34

Slide 34

Preload is a tradeoff

Slide 35

Slide 35

Preload is a tradeoff

Slide 36

Slide 36

Preload is a tradeoff

Slide 37

Slide 37

Using a known good CDN or server is always better

Slide 38

Slide 38

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

Slide 39

Slide 39

Being selective about what you preload

Slide 40

Slide 40

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>

Slide 41

Slide 41

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

Slide 42

Slide 42

Sometimes seemingly simple changes can have performance implications

Slide 43

Slide 43

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

Slide 44

Slide 44

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/

Slide 45

Slide 45

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)

Slide 46

Slide 46

Firefox waits until onload before dispatching prefetches

Slide 47

Slide 47

Chrome delays prefetches but still danger of contention

Slide 48

Slide 48

Chrome delays prefetches but still danger of contention

Slide 49

Slide 49

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

Slide 50

Slide 50

What if a Service Worker made your site slower?

Slide 51

Slide 51

Competition can come from surprising sources!

Slide 52

Slide 52

Competition can come from surprising sources!

Slide 53

Slide 53

Competition can come from surprising sources!

Slide 54

Slide 54

Slide 55

Slide 55

Slide 56

Slide 56

1

Slide 57

Slide 57

1 2

Slide 58

Slide 58

1 2 3

Slide 59

Slide 59

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”>

Slide 60

Slide 60

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

Slide 61

Slide 61

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/

Slide 62

Slide 62

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/

Slide 63

Slide 63

There are other ways we can influence priority too

Slide 64

Slide 64

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

Slide 65

Slide 65

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

Slide 66

Slide 66

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

Slide 67

Slide 67

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

Slide 68

Slide 68

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

Slide 69

Slide 69

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

Slide 70

Slide 70

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

Slide 71

Slide 71

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

Slide 72

Slide 72

Emily Hayman, SmashingConf London 2018

Slide 73

Slide 73

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