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 may 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
Most 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 prioritization 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 prioritizing 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
How does Chrome prioritize resources?
HTML— Highest priority.
Styles—Highest priority. Stylesheets that are referenced using an @import directive will also be prioritized 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 visible 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.
Scripts follow a complex loading prioritization 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:
Scripts loaded using will be prioritized as High if they appear in the markup before an image.
Scripts loaded using will be prioritized as Medium if they’re appear in the markup after an image.
Scripts that use async or defer attributes will be prioritized as Low.
Scripts using type=module will be prioritized as Low.
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?), 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: ). 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.
What makes a request critical?
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 recognize 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):
Most importantly, the HTML. If all else fails the user can still read the page.
The logo (A PNG background-image placed by CSS. This could probably be an inline SVG).
4(!) web font weights.
The leading article image.
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.
Controlling resource priorities
Now that we’ve defined what critical requests are, we can start to prioritize them using a few simple, powerful tweaks.
Preload () 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 categorized as absolutely critical, but there are some fundamental issues with how fonts are discovered and loaded:
We wait for the CSS to be loaded, parsed and applied before @font-face rules are discovered.
A font is not added to the browser’s network queue until it matches up its CSS rules to the DOM via the selectors.
This selector matching occurs during style recalculation. It doesn’t necessarily happen immediately after download. It can be delayed if the main thread is busy.
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.
Preload in action: fonts
I ran two tests against calibreapp.com. On the first run, I’d changed nothing about the site at all. On the second, I added these two tags: 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. also accepts a media= attribute, which will selectively prioritize resources based on @media query rules: 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.
Getting tough on web fonts
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 prioritize web font requests in most cases.
We can make further improvements using the CSS font-display property. 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 using font-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:
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.
Keeping on top of page performance
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. (Disclosure: I run Calibre.)
As you’ve seen above, there are a few metrics that are key to understanding user performance.
First paint, tells us when the browser goes from “nothing to something”.
First meaningful paint, tells us when the browser has “rendered something useful”.
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.
Your critical request checklist:
? Enable the Priority column in Chrome DevTools.
? Decide which requests must be made before users can see a fully rendered page.
? Reduce the number of required critical requests where possible.
? Use for assets that will probably be used on the next page of the site.
? Use nopush HTTP headers to tell the browser which resources to preload before the HTML has been fully delivered.
? HTTP/2 Server push is thorny. Probably avoid it. (See this informative document by Tom Bergan, Simon Pelchat and Michael Buettner, as well as Jake Archibald’s “HTTP/2 Push is tougher than I thought”)
? Use font-display: swap; with web fonts where possible.
? Are web fonts being used? Can they be removed? If no: prioritize them and use WOFF2!
? Is a late loading script delaying your single page app from displaying anything at all?
? Check this great free screencast by Front End Center that shows how load webfonts with the best possible fallback experience.
? View chrome://net-internals/#events and load a page — this logs network related events.
No request is faster than no request. ??