Not every byte that is sent down the wire to the browser has the same degree of importance, and the browser knows this. Browsers have heuristics that attempt to make a best-guess at the most important resources to load first — such as CSS before scripts and images.
That said, as with any heuristic, it doesn’t always work out; the browser might make the wrong decision, usually because it doesn’t have enough information at that time. This article explains how to influence the priority of content adequately in modern browsers by letting them know what you’ll be needing later.
Default Priorities in the Browser
As mentioned before, the browser assigns different relative priorities to
different types of resources based on how critical they might be. So, for
<script> tag in your page’s
<head> would be loaded in Chrome at a
High priority (below CSS, at Highest), but that priority would change to
Low if it has the async attribute (meaning it can be loaded and run
Priorities become important when investigating loading performance in your site. Beyond the usual techniques of measuring and analyzing the critical rendering path, it’s useful to know Chrome’s priority for each resource. You can find that in the Network panel in Chrome Developer Tools. Here’s what it looks like:
These priorities give you an idea of how much relative importance the browser attributes to each resource. And remember that subtle differences are enough for the browser to assign a different priority; for example, an image that is part of the initial render is prioritized higher than an image that starts offscreen. If you’re curious about priorities, this article by Addy Osmani digs a lot deeper into the current state of priorities in Chrome.
So what can you do if you find any resources that are marked with a different priority than the one you’d want?
This article dives into three different declarative solutions, which are all
<link> types. If your resources are crucial to the user
experience but are being loaded at too low a priority, you can try fixing that
in one of two ways: preload or preconnect. On the other hand, if you’d like
the browser to fetch some resources only when it’s done dealing with everything
else, try prefetch.
Let’s look at all three!
<link rel="preload"> informs the browser that a resource is needed as
part of the current navigation, and that it should start getting fetched as soon
as possible. Here’s how you use it:
<link rel="preload" as="script" href="super-important.js"> <link rel="preload" as="style" href="critical.css">
Most of this is probably what you’d expect, except perhaps for the “as” attribute. This allows you to tell the browser the type of the resource you’re loading, so that it can be handled correctly. The browser doesn't use the preloaded resource unless the correct type is set. The resource is loaded with the same priority as it would otherwise, but now the browser knows about it ahead of time, allowing for the download to start earlier.
<link rel="preload"> is a compulsory instruction to the browser;
unlike the other resource hints we’ll be talking about, it’s something the
browser must do, rather than merely an optional hint. This makes it particularly
important to test carefully, to insure that you’re not accidentally causing
anything to fetch twice by using it, or fetching something that’s not needed.
Resources that are fetched using
<link rel="preload">, but not used by the
current page within 3 seconds will trigger a warning in the Console in Chrome
Developer Tools, so be sure to keep an eye out for these!
Fonts are a great example of late-discovered resources that must be fetched, often sitting at the bottom of one of several CSS files loaded by a page.
In order to reduce the amount of time the user has to wait for the text content
of your site, as well as avoid jarring flashes between system fonts and your
preferred ones, you can use
<link rel="preload"> in your HTML to let the
browser know immediately that a font is needed.
<link rel="preload" as="font" crossorigin type="font/woff2" href="myfont.woff2">
Note that the use of
crossorigin here is important; without this attribute,
the preloaded font is ignored by the browser, and a new fetch takes
place. This is because fonts are expected to be fetched anonymously by the
browser, and the preload request is only made anonymous by using the
When talking about page performance, one useful concept is the “critical path”. The critical path refers to the resources that must be loaded before your initial render. These resources, like CSS, are critical to getting the first pixels on the user’s screen.
Previously, the recommendation was to inline this content into your HTML. However, in a multi-page, server-side rendered scenario, this quickly grows into a lot of wasted bytes. It also makes versioning harder, as any change in the critical code invalidates any page that has it inlined.
<link rel="preload"> allows you to keep the benefits of
individual file versioning and caching, while giving you mechanism to request
the resource as soon as possible.
<link rel="preload" as="script" href="super-important.js"> <link rel="preload" as="style" href="critical.css">
With preload, there is one downside: you’re still subject to an extra roundtrip. This extra roundtrip comes from the fact that the browser first has to fetch the HTML, and only then does it find out about the next resources.
One way around the extra roundtrip is to use HTTP/2 push instead, where you preemptively attach the critical assets to the same connection through which you’re sending the HTML. This guarantees that there’s no downtime between the user’s browser retrieving the HTML and starting the download of the critical assets. Be mindful when using HTTP/2 push, though, as it’s a very forceful way of controlling the user’s bandwidth usage (“server knows best”), and leaves the browser very little room for making its own decisions, such as not retrieving a file that is already in its cache!
<link rel="preconnect"> informs the browser that your page intends to
establish a connection to another origin, and that you’d like the process to
start as soon as possible.
Establishing connections often involves significant time in slow networks, particularly when it comes to secure connections, as it may involve DNS lookups, redirects, and several round trips to the final server that handles the user’s request. Taking care of all this ahead of time can make your application feel much snappier to the user without negatively affecting the use of bandwidth. Most of the time in establishing a connection is spent waiting, rather than exchanging data.
Informing the browser of your intention is as simple as adding a link tag to your page:
<link rel="preconnect" href="https://example.com">
In this case, we’re letting the browser know that we intend to connect to
example.com and retrieve content from there.
Bear in mind that while
<link rel="preconnect"> is pretty cheap, it can still
take up valuable CPU time, particularly on secure connections. This is
especially bad if the connection isn’t used within 10 seconds, as the browser
closes it, wasting all of that early connection work.
In general, try to use
<link rel="preload"> wherever you can, as it’s a more
comprehensive performance tweak, but do keep
<link rel="preconnect"> in your
toolbelt for the edge cases. Let’s look at a couple of them.
Use-case: Knowing Where From, but not What You're Fetching
Due to versioned dependencies, you sometimes end up in a situation where you know you’ll be retrieving a resource from a given CDN, but not the exact path for it. In other cases, one of several resources may be retrieved, depending on media queries or runtime feature checks on the user’s browser.
In these situations, and if the resource you’ll be fetching is important, you may want to save as much time as possible by pre-connecting to the server. The browser won’t begin fetching the file before it needs it (that is, once the request is made from your page somehow), but at least it can handle the connection aspects ahead of time, saving the user from waiting for several roundtrips.
Use-case: Streaming Media
Another example where you may want to save some time in the connection phase, but not necessarily start retrieving content right away, is when streaming media from a different origin.
Depending on how your page handles the streamed content, you may want to wait until your scripts have loaded and are ready to process the stream. Preconnect helps you cut the waiting time to a single roundtrip once you’re ready to start fetching.
<link rel="prefetch"> is somewhat different from
<link rel="preload"> and
<link rel="preconnect">, in that it doesn’t try to make something critical
happen faster; instead, it tries to make something non-critical happen earlier,
if there’s a chance.
It does this by informing the browser of a resource that is expected to be needed as part of a future navigation or user interaction, for example, something that might be needed later, if the user takes the action we’re expecting. These resources are fetched at the Lowest priority in Chrome, when the current page is done loading and there’s bandwidth available.
This means that
prefetch is most suitable to preempt what the user might be
doing next, and prepare for it, such as retrieving the first product details
page in a list of results, or retrieving the next page in paginated content.
<link rel="prefetch" href="page-2.html">
Bear in mind that prefetch doesn’t work recursively, though. In the example
above you’d only be retrieving the HTML; any resources that
would not be downloaded ahead of time unless you explicitly prefetch them as
Prefetch Doesn't Work as an Override
It’s important to note that you can’t use
<link rel="prefetch"> as a way of
lowering the priority of an existing resource. In the following HTML, you might
think that declaring
optional.css in a prefetch would lower its priority for
<html> <head> <link rel="prefetch" href="optional.css"> <link rel="stylesheet" href="optional.css"> </head> <body> Hello! </body> </html>
However, this will actually cause your stylesheet to be fetched twice (albeit with a potential cache hit on the second one), once at the default Highest priority, and once at the Lowest priority, as prefetch kicks off a separate fetch:
Double-fetching can be bad for users. In this case, not only would they have to wait for the render-blocking CSS, but they would also potentially have their bandwidth wasted by downloading the file twice. Remember their bandwidth may be metered. Be sure to analyze your network requests thoroughly, and watch out for any double-fetching!
Other Techniques and Tools
<link rel="preconnect">, and
(as well as the bonus
<link rel="dns-prefetch">) offer a
great way of declaratively letting the browser know about resources and
connections ahead of time, and tweaking when things happen, according to when
There’s a number of other tools and techniques you can use to tweak the priority
and timing at which your resources get loaded. Be sure to read up on
HTTP/2 server push;
IntersectionObserver to lazily load images and other media;
avoiding render-blocking CSS
with media queries and libraries like