Serving a website seems pretty simple: Send some HTML, the browser figures out what resources to load next. Then we wait patiently for the page to be ready.
Little did you know, a lot is going on under the hood.
Have you ever wondered how browser figures out which assets should be requested and in what order?
Today we’re going to take a look at how we can use resource priorities to improve the speed of delivery.
Resource priority at work
Modern browsers parse HTML using a streaming parser—assets are discovered within the markup before it has been fully delivered.
As assets are found, they’re added to a network queue along with a predetermined priority.
In Chrome today, there are a number of asset prioritisation levels:Very Low, Low, Medium, High and Very high. Peeking into Chrome Devtools source shows that these are aliased to slightly different labels: Lowest, Low, Medium, High and Highest.
To see how your site is prioritising requests, you can enable a priority column in the Chrome devtools network request table.
If you’re using Safari Technology preview, the (new!) Priority column can be shown in exactly the same way.
Show the Priority column by right clicking any of the request table headings.
You’ll also find the priority for a given request in the Performance tab.
Resource timings and priorities are shown on hover
Each resource type (CSS, JS, fonts, etc.) has their own set of rules that dictate how they’ll be prioritised. What follows is a non-exhaustive list of network priority plans:
HTML — Highest priority.
Styles — Highest priority. (Stylesheets that are referenced using an @import directive will also be prioritised ‘Highest’, but they’ll be queued after blocking scripts.)
Images are the only assets that can vary priority based on viewport heuristics. All images start with a priority of Low but will be upgraded to Medium priority when to be rendered within the viewport. Images outside the viewport (also known as ‘below the fold’) will remain at Low priority.
During the process of researching this article, I discovered (with the help of Paul Irish) that Chrome devtools are currently misreporting images that have been upgraded to Medium as ‘Low’ priority. Paul wrote up a bug report, which you can track here.
If you’re interested in reading the Chrome source that handles the image priority upgrade, start with UpdateAllImageResourcePriorities and ComputeResourcePriority.
Ajax/XHR/****fetch()—High priority.
Scripts follow a complex loading prioritisation scheme. (Jake Archibald wrote about this in detail during 2013. If you want to know the science behind it, I suggest you grab a cuppa and dive in). The TL;DR version is:
Fonts are a bit of a strange beast; they’re hugely important resources (who else loves the “‘I see it!”, “Now it’s gone”, “Whoa, a new font!” game? Schrödinger’s Font.), so it makes sense that fonts are downloaded at the Highest priority.
Unfortunately, most @font-face rules are found within an external stylesheet (loaded using something like: <link rel="stylesheet" href="file.css" /> ). This means that web fonts are usually delayed until after the stylesheet has downloaded.
Even if your CSS file references a @font-face font, it will not be requested until it is used on a selector and that selector matches an element on the page. If you’ve built a single page app that doesn’t render any text until it renders, you’re delaying the fonts even further. 🙈
Most websites effectively ask the browser to load everything for the page to be fully rendered, there is no concrete concept of “above the fold”.
Back in the day, browsers wouldn’t make more than 6 simultaneous requests per domain — people hacked around this by using assets-1.domain.tld, assets-2.domain.tld hosts to increase the number of asynchronous downloads but failed to recognise that there would be a DNS hit and TCP connection for each new domain and asset.
While this approach had some merits, many of us didn’t understand the full impacts, and certainly didn’t have good quality browser developer tools to confirm these experiments. 😵
Thankfully today, we have great tools at our disposal. Using CNN as an example, let’s identify assets that are absolutely required for the viewport to be visually ready (also known as ‘useful’ to a someone trying to read it).
The user critical content is the masthead, and leading article.
There’s really only 5 things that are necessary to display this screen (and not all of them need to be loaded before the site is usable)
These assets (note the lack of JavaScript) are essential to the visuals that make up the main viewport of the page. These assets are the ones that should be loaded first.
Diving into the performance panel in Chrome shows us that around 50 requests are made before the fonts and leading image are requested.
CNN.com becomes fully rendered somewhere around the 9s mark. This was recorded using a ‘4G’ connection, with reasonably spotty connectivity.
There’s a clear mismatch between the requests that are required for viewing and the requests that are being made.
Now that we’ve defined what critical requests are, we can start to prioritise them using a few simple, powerful tweaks.
Preload (<link rel="preload" href="font.woff" as="font" crossorigin />) instructs the browser to add font.woff to the browser’s download queue at a ‘High’ priority.
Note: as="font" is the reason why font.woff would be downloaded as High priority — It’s a font, so it follows the priority plan discussed earlier in the “How does Chrome prioritise resources?” section.
Essentially, you’re telling the browser:
“You might not know it yet, but we’re going to need this.”
This is perfect for those critical requests that we identified earlier.
Web fonts can nearly always be categorised as ‘absolutely critical’, but there are some fundamental issues with how fonts are discovered and loaded:
In most cases, fonts are delayed by a number of seconds, just because we’re not instructing the browser to download them in a timely fashion.
On a mobile device with a slow CPU, laggy connectivity, and without a properly-constructed fallback this can be an absolute deal breaker. 😫
I ran two tests against calibreapp.com, on the first run, I’d changed nothing about the site at all, but on the second, I added these two tags:
<link rel="preload" as="font" href="…Calibre-Regular.woff2" type="font/woff2" crossorigin />
<link rel="preload" as="font" href="…/Calibre-Semibold.woff2" type="font/woff2" crossorigin />
Below, you’ll see a visual comparison of the rendering of these two tests—the results are quite staggering:
The page rendered 3.5 seconds faster when the fonts were preloaded.
Bottom: Fonts are preloaded — The site finishes rendering in 5 seconds on a ‘emerging markets’ 3G connection.
<link rel="preload" /> also accepts a media="" attribute, which will selectively prioritise resources based on @media query rules:
<link rel="preload" href="article-lead-sm.jpg" as="image" type="image/jpeg" media="only screen and (max-width: 48rem)" />
Here, we’re able to preload a particular image for small screen devices. Perfect for that ‘main hero image’.
As demonstrated above, a simple audit and a couple of tags later and we’ve vastly improved the delivery & render phase. Super. 👯
69% of sites use web fonts, and unfortunately, they’re providing a sub-par experience in most cases. They appear, then disappear, then appear again, change weights and jolt the page around during the render sequence.
Frankly, this sucks on almost every level.
As you’ve seen above, controlling the request order and priority of fonts has a massive effect on render speed. It’s clear that we should be looking to prioritise web font requests in most cases.
We can make further improvements using the CSS font-display property. font-display allows us to control how fonts display during the process of web fonts being requested and loaded.
There are 4 options at your disposal, but I’d suggest usingfont-display: swap;, which will show the fallback font until the web font has loaded—at which point it’ll be replaced.
Given a font stack like this:
body {
font-family: Calibre, Helvetica, Arial;
}
The browser will display Helvetica (or Arial, if you don’t have Helvetica) until the Calibre font has loaded. Right now, Chrome and Opera are the only browsers that support font-display, but it’s a step forward, and there’s no reason to not use it starting today. 🗓
As you’re well aware, websites are never ‘complete’, there are always improvements to be made and it can feel overwhelming, quickly.
Calibre is an automated tool for auditing performance, accessibility and web best practices, it’ll help you stay on top of things.
As you’ve seen above, there are a few metrics that are key to understanding user performance.
Here, we set a budget on CNN’s ‘First meaningful paint’ for <5 seconds.
You can set budgets against all these key user experience metrics. When those budgets are exceeded (or met) your team will be notified by Slack, email or wherever you like. 💁
Calibre displays network request priority, so you can be sure of the requests being made. Tweak priorities and improve performance. 💯
I hope that you’ve learned some valuable skills to audit requests and their priorities, as well as sparked a few ideas to experiment with to make meaningful performance improvements.
🤔 Are web fonts being used? Can they be removed?If no: Prioritise them and use WOFF2!