
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.

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.

You’ll also find the priority for a given request in the Performance tab.

How does Chrome prioritize resources?
Each resource type (CSS, JavaScript, fonts, etc.) has their own set of rules that dictate how they’ll be prioritized. 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 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.
Ajax/XHR/fetch()—High priority.
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
ordefer
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).

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.
- CSS
- The logo (A PNG
background-image
placed by CSS. This could probably be an inline SVG). - 4(!) web font weights.
- The leading article image.
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.

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:
<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.

also accepts a
media=""
attribute, which will selectively prioritize 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.
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:
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.
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”.
- Finally, First Interactive will tell you when the page has fully rendered, and the JavaScript main thread has settled (low CPU activity for a number of 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. ✌️
Thanks for this article, superb! Did not know about being able to see the stages of render using the performance tab. Spent a good hour playing with that!
Brilliant article. Thanks so much. Have always struggled to understand why fonts are loaded so late…
So glad you articulated the awful state of ‘web fonts’. Anybody (such as myself) who has criticised the adoption of web fonts due to the clunky manner in which they are implemented/used has looked like some sort of laggard when really the issue is very genuine and goes way back to font replacement techniques.
In my own personal browsing, I happily block all web fonts. I could care less about typography relative to performance and smooth page loading that doesn’t give me a jarring experience. There’s more than enough competition for my ocular focus in today’s screen-dominant lifestyle. The last thing I need is the feeling I’m constantly waiting that extra few seconds to load, and refocus, on pages simply FFontS. I know all the mac-toting, graphic design hipsters out there will be up in arms at this viewpoint, but that doesn’t make it any less legitimate.
{{font-display: swap;}} doesn’t really help. It adds a third state of rendering text if my eyes don’t deceive me. 1) text rendered with native fonts; 2) text momentarily rendered with native fonts; 3) momentary text rendered with native fonts and seconds later replaced by web fonts. It’s just a native CSS equivalent to the old SIFR days. It doesn’t solve the problem at all. In fact it looks a touch frantic to the point where it may cause problems for people with epilepsy.
I’d suggest the only complete solution is delaying all font rendering until all fonts are available. The only other option might be to fade font replacement in slowly, if that’s even possible. It would be one of those less (slower) is more (faster to comprehend versus simply display) cases whereby human cognition doesn’t like seeing visual effects on the edge of perception that are actually too fast. A classic example being the MenuShowDelay on the Windows start menu.
This is a fantastic article!
Great Article! I love how you focus on ways to optimize the loading of fonts and the rendering lifecycle in terms of priority. Love it!
Thank you so much this priceless information as for the beginner this is very interesting and new.
Attractive presentation
Great article!
Just days before, I did my own thorough analysis of custom fonts and how they seem to be the biggest blocking culprit and wondering why major browsers haven’t done more to improve their performance, until Chrome v60 with font-display. I came to the same conclusion that preloading fonts is the first thing to add, and it is a tremendous performance boost in browsers that support it. Next is to use the new font-display:swap. Then fall back to JS workarounds for other browsers.
Thank you for putting more of the puzzle pieces together!
Really useful article, thanks!
Anyone have any thoughts about preloading Google Fonts? Their font resources come from long unreadable urls like
https://fonts.gstatic.com/s/roboto/v16/Hgo13k-tfSpn0qi1SFdUfVtXRa8TVwTICgirnJhmVJw.woff2
, and I’ve heard rumours that these files are customised for different user agents, so I’m not sure if it’s a good idea to try to preload them.I found an answer to my own question! See the comments on this post: https://www.cdnplanet.com/blog/faster-google-webfonts-preconnect/
It looks like Google changes the URLs frequently, so there’s little point trying to preload them. But you can use
<link rel="preconnect">
to pre-emptively open a connection to thefonts.gstatic.com
domain, which will speed things up a little.Good info, thank you.
Exactly what I was looking for and very helpful information. Specially about the web fonts. Thank you for sharing the knowledge!
Hi,
I like the benefits of telling the browser to preload the fonts, but the classic way was to list all the font formats in the CSS and let the browser sort through which format to load, like this:
But if we tell the browser to preload a specific format, we bypass its choosing, and if we list all of them, we’ll load too many formats.
So how can we get the best of those two approaches?
Does preload bypass its choosing for which format to use, or do you mean just bypass its choosing for which to download first? I think the latter.
I do think it is an interesting question to explore preload some more, addressing the choice of which formats to preload. Check out the preload section on this page:
https://www.zachleat.com/web/comprehensive-webfonts/#preload