Codelab: Preload critical assets to improve loading speed

In this codelab, the performance of the following web page is improved by preloading and prefetching a few resources:

App Screenshot

Measure

First measure how the website performs before adding any optimizations.

  • To preview the site, press View App. Then press Fullscreen fullscreen.

Run the Lighthouse performance audit (Lighthouse > Options > Performance) on the live version of your Glitch (see also Discover performance opportunities with Lighthouse).

Lighthouse shows the following failed audit for a resource that is fetched late:

Lighthouse: Preload key requests audit
  • Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.
  • Click the Network tab.
Network panel with late-discovered resource

The main.css file is not fetched by a Link element (<link>) placed in the HTML document, but a separate JavaScript file, fetch-css.js, attaches the Link element to the DOM after the window.onLoad event. This means that the file is only fetched after the browser finishes parsing and executing the JS file. Similarly, a web font (K2D.woff2) specified within main.css is only fetched once the CSS file has finished downloading.

The critical request chain represents the order of resources that are prioritized and fetched by the browser. For this web page, it currently looks like this:

├─┬ / (initial HTML file)
  └── fetch-css.js
    └── main.css
      └── K2D.woff2

Since the CSS file is on the third level of the request chain, Lighthouse has identified it as a late-discovered resource.

Preload critical resources

The main.css file is a critical asset that's needed immediately as soon as the page is loaded. For important files like this resource that are fetched late in your application, use a link preload tag to inform the browser to download it sooner by adding a Link element to the head of the document.

Add a preload tag for this application:

<head>
  <!-- ... -->
  <link rel="preload" href="main.css" as="style">
</head>

The as attribute is used to identify which type of resource is being fetched, and as="style" is used to preload stylesheet files.

Reload the application and take a look at the Network panel in DevTools.

Network panel with preloaded resource

Notice how the browser fetches the CSS file before the JavaScript responsible for fetching it has even been finished parsing. With preload, the browser knows to make a preemptive fetch for the resource with the assumption that it is critical for the web page.

If not used correctly, preload can harm performance by making unnecessary requests for resources that aren't used. In this application, details.css is another CSS file located at the root of the project but is used for a separate /details route. To show an example of how preload can be used incorrectly, add a preload hint for this resource as well.

<head>
  <!-- ... -->
  <link rel="preload" href="main.css" as="style">
  <link rel="preload" href="details.css" as="style">
</head>

Reload the application and take a look at the Network panel. A request is made to retrieve details.css even though it is not being used by the web page.

Network panel with unecessary preload

Chrome displays a warning in the Console panel when a preloaded resource is not used by the page within a few seconds after it has loaded.

Preload warning in console

Use this warning as an indicator to identify if you have any preloaded resources that are not being used immediately by your web page. You can now remove the unnecessary preload link for this page.

<head>
  <!-- ... -->
  <link rel="preload" href="main.css" as="style">
  <link rel="preload" href="details.css" as="style">
</head>

For a list of all the types of resources that can be fetched along with the correct values that should be used for the as attribute, refer to the MDN article on Preloading.

Prefetch future resources

Prefetch is another browser hint that can be used to make a request for an asset used for a different navigation route but at a lower priority than other important assets needed for the current page.

In this website, clicking the image takes you to a separate details/ route.

Details route

A separate CSS file, details.css, contains all the styles needed for this simple page. Add a link element to index.html to prefetch this resource.

<head>
  <!-- ... -->
  <link rel="prefetch" href="details.css">
</head>

To understand how this triggers a request for the file, open the Network panel in DevTools and uncheck the Disable cache option.

Disable cache in Chrome DevTools

Reload the application and notice how a very low priority request is made for details.css after all the other files have been fetched.

Network panel with prefetched resource

With DevTools open, click the image on the website to navigate to the details page. Since a link element is used in details.html to fetch details.css, a request is made for the resource as expected.

Details page network requests

Click the details.css network request in DevTools to view its details. You'll notice that the file is retrieved from the browser's disk cache.

Details request fetched from disk cache

By taking advantage of browser idle time, prefetch makes an early request for a resource needed for a different page. This speeds up future navigation requests by allowing the browser to cache the asset sooner and serve it from the cache when needed.

Preloading and prefetching with webpack

The Reduce JavaScript payloads with code splitting post explores the use of dynamic imports to split a bundle into multiple chunks. This is demonstrated with a simple application that dynamically imports a module from Lodash when a form is submitted.

Magic Sorter app that demonstrates code splitting

You can access the Glitch for this application here.

The following block of code, which lives in src/index.js, is responsible for dynamically importing the method when the button is clicked.

form.addEventListener("submit", e => {
  e.preventDefault()
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

Splitting a bundle improves page loading times by reducing its initial size. Version 4.6.0 of webpack provides support to preload or prefetch chunks that are imported dynamically. Using this application as an example, the lodash method can be prefetched at browser idle time; when a user presses the button, there is no delay for the resource to be fetched.

Use the specific webpackPrefetch comment parameter within a dynamic import to prefetch a particular chunk. Here is how it would look with this particular application.

form.addEventListener("submit", e => {
  e.preventDefault()
  import(/* webpackPrefetch: true */ 'lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

Once the application is reloaded, webpack injects a prefetch tag for the resource into the head of the document. This can be seen in the Elements panel in DevTools.

Elements panel with prefetch tag

Observing the requests in the Network panel also shows that this chunk is fetched with a low priority after all other resources have been requested.

Network panel with prefetched request

Although prefetch makes more sense for this use case, webpack also provides support for preloading chunks that are dynamically imported.

import(/* webpackPreload: true */ 'module')

Conclusion

With this codelab, you should have a solid understanding of how preloading or prefetching certain assets can improve the user experience of your site. It is important to mention that these techniques should not be used for every resource and using them incorrectly can harm performance. The best results are noticed by only preloading or prefetching selectively.

To summarize:

  • Use preload for resources that are discovered late but are critical to the current page.
  • Use prefetch for resources that are needed for a future navigation route or user action.

Not all browsers currently support both preload and prefetch. This means that not all users of your application may notice performance improvements.

If you would like more information about specific aspects of how preloading and prefetching can affect your web page, refer to these articles: