Preload critical assets to improve loading speed

When you open a web page, the browser requests the HTML document from a server, parses its contents, and submits separate requests for any referenced resources. As a developer, you already know about all the resources your page needs and which of them are the most important. You can use that knowledge to request the critical resources ahead of time and speed up the loading process. This post explains how to achieve that with <link rel="preload">.

How preloading works

Preloading is best suited for resources typically discovered late by the browser.

Screenshot of Chrome DevTools Network panel.
In this example, Pacifico font is defined in the stylesheet with a @font-face rule. The browser loads the font file only after it has finished downloading and parsing the stylesheet.

By preloading a certain resource, you are telling the browser that you would like to fetch it sooner than the browser would otherwise discover it because you are certain that it is important for the current page.

Screenshot of Chrome DevTools Network panel after applying preloading.
In this example, Pacifico font is preloaded, so the download happens in parallel with the stylesheet.

The critical request chain represents the order of resources that are prioritized and fetched by the browser. Lighthouse identifies assets that are on the third level of this chain as late-discovered. You can use the Preload key requests audit to identify which resources to preload.

Lighthouse's preload key requests audit.

You can preload resources by adding a <link> tag with rel="preload" to the head of your HTML document:

<link rel="preload" as="script" href="critical.js">

The browser caches preloaded resources so they are available immediately when needed. (It doesn't execute the scripts or apply the stylesheets.)

Resource hints, for example, preconnectand prefetch, are executed as the browser sees fit. The preload, on the other hand, is mandatory for the browser. Modern browsers are already pretty good at prioritizing resources, that's why it's important to use preload sparingly and only preload the most critical resources.

Unused preloads trigger a Console warning in Chrome, approximately 3 seconds after the load event.

Chrome DevTools Console warning about unused preloaded resources.

Use cases

Preloading resources defined in CSS

Fonts defined with @font-face rules or background images defined in CSS files aren't discovered until the browser downloads and parses those CSS files. Preloading these resources ensures they are fetched before the CSS files have downloaded.

Preloading CSS files

If you are using the critical CSS approach, you split your CSS into two parts. The critical CSS required for rendering the above-the-fold content is inlined in the <head> of the document and non-critical CSS is usually lazy-loaded with JavaScript. Waiting for JavaScript to execute before loading non-critical CSS can cause delays in rendering when users scroll, so it's a good idea to use <link rel="preload"> to initiate the download sooner.

Preloading JavaScript files

Because browsers don't execute preloaded files, preloading is useful to separate fetching from execution which can improve metrics such as Time to Interactive. Preloading works best if you split your JavaScript bundles and only preload critical chunks.

How to implement rel=preload

The simplest way to implement preload is to add a <link> tag to the <head> of the document:

<head>
  <link rel="preload" as="script" href="critical.js">
</head>

Supplying the as attribute helps the browser set the priority of the prefetched resource according to its type, set the right headers, and determine whether the resource already exists in the cache. Accepted values for this attribute include: script, style, font, image, and others.

Some types of resources, such as fonts, are loaded in anonymous mode. For those you must set the crossorigin attribute with preload:

<link rel="preload" href="ComicSans.woff2" as="font" type="font/woff2" crossorigin>

<link> elements also accept a type attribute, which contains the MIME type of the linked resource. The browsers use the value of the type attribute to make sure that resources get preloaded only if their file type is supported. If a browser doesn't support the specified resource type, it will ignore the <link rel="preload">.

You can also preload any type of resource via the Link HTTP header:

Link: </css/style.css>; rel="preload"; as="style"

A benefit of specifying preload in the HTTP Header is that the browser doesn't need to parse the document to discover it, which can offer small improvements in some cases.

Preloading JavaScript modules with webpack

If you are using a module bundler that creates build files of your application, you need to check if it supports the injection of preload tags. With webpack version 4.6.0 or later, preloading is supported through the use of magic comments inside import():

import(_/* webpackPreload: true */_ "CriticalChunk")

If you are using an older version of webpack, use a third-party plugin such as preload-webpack-plugin.

Effects of preloading on Core Web Vitals

Preloading is a powerful performance optimization that has an effect on loading speed. Such optimizations can lead to changes in your site's Core Web Vitals, and it's important to be aware them.

Largest Contentful Paint (LCP)

Preloading has a powerful effect on Largest Contentful Paint (LCP) when it comes to fonts and images, as both images and text nodes can be LCP candidates. Hero images and large runs of text that are rendered using web fonts can benefit significantly from a well-placed preload hint, and should be used when there are opportunities to deliver these important bits of content to the user faster.

However, you want to be careful when it comes to preloading—and other optimizations! In particular, avoid preloading too many resources. If too many resources are prioritized, effectively none of them are. The effects of excessive preload hints will be especially detrimental to those on slower networks where bandwidth contention will be more evident.

Instead, focus on a few high-value resources that you know will benefit from a well-placed preload. When preloading fonts, ensure that you're serving fonts in WOFF 2.0 format to reduce resource load time as much as possible. Since WOFF 2.0 has excellent browser support, using older formats such as WOFF 1.0 or TrueType (TTF) will delay your LCP if the LCP candidate is a text node.

When it comes to LCP and JavaScript, you'll want to ensure that you're sending complete markup from the server in order for the browser's preload scanner to work properly. If you're serving up an experience that relies entirely on JavaScript to render markup and can't send server-rendered HTML, it would be advantageous to step in where the browser preload scanner can't and preload resources that would only otherwise be discoverable when the JavaScript finishes loading and executing.

Cumulative Layout Shift (CLS)

Cumulative Layout Shift (CLS) is an especially important metric where web fonts are concerned, and CLS has significant interplay with web fonts that use the font-display CSS property to manage how fonts are loaded. To minimize web font-related layout shifts, consider the following strategies:

  1. Preload fonts while using the default block value for font-display. This is a delicate balance. Blocking the display of fonts without a fallback can be considered a user experience problem. On one hand, loading fonts with font-display: block; eliminates web font-related layout shifts. On the other hand, you still want to get those web fonts loaded as soon as possible if they're crucial to the user experience. Combining a preload with font-display: block; may be an acceptable compromise.
  2. Preload fonts while using the fallback value for font-display. fallback is a compromise between swap and block, in that it has an extremely short blocking period.
  3. Use the optional value for font-display without a preload. If a web font isn't crucial to the user experience, but it is still used to render a significant amount of page text, consider using the optional value. In adverse conditions, optional will display page text in a fallback font while it loads the font in the background for the next navigation. The net result in these conditions is improved CLS, as system fonts will render immediately, while subsequent page loads will load the font immediately without layout shifts.

CLS is a difficult metric to optimize for when it comes to web fonts. As always, experiment in the lab, but trust your field data to determine if your font loading strategies are improving CLS or making it worse.

Responsiveness metrics

First Input Delay (FID) and Interaction to Next Paint are two metrics that gauge responsiveness to user input. Since the lion's share of interactivity on the web is driven by JavaScript, preloading JavaScript that powers important interactions may help to keep your FID and INP metrics as low as they can possibly be. However, be aware that FID is a load responsiveness metric and INP observes interactions throughout the entire page lifecycle—including during startup. Preloading too much JavaScript during startup can carry unintended negative consequences if too many resources are contending for bandwidth.

You'll also want to be careful about how you go about code splitting. Code splitting is an excellent optimization for reducing the amount of JavaScript loaded during startup, but interactions can be delayed if they rely on JavaScript loaded right at the start of the interaction. To compensate for this, you'll need to examine the user's intent, and inject a preload for the necessary chunk(s) of JavaScript before the interaction takes place. One example could be preloading JavaScript required for validating a form's contents when any of the fields in the form are focused.

Conclusion

To improve page speed, preload important resources that are discovered late by the browser. Preloading everything would be counterproductive so use preload sparingly and measure the impact in the real-world.