The Simplest Way to Load CSS Asynchronously
One of the most impactful things we can do to improve page performance and resilience is to load CSS in a way that does not delay page rendering. Thatâs because by default, browsers will load external CSS synchronouslyâhalting all page rendering while the CSS is downloaded and parsedâboth of which incur potential delays. Of course, at least a portion of a siteâs CSS should be loaded before the page should be allowed to start rendering, and to get that initial CSS to the browser immediately, we recommend inlining (or HTTP2 server-pushing) the CSS. For sites with a small amount of overall, that alone might be enough, but if the CSS is large (say, bigger than 15 to 20kb), it can help performance to split it up by priority. Once split, less-critical CSS should be loaded in the backgroundâAKA asynchronously. In this post, I aim to describe up our preferred way to do that these days, which has actually been around for years.
There are several ways to make CSS load asynchronously, but none are as intuitive as you might expect. Unlike script elements, there is no async or defer attribute to simply apply to a link element, so for years now weâve maintained the loadCSS project to make the process of loading async CSS a little easier. Recently though, browsers have standardized their CSS loading behavior, so a dedicated script like loadCSS to handle their minor differences is likely no longer necessary.
The code
Permalink to 'The code'Today, armed with a little knowledge of how the browser handles various link element attributes, we can achieve the effect of loading CSS asynchronously with a short line of HTML. Here it is, the simplest way to load a stylesheet asynchronously:
<link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">
Breaking that downâŚ
Permalink to 'Breaking that downâŚ'This line of HTML is concise, but itâs not very intuitive, so letâs break down whatâs going on here.
To start, the linkâs media attribute is set to print. âPrintâ is a media type that says, âapply this stylesheetâs rules for print-based media,â or in other words, apply them when the user tries to print the page. Admittedly, we want our stylesheet to apply to all media (especially screens) and not just print, but by declaring a media type that doesnât match the current environment, we can achieve an interesting and useful effect: the browser will load the stylesheet without delaying page rendering, asynchronously! Thatâs helpful, but itâs not all we want. We also want the CSS to actually apply to the screen environment once it loads. For that, we can use the onload attribute to set the linkâs media to all when it finishes loading.
Canât rel=preload do this too?
Permalink to 'Canât rel=preload do this too?'Yes, similarly! In the past year or two, weâve been using link[rel=preload] (instead of rel=stylesheet) to achieve a similar pattern as above (toggling the rel attribute once loaded instead of the media attribute, respectively). It still works fine to use that approach, however, there are a couple of drawbacks to consider when using preload. First, browser support for preload is still not great, so a polyfill (such as the one loadCSS provides) is necessary if you want to rely on it to fetch and apply a stylesheet across browsers. More importantly though, preload fetches files very early, at the highest priority, potentially deprioritizing other important downloads, and that may be higher priority than you actually need for non-critical CSS.
Fortunately, if you happen to want the high-priority fetch that rel=preload provides (in browsers that support it), you can combine it with the above pattern, like this:
<link rel="preload" href="/path/to/my.css" as="style">
<link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">
Given the simple and declarative nature of the code above, weâd choose that over a polyfill, so the print media toggle approach is our preference again now.
Why not use a faux media attribute?
Permalink to 'Why not use a faux media attribute?'Anyone who has followed our writing on this for the past several years might recall that weâve used media attribute values like âonly xâ to achieve the same effect as âprintâ by providing a value that doesnât match any environment, as x is a nonsense media type. When browsers encounter media types that donât match, they currently treat them all the same - they load the file anyway. That said, some browser teams are starting to consider differentiating between non-matching media types and those that are not valid (or arenât recognized by the browser at all), and potentially not requesting files that are linked using invalid media types. This would break a lot of existing CSS loading implementations, so itâs not likely, but for safety sake we recommend using a valid, non-matching type like print.
You may not need loadCSSâŚ
Permalink to 'You may not need loadCSSâŚ'We continue to maintain loadCSS and find it useful in some situations, particularly for programatically fetching a CSS file from JavaScript, like this: loadCSS("/path/to/my.css"). If youâre already using loadCSS or its rel=preload polyfill pattern, you donât necessarily need to change anything. Internally, it uses the same mechanics described in this article.
More and more though, weâre finding that the simple HTML approach may be all you need. And simple is often best.
Thanks for reading!
Permalink to 'Thanks for reading!'Questions? Hit us up on Twitter!