# Fast

# index

---
layout: collection
override:tags: []
date: 2018-11-05
pathName: fast
---

# Adaptive serving based on network quality

Loading a website can be a very different experience depending on the network
conditions. Everything is usually smooth when you are on a fast network, but
when you're on the go with a limited data plan and spotty connection, or stuck
with a laptop on slow coffee-shop Wi-Fi, it's a different story.

One way to deal with this is by adapting which assets you're serving to users
based on the quality of their connection. This is now possible with the
[Network Information API](https://developer.mozilla.org/docs/Web/API/Network_Information_API)
which enables web applications to access information about the user's network.

{% BrowserCompat 'api.NetworkInformation' %}

## Usage

There are many ways you can use this network information to improve the user
experience:

* Switch between serving high-definition and low-definition content based on the
  user's network.
* Decide whether to preload resources.
* Defer uploads and downloads when users are on a slow connection.
* Enable offline mode if the network quality is not good enough to load the app
  and use the features.
* Warn users that doing something (for example, watching video) over cellular could cost
  them money.
* Use it in your analytics to gather data on your users' network quality.

Many applications are already doing something similar. For example, YouTube,
Netflix and most other video (or video calling) services automatically adjust
the resolution during streaming. When Gmail is loading, it provides users with a
link to "load basic HTML (for slow connections)".

<figure>
  {% Img src="image/admin/zSTd1IMrb3UJfefdeRAt.png", alt="A link to load basic HTML version of Gmail when users are on slow connections", width="528", height="236" %}
</figure>

## How it works

The `navigator.connection` object contains information about a client's
connection. Its properties are explained in the table bellow.

<div class="table-wrapper">
  <table>
    <tr>
      <th>Property</th>
      <th>Explanation</th>
    </tr>
    <tr>
      <td><code>downlink</code></td>
      <td>The bandwidth estimate in megabits per second.</td>
    </tr>
    <tr>
      <td><code>effectiveType</code></td>
      <td>The effective type of the connection, with possible values <code>'slow-2g'</code>, <code>'2g'</code>, <code>'3g'</code>, or <code>'4g'</code> (covers 4g and higher). Determined based on the combination of <a href="https://wicg.github.io/netinfo/#effective-connection-types">round-trip time and downlink speed</a>. For example, fast downlink combined with high latency will have lower effectiveType due to latency.</td>
    </tr>
    <tr>
      <td><code>onchange</code></td>
      <td>An event handler that fires when connection information changes.</td>
    </tr>
    <tr>
      <td><code>rtt</code></td>
      <td>The estimated round-trip latency of the connection in milliseconds.</td>
    </tr>
    <tr>
      <td><code>saveData</code></td>
      <td>A boolean that defines whether the user has requested a reduced data usage mode.</td>
    </tr>
  </table>
</div>

Here's what this looks like when you run it in the browser's console:

{% Img src="image/admin/ayW8uxhh3S6I4MTipr0m.jpg", alt="Chrome DevTools console displaying the values of navigator.connection object's properties", width="800", height="523" %}

The `effectiveType` values are also available via
[Client Hints](https://www.chromestatus.com/feature/5407907378102272)
and allow you to communicate the browser's connection type to servers.

{% Aside %}
You can access Network Information API inside
[Service Workers](https://developer.mozilla.org/docs/Web/API/ServiceWorker)
to adapt to situations when users are offline.
{% endAside %}

The `onchange` event listener enables you to dynamically adapt to changes in
network quality. If you deferred uploads or downloads because of poor network
conditions, you can rely on the event listener to restart the transfer when it
detects better network conditions. You can also use it to notify users when the
network quality changes. For example, if they lost their Wi-Fi signal and were
dropped to a cellular network this can prevent accidental data transfers (and
charges 💸).

Use the `onchange` event listener as you would any other event listener:

```js
navigator.connection.addEventListener('change', doSomethingOnChange);
```

{% Aside %}
Network information API is
[supported in Chromium-based browsers](https://caniuse.com/#feat=netinfo)
since version 62.
{% endAside %}

## Conclusion

The potential benefits of the Network Information API are big, especially for
users on slow networks and applications that require a lot of bandwidth. Best of
all, it can be used as a progressive enhancement technique.

# Apply instant loading with the PRPL pattern

PRPL is an acronym that describes a pattern used to make web pages load and
become interactive, faster:

+  **Preload** the most important resources.
+  **Render** the initial route as soon as possible.
+  **Pre-cache** remaining assets.
+  **Lazy load** other routes and non-critical assets.

In this guide, learn how each of these techniques fit together but still can be
used independently to achieve performance results.

## Audit your page with Lighthouse

Run Lighthouse to identify opportunities for improvement aligned with the PRPL
techniques:

{% Instruction 'devtools-lighthouse', 'ol' %}
1. Select the **Performance** and **Progressive Web App** checkboxes.
1. Click **Run Audits** to generate a report.

For more information, see [Discover performance opportunities with Lighthouse](/discover-performance-opportunities-with-lighthouse).

## Preload critical resources

Lighthouse shows the following failed audit if a certain resource is parsed and
fetched late:

{% Img src="image/admin/tgcMfl3HJLmdoERFn7Ji.png", alt="Lighthouse: Preload key requests audit", width="745", height="97" %}

[**Preload**](https://developer.mozilla.org/docs/Web/HTML/Preloading_content)
is a declarative fetch request that tells the browser to request a resource as
soon as possible. Preload critical resources by adding a `<link>` tag with
`rel="preload"` to the head of your HTML document:

```html
<link rel="preload" as="style" href="css/style.css">
```

The browser sets a more appropriate priority level for the resource in order to
try to download it sooner while not delaying the `window.onload` event.

For more information about preloading critical resources, refer to the
[Preload critical assets](/preload-critical-assets) guide.

## Render the initial route as soon as possible

Lighthouse provides a warning if there are resources that delay [**First Paint**](/user-centric-performance-metrics/#important-metrics-to-measure),
the moment when your site renders pixels to the screen:

{% Img src="image/admin/gvj0jlCYbMdpLNtHu0Ji.png", alt="Lighthouse: Eliminate render-blocking resources audit", width="800", height="111" %}

To improve First Paint, Lighthouse recommends inlining critical JavaScript and
deferring the rest using
[`async`](/critical-rendering-path-adding-interactivity-with-javascript/),
as well as inlining critical CSS used above-the-fold. This improves performance
by eliminating round-trips to the server to fetch render-blocking assets.
However, inline code is harder to maintain from a development perspective and
cannot be cached separately by the browser.

Another approach to improve First Paint is to **server-side render** the initial
HTML of your page. This displays content immediately to the user while scripts
are still being fetched, parsed, and executed. However, this can increase the
payload of the HTML file significantly, which can harm [**Time to Interactive**](/tti/),
or the time it takes for your application to become interactive and can respond
to user input.

There is no single correct solution to reduce the First Paint in your
application, and you should only consider inlining styles and server-side
rendering if the benefits outweigh the tradeoffs for your application. You can
learn more about both of these concepts with the following resources.

+  [Optimize CSS Delivery](https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery)
+  [What is Server-Side Rendering?](https://www.youtube.com/watch?v=GQzn7XRdzxY)

<figure data-float="right">
  {% Img src="image/admin/xv1f7ZLKeBZD83Wcw6pd.png", alt="Requests/responses with service worker", width="800", height="1224" %}
</figure>

## Pre-cache assets

By acting as a proxy, **service workers** can fetch assets directly from the cache
rather than the server on repeat visits. This not only allows users to use your
application when they are offline, but also results in faster page load times on
repeat visits.

Use a third-party library to simplify the process of generating a service worker
unless you have more complex caching requirements than what a library can
provide. For example,
[Workbox](/workbox) provides a
collection of tools that allow you to create and maintain a service worker to
cache assets. For more information on service workers and offline reliability,
refer to the [service worker guide](/service-workers-cache-storage) in the reliability learning path.

## Lazy load

Lighthouse displays a failed audit if you send too much data over the network.

{% Img src="image/admin/Ml4hOCqfD4kGWfuKYVTN.png", alt="Lighthouse: Has enormous network payloads audit", width="800", height="99" %}

This includes all asset types, but large JavaScript payloads are especially
costly due to the time it takes the browser to parse and compile them.
Lighthouse also provides a warning for this when appropriate.

{% Img src="image/admin/aKDCV8qv3nuTVFt0Txyj.png", alt="Lighthouse: JavaScript boot-up time audit", width="797", height="100" %}

To send a smaller JavaScript payload that contains only the code needed when a
user initially loads your application, split the entire bundle and [lazy load](/reduce-javascript-payloads-with-code-splitting) chunks on demand.

Once you've managed to split your bundle, preload the chunks that are more
important (see the [Preload critical assets](/preload-critical-assets) guide).
Preloading ensures more important resources are fetched and downloaded sooner
by the browser.

Aside from splitting and loading different JavaScript chunks on demand,
Lighthouse also provides an audit for lazy loading non-critical images.

{% Img src="image/admin/sEgLhoYadRCtKFCYVM1d.png", alt="Lighthouse: Defer offscreen images audit", width="800", height="90" %}

If you load many images on your web page, defer all that are below the fold, or
outside the device viewport, when a page is loaded (see [Use lazysizes to lazyload images](/use-lazysizes-to-lazyload-images)).

## Next Steps

Now that you understand some of the basic concepts behind the PRPL pattern,
continue to the next guide in this section to learn more.
It's important to remember that not all of the techniques need to be
applied together. Any efforts made with any of the following will provide
noticeable performance improvements.

+  **Preload** critical resources.
+  **Render** the initial route as soon as possible.
+  **Pre-cache** remaining assets.
+  **Lazy load** other routes and non-critical assets.

You can read up more about [PRPL](https://www.patterns.dev/posts/prpl/) patterns.

# Avoid invisible text during font loading

Fonts are often large files that take awhile to load. To deal with this, some
browsers hide text until the font loads (the "flash of invisible text"). If
you're optimizing for performance, you'll want to avoid the "flash of invisible
text" and show content to users immediately using a system font (the "flash of
unstyled text").

## Display text immediately

This guide outlines two ways to achieve this: the first approach is very simple
but does not have universal browser
[support](https://caniuse.com/#search=font-display); the second approach is more
work but has full browser support. The best choice for you is the one that
you'll actually implement and maintain.

## Option #1: Use font-display

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>Before</th>
        <th>After</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>
<code>@font-face {
  font-family: Helvetica;
}
</code>
        </td>
        <td>
<code>@font-face {
  font-family: Helvetica;
  <strong>font-display: swap;</strong>
}
</code>
        </td>
      </tr>
    </tbody>
  </table>
</div>

[`font-display`](https://developer.mozilla.org/docs/Web/CSS/@font-face/font-display)
is an API for specifying font display strategy. `swap` tells the browser that
text using this font should be displayed immediately using a system font. Once
the custom font is ready, the system font is swapped out.

{% BrowserCompat 'css.at-rules.font-face.font-display' %}

If a browser does not support `font-display`, the browser continues to follow
its default behavior for loading fonts.

These are the default font-loading behaviors for common browsers:

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th><strong>Browser</strong></th>
        <th><strong>Default behavior if font is not ready…</strong></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Edge</td>
        <td>Uses a system font until font is ready. Swaps out font.</td>
      </tr>
      <tr>
        <td>Chrome</td>
        <td>
          Will hide text for up to 3 seconds. If text is still not ready, uses a
          system font until font is ready. Swaps out font.
        </td>
      </tr>
      <tr>
        <td>Firefox</td>
        <td>
          Will hide text for up to 3 seconds. If text is still not ready, uses a
          system font until font is ready. Swaps out font.
        </td>
      </tr>
      <tr>
        <td>Safari</td>
        <td>Hides text until font is ready.</td>
      </tr>
    </tbody>
  </table>
</div>

## Option #2: Wait to use custom fonts until they are loaded

With a bit more work, the same behavior can be implemented to work across all
browsers.

There are three parts to this approach:

+  Don't use a custom font on initial page load. This ensures that the
    browser displays text immediately using a system font.
+  Detect when your custom font is loaded. This can be accomplished with a
    couple lines of JavaScript code, thanks to the [FontFaceObserver](https://github.com/bramstein/fontfaceobserver) library.
+  Update page styling to use the custom font.

Here are the changes you can expect to make to implement this:

+  Refactor your CSS to not use a custom font on initial page load.
+  Add a script to your page. This script detects when the custom font is
    loaded and then will update the page styling.

{% Aside 'codelab' %}
[Use Font Face Observer to display text immediately](/codelab-avoid-invisible-text).
{% endAside %}

## Verify

Run Lighthouse to verify the site is using `font-display: swap` to display
text:

{% Instruction 'audit-performance', 'ol' %}

Confirm that the **Ensure text remains visible during webfont load** audit is passing.

# Browser-level image lazy loading for the web

Browser-level support for lazy loading images is now supported on the web! This video shows a [demo](https://mathiasbynens.be/demo/img-loading-lazy) of the feature:

<figure>
  <video controls autoplay loop muted>
    <source src="https://storage.googleapis.com/web-dev-assets/native-lazy-loading/lazyload.webm" type="video/webm">
    <source src="https://storage.googleapis.com/web-dev-assets/native-lazy-loading/lazyload.mp4" type="video/mp4">
  </video>
</figure>

You can use the `loading` attribute to lazy-load images without the need to write custom lazy loading code or use a separate JavaScript library. Let's dive into the details.

## Browser compatibility

{% BrowserCompat 'html.elements.img.loading' %}

Browsers that do not support the `loading` attribute simply ignore it without side effects.

## Why browser-level lazy loading?

According to the [HTTP Archive](https://httparchive.org/reports/page-weight), images are the most requested asset type for most websites and usually take up more bandwidth than any other resource. At the 90th percentile, sites send over 5 MB of images on desktop and mobile. That's a lot of [cat pictures](https://en.wikipedia.org/wiki/Cats_and_the_Internet).

Previously, there were two ways to defer the loading of off-screen images:

- Using the [Intersection Observer API](https://developer.chrome.com/blog/intersectionobserver/)
- Using `scroll`, `resize`, or `orientationchange` [event handlers](/lazy-loading-images/)

Either option can let developers include lazy loading functionality, and many developers have built third-party libraries to provide abstractions that are even easier to use. With lazy loading supported directly by the browser, however, there's no need for an external library. Browser-level lazymloading also ensures that deferred loading of images still works even if JavaScript is disabled on the client.

## The `loading` attribute

Chrome loads images at different priorities depending on where they're located with respect to the device viewport. Images below the viewport are loaded with a lower priority, but they're still fetched as the page loads.

You can use the `loading` attribute to completely defer the loading of offscreen images that are reached by scrolling:

```html
<img src="image.png" loading="lazy" alt="…" width="200" height="200">
```

Here are the supported values for the `loading` attribute:

- `lazy`: Defer loading of the resource until it reaches a [calculated distance](#distance-from-viewport-thresholds) from the viewport.
- `eager`: Default loading behavior of the browser, which is the same as not including the attribute and means the image is loaded regardless of where it's located on the page. While this is the default, it can be useful to explicitly set this if your tooling automatically adds `loading="lazy"` if there is no explicit value, or if your linter complains if it is not explicitly set.

{% Aside 'caution' %}
  Images that are highly likely to be in-viewport, and in particular [LCP](/lcp/) images, [should not be lazy-loaded](#avoid-lazy-loading-images-that-are-in-the-first-visible-viewport).
{% endAside %}

### Relationship between the `loading` attribute and fetch priority

The `eager` value is simply an instruction to load the image as usual, without delaying the load further if it is off-screen. It does not imply that the image is loaded any quicker than another image without the `loading="eager"` attribute.

Browsers prioritize resources based on various heuristics, and the `loading` attribute just states _when_ the image resource is queued, not _how_ it is prioritized in that queue. `eager` just implies the usual eager queueing browsers use by default.

If you want to increase the fetch priority of an important image (for example the LCP image), then [Fetch Priority](/fetch-priority/) should be used with `fetchpriority="high"`.

Note that an image with `loading="lazy"` and `fetchpriority="high"` will still be delayed while it is off-screen, and then fetched with a high priority when it is nearly within the viewport. It would likely be fetched with a high priority in this case anyway, so this combination should not really be needed nor used.

### Distance-from-viewport thresholds

All images that are above the fold—that is, immediately viewable without scrolling—load normally. Those that are far below the device viewport are only fetched when the user scrolls near them.

Chromium's implementation of lazy loading tries to ensure that offscreen images are loaded early enough so that they have finished loading once the user scrolls near to them. By fetching nearby images well before they become visible in the viewport, we maximize the chance they are already loaded by the time they become visible.

Compared to JavaScript lazy loading libraries, the thresholds for fetching images that scroll into view may be considered conservative.

{% Aside %}
Experiments conducted using Chrome on Android suggest that on 4G, 97.5% of below-the-fold images that are lazy-loaded were fully loaded within 10ms of becoming visible. Even on slow 2G networks, 92.6% of below-the-fold images were fully loaded within 10ms. This means browser-level lazy loading offers a stable experience regarding the visibility of elements that are scrolled into view.
{% endAside %}

The distance threshold is not fixed and varies depending on several factors:

- The type of image resource being fetched
- The [effective connection type](https://googlechrome.github.io/samples/network-information/)

You can find the default values for the different effective connection types in the [Chromium source](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/settings.json5;l=963-995). These numbers, and even the approach of fetching only when a certain distance from the viewport is reached, may change in the future as the Chrome team improves heuristics to determine when to begin loading.

{% Aside %}
You can experiment with these different thresholds by [throttling the network](https://developer.chrome.com/docs/devtools/network/#throttle) in DevTools.
{% endAside %}

## Improved data-savings and distance-from-viewport thresholds

As of July 2020, Chrome has made significant improvements to align the image lazy loading distance-from-viewport thresholds to better meet developer expectations.

On fast connections (4G), we reduced Chrome's distance-from-viewport thresholds from `3000px` to `1250px` and on slower connections (3G or lower), changed the threshold from `4000px` to `2500px`. This change achieves two things:

* `<img loading=lazy>` behaves closer to the experience offered by JavaScript lazy loading libraries.
* The new distance-from-viewport thresholds still allow us to guarantee images have probably loaded by the time a user has scrolled to them.

You can find a comparison between the old vs. new distance-from-viewport thresholds for one of our demos on a fast connection (4G) below:

Old thresholds. vs new thresholds:

<figure>
  {% Img src="image/admin/xSZMqpbioBRwRTnenK8f.png", alt="The new and improved thresholds for image lazy loading, reducing the distance-from-viewport thresholds for fast connections from 3000px down to 1250px", width="800", height="460" %}
</figure>

and the new thresholds vs. LazySizes (a popular JS lazy loading library):

<figure>
  {% Img src="image/admin/oHMFvflk9aesT7r0iJbx.png", alt="The new  distance-from-viewport thresholds in Chrome loading 90KB of images compared to LazySizes loading in 70KB under the same network conditions", width="800", height="355" %}
</figure>

We are committed to working with the web standards community to explore better alignment in how distance-from-viewport thresholds are approached across different browsers.

### Images should include dimension attributes

While the browser loads an image, it does not immediately know the image's dimensions, unless these are explicitly specified. To enable the browser to reserve sufficient space on a page for images, it is recommended that all `<img>` tags include both `width` and `height` attributes. Without dimensions specified, [layout shifts](/cls/) can occur, which are more noticeable on pages that take some time to load.

```html
<img src="image.png" loading="lazy" alt="…" width="200" height="200">
```

Alternatively, specify their values directly in an inline style:

```html
<img src="image.png" loading="lazy" alt="…" style="height:200px; width:200px;">
```

The best practice of setting dimensions applies to `<img>` tags regardless of whether or not they are being loaded lazily. With lazy loading, this can become more relevant. Setting `width` and `height` on images in modern browsers also allows browsers to infer their intrinsic size.

In most scenarios images still lazy-load if dimensions are not included, but there are a few edge cases you should be aware of. Without `width` and `height` specified, image dimensions are 0×0 pixels at first. If you have a gallery of such images, the browser may conclude that all of them fit inside the viewport at the start, as each takes up practically no space and no image is pushed offscreen. In this case the browser determines that all of them are visible to the user and decides to load everything.

Also, [specifying image dimensions decreases the chances of layout shifts happening](https://www.youtube.com/watch?v=4-d_SoCHeWE). If you are unable to include dimensions for your images, lazy loading them can be a trade-off between saving network resources and potentially being more at risk of layout shift.

While lazy loading in Chromium is implemented in a way such that images are likely to be loaded once they are visible, there is still a small chance that they might not be loaded yet. In this case, missing `width` and `height` attributes on such images increase their impact on Cumulative Layout Shift.

{% Aside %}
  Take a look at this [demo](https://mathiasbynens.be/demo/img-loading-lazy) to see how the `loading` attribute works with 100 pictures.
{% endAside %}

Images that are defined using the `<picture>` element can also be lazy-loaded:

```html
<picture>
  <source media="(min-width: 800px)" srcset="large.jpg 1x, larger.jpg 2x">
  <img src="photo.jpg" loading="lazy">
</picture>
```

Although a browser will decide which image to load from any of the `<source>` elements, the `loading` attribute only needs to be included to the fallback `<img>` element.


## Avoid lazy loading images that are in the first visible viewport

You should avoid setting `loading=lazy` for any images that are in the first visible viewport. This is particularly relevant for LCP images. See the article [The performance effects of too much lazy-loading](/lcp-lazy-loading/) for more information.

It is recommended to only add `loading=lazy` to images which are positioned below the fold, if possible. Images that are eagerly loaded can be fetched right away, while images which are loaded lazily the browser currently needs to wait until it knows where the image is positioned on the page, which relies on the `IntersectionObserver` to be available.

Generally, any images within the viewport should be loaded eagerly using the browser's defaults. You do not need to specify `loading=eager` for this to be the case for in-viewport images.

```html
<!-- visible in the viewport -->
<img src="product-1.jpg" alt="..." width="200" height="200">
<img src="product-2.jpg" alt="..." width="200" height="200">
<img src="product-3.jpg" alt="..." width="200" height="200">

<!-- offscreen images -->
<img src="product-4.jpg" loading="lazy" alt="..." width="200" height="200">
<img src="product-5.jpg" loading="lazy" alt="..." width="200" height="200">
<img src="product-6.jpg" loading="lazy" alt="..." width="200" height="200">
```

## Graceful degradation

Browsers that do not support the `loading` attribute will ignore its presence. While these browsers will of course not get the benefits of lazy loading, including the attribute has no negative impact on them.

## FAQ

### Are there plans to automatically lazy-load images in Chrome?

Previously, Chromium automatically lazy-loaded any images that were well suited to being deferred if [Lite mode](https://blog.chromium.org/2019/04/data-saver-is-now-lite-mode.html) was enabled on Chrome for Android and the `loading` attribute was either not provided or set as `loading="auto"`. However, [Lite mode has been deprecated](https://support.google.com/chrome/thread/151853370/sunsetting-chrome-lite-mode-in-m100-and-older?hl=en) (as was the non-standard `loading="auto"`) and there are currently no plans to provide automatically lazy-load of images in Chrome.

### Can I change how close an image needs to be before a load is triggered?

These values are hardcoded and can't be changed through the API. However, they may change in the
future as browsers experiment with different threshold distances and variables.

### Can CSS background images take advantage of the `loading` attribute?

No, it can currently only be used with `<img>` tags.

### Is there a downside to lazy loading images that are within the device viewport?

It is safer to avoid putting `loading=lazy` on above-the-fold images, as Chrome won't preload `loading=lazy` images in the [preload scanner](/preload-scanner/) and will also delay fetching such images until all layout is complete. See [Avoid lazy-loading images that are in the first visible viewport](#avoid-lazy-loading-images-that-are-in-the-first-visible-viewport) for more information.

### How does the `loading` attribute work with images that are in the viewport but not immediately visible (for example: behind a carousel, or hidden by CSS for certain screen sizes)?

Using `loading="lazy"` _may_ prevent them being loaded when they are not visible but within the [calculated-distance](#distance-from-viewport-thresholds). For example, Chrome, Safari and Firefox do not load images using `display: none;` styling—either on the image element or on a parent element. However, other techniques to hide images—such as using `opacity:0` styling—will still result in the images being loaded. Always test your implementation thoroughly to ensure it's acting as intended.

### What if I'm already using a third-party library or a script to lazy-load images?

With full support of native lazy loading now available in modern browsers, you may wish to reconsider if you still need a third-party library or script to lazy-load images.

One reason to continue to use a third-party library along with `loading="lazy"` is to provide a polyfill for browsers that do not support the attribute, or to have more control over when lazy loading is triggered.

### How do I handle browsers that don't support lazy loading?

Create a polyfill or use a third-party library to lazy-load images on your site. The `loading`
property can be used to detect if the feature is supported in the browser:

```js
if ('loading' in HTMLImageElement.prototype) {
  // supported in browser
} else {
  // fetch polyfill/third-party library
}
```

For example, [lazysizes](https://github.com/aFarkas/lazysizes) is a popular JavaScript lazy loading library. You can detect support for the `loading` attribute to load lazysizes as a fallback library only when `loading` isn't supported. This works as follows:

- Replace `<img src>` with `<img data-src>` to avoid an eager load in unsupported browsers. If the `loading` attribute is supported, swap `data-src` for `src`.
- If `loading` is not supported, load a fallback (lazysizes) and initiate it. As per lazysizes docs, you use the `lazyload` class as a way to indicate to lazysizes which images to lazy-load.

```html
<!-- Let's load this in-viewport image normally -->
<img src="hero.jpg" alt="…">

<!-- Let's lazy-load the rest of these images -->
<img data-src="unicorn.jpg" alt="…" loading="lazy" class="lazyload">
<img data-src="cats.jpg" alt="…" loading="lazy" class="lazyload">
<img data-src="dogs.jpg" alt="…" loading="lazy" class="lazyload">

<script>
  if ('loading' in HTMLImageElement.prototype) {
    const images = document.querySelectorAll('img[loading="lazy"]');
    images.forEach(img => {
      img.src = img.dataset.src;
    });
  } else {
    // Dynamically import the LazySizes library
    const script = document.createElement('script');
    script.src =
      'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.1.2/lazysizes.min.js';
    document.body.appendChild(script);
  }
</script>
```

Here's a [demo](https://lazy-loading.firebaseapp.com/lazy_loading_lib.html) of this pattern. Try it out in an older browser to see the fallback in action.

{% Aside %}
  The lazysizes library also provides a [loading plugin](https://github.com/aFarkas/lazysizes/tree/gh-pages/plugins/native-loading)
  that uses browser-level lazy loading when available but falls back to the library's custom functionality when needed.
{% endAside %}

### Is lazy loading for iframes also supported in browsers?

{% BrowserCompat 'html.elements.iframe.loading' %}

`<iframe loading=lazy>` has also been standardized and is already implemented in Chromium and Safari. This allows you to lazy-load iframes using the `loading` attribute. See [this dedicated article about iframe lazy-loading](/iframe-lazy-loading/) for more information.

### How does browser-level lazy loading affect advertisements on a web page?

All ads displayed to the user in the form of an image or iframe lazy-load just like any other image or iframe.

### How are images handled when a web page is printed?

All images and iframes are immediately loaded if the page is printed. See [issue #875403](https://bugs.chromium.org/p/chromium/issues/detail?id=875403) for details.

### Does Lighthouse recognize browser-level lazy loading?

[Lighthouse 6.0](/lighthouse-whats-new-6.0/) and above factor in approaches for offscreen image lazy loading that may use different thresholds, allowing them to pass the [Defer offscreen images](https://developer.chrome.com/docs/lighthouse/performance/offscreen-images/) audit.

## Conclusion

Baking in support for lazy loading images can make it significantly easier for you to improve the performance of your web pages.

Are you noticing any unusual behavior with this feature enabled in Chrome? [File a bug](https://bugs.chromium.org/p/chromium/issues/entry?summary=%5BLazyLoad%5D:&comment=Application%20Version%20%28from%20%22Chrome%20Settings%20%3E%20About%20Chrome%22%29:%20%0DAndroid%20Build%20Number%20%28from%20%22Android%20Settings%20%3E%20About%20Phone/Tablet%22%29:%20%0DDevice:%20%0D%0DSteps%20to%20reproduce:%20%0D%0DObserved%20behavior:%20%0D%0DExpected%20behavior:%20%0D%0DFrequency:%20%0D%3Cnumber%20of%20times%20you%20were%20able%20to%20reproduce%3E%20%0D%0DAdditional%20comments:%20%0D&labels=Pri-2&components=Blink%3ELoader%3ELazyLoad%2C)!

# Choose the right image format

The very first question you should ask yourself is whether an image is,
in fact, required to achieve the effect you are after.
Good design is simple and will also always yield the best performance.
If you can eliminate an image resource,
which often requires a large number of bytes relative to HTML, CSS, JavaScript and other assets on the page,
then that is always the best optimization strategy.
That said, a well-placed image can also communicate more information than a thousand words,
so it is up to you to find that balance.

Next, you should consider if there is an alternative technology that could deliver the desired results,
but in a more efficient manner:

* **CSS effects** (such as shadows or gradients) and CSS animations
can be used to produce resolution-independent assets that always look sharp at every resolution and zoom level,
often at a fraction of the bytes required by an image file.
* **Web fonts** enable use of beautiful typefaces
while preserving the ability to select, search,
and resize text&mdash;a significant improvement in usability.

If you ever find yourself encoding text in an image asset, stop and reconsider.
Great typography is critical to good design, branding, and readability,
but text-in-images delivers a poor user experience:
the text is not selectable, not searchable, not zoomable,
not accessible, and not friendly for high-DPI devices.
The use of web fonts requires its [own set of optimizations](https://www.igvita.com/2014/01/31/optimizing-web-font-rendering-performance/),
but it addresses all of these concerns and is always a better choice for displaying text.

## Choose the right image format

If you are sure an image is the correct option, you should carefully select the right kind of image for the job.

<figure>
  {% Img src="image/admin/dJuB2DQcbhtwD5VdPVlR.png", alt="Zoomed-in vector and raster images", width="585", height="313" %}
  <figcaption>Zoomed-in vector image (L) raster image (R)</figcaption>
</figure>

* [Vector graphics](https://en.wikipedia.org/wiki/Vector_graphics)
use lines, points, and polygons to represent an image.
* [Raster graphics](https://en.wikipedia.org/wiki/Raster_graphics)
represent an image by encoding the individual values of each pixel within a rectangular grid.

Each format has its own set of pros and cons.
Vector formats are ideally suited for images that consist of simple geometric shapes such as logos, text, or icons.
They deliver sharp results at every resolution and zoom setting,
which makes them an ideal format for high-resolution screens and assets that need to be displayed at varying sizes.

However, vector formats fall short when the scene is complicated (for example, a photo):
the amount of SVG markup to describe all the shapes can be prohibitively high
and the output may still not look "photorealistic".
When that's the case, that's when you should be using a raster image format
such as PNG, JPEG, WebP, or AVIF.

Raster images do not have the same nice properties of being resolution or zoom independent
&mdash;when you scale up a raster image you'll see jagged and blurry graphics.
As a result, you may need to save multiple versions of a raster image at various resolutions
to deliver the optimal experience to your users.

## Implications of high-resolution screens

There are two different kinds of pixels: CSS pixels and device pixels.
A single CSS pixel may correspond directly to a single device pixel, or may be backed by multiple device pixels.
What's the point? Well, the more device pixels there are, the finer the detail of the displayed content on the screen.

<figure>
  {% Img src="image/admin/oQV7qJ9fUMkYsKlUMrL4.png", alt="Three images showing the difference between CSS pixels and device pixels.", width="470", height="205" %}
  <figcaption>The difference between CSS pixels and device pixels.</figcaption>
</figure>

High DPI (HiDPI) screens produce beautiful results, but there is one obvious tradeoff:
image assets require more detail to take advantage of the higher device pixel counts.
The good news is, vector images are ideally suited for this task,
as they can be rendered at any resolution with sharp results&mdash;
you might incur a higher processing cost to render the finer detail,
but the underlying asset is the same and is resolution independent.

On the other hand, raster images pose a much larger challenge because they encode image data on a per-pixel basis.
Hence, the larger the number of pixels, the larger the filesize of a raster image.
As an example, let's consider the difference between a photo asset displayed at 100x100 (CSS) pixels:

<div class="table-wrapper">
<table>
<thead>
  <tr>
    <th>Screen resolution</th>
    <th>Total pixels</th>
    <th>Uncompressed filesize (4 bytes per pixel)</th>
  </tr>
</thead>
<tbody>
<tr>
  <td data-th="resolution">1x</td>
  <td data-th="total pixels">100 x 100 = 10,000</td>
  <td data-th="filesize">40,000 bytes</td>
</tr>
<tr>
  <td data-th="resolution">2x</td>
  <td data-th="total pixels">100 x 100 x 4 = 40,000</td>
  <td data-th="filesize">160,000 bytes</td>
</tr>
<tr>
  <td data-th="resolution">3x</td>
  <td data-th="total pixels">100 x 100 x 9 = 90,000</td>
  <td data-th="filesize">360,000 bytes</td>
</tr>
</tbody>
</table>
</div>

When we double the resolution of the physical screen,
the total number of pixels increases by a factor of four:
double the number of horizontal pixels, times double the number of vertical pixels.
Hence, a "2x" screen not just doubles, but quadruples the number of required pixels!

So, what does this mean in practice?
High-resolution screens enable you to deliver beautiful images, which can be a great product feature.
However, high-resolution screens also require high-resolution images, therefore:

* Prefer vector images whenever possible as they are resolution-independent and always deliver sharp results.
* If a raster image is required, serve [responsive images](/serve-responsive-images/).

## Features of different raster image formats

In addition to different lossy and lossless compression algorithms,
different image formats support different features such as animation and transparency (alpha) channels.
As a result, the choice of the "right format" for a particular image is a combination of desired visual results and functional requirements.

<div class="table-wrapper">
<table>
<thead>
  <tr>
    <th>Format</th>
    <th>Transparency</th>
    <th>Animation</th>
    <th>Browser</th>
  </tr>
</thead>
<tbody>
<tr>
  <td data-th="format"><a rel="noopener" href="http://en.wikipedia.org/wiki/Portable_Network_Graphics">PNG</a></td>
  <td data-th="transparency">Yes</td>
  <td data-th="animation">No</td>
  <td data-th="browser">All</td>
</tr>
<tr>
  <td data-th="format"><a rel="noopener" href="http://en.wikipedia.org/wiki/JPEG">JPEG</a></td>
  <td data-th="transparency">No</td>
  <td data-th="animation">No</td>
  <td data-th="browser">All</td>
</tr>
<tr>
  <td data-th="format"><a rel="noopener" href="http://en.wikipedia.org/wiki/WebP">WebP</a></td>
  <td data-th="transparency">Yes</td>
  <td data-th="animation">Yes</td>
  <td data-th="browser">All modern browsers. See <a href="https://caniuse.com/#feat=webp">Can I use?</a></td>
</tr>
<tr>
  <td data-th="format"><a rel="noopener" href="http://en.wikipedia.org/wiki/AVIF">AVIF</a></td>
  <td data-th="transparency">Yes</td>
  <td data-th="animation">Yes</td>
  <td data-th="browser">No. See <a href="https://caniuse.com/avif">Can I use?</a></td>
</tr>
</tbody>
</table>
</div>

There are two universally supported raster image formats: PNG and JPEG.
In addition to these formats, modern browsers support the newer format WebP, while only some support the newer AVIF format. Both of the newer formats offer better overall compression and more features. So, which format should you use?

WebP and AVIF will generally provide better compression than older formats,
and should be used where possible.
You can use WebP or AVIF images along with a JPEG or PNG image as a fallback.
See [Use WebP images](/serve-images-webp/) for more details.

In terms of older image formats, consider the following:

1. **Do you need animation? Use `<video>` elements.**
    * What about GIF? GIF limits the color palette to at most 256 colors,
      and creates significantly larger file sizes than `<video>` elements. See
      [Replace animated GIFs with video](/replace-gifs-with-videos/).
1. **Do you need to preserve fine detail with highest resolution? Use PNG or lossless WebP.**
    * PNG does not apply any lossy compression algorithms beyond the choice of the size of the color palette.
    As a result, it will produce the highest quality image,
    but at a cost of significantly higher filesize than other formats. Use judiciously.
    * WebP has a lossless encoding mode that may be more efficient than PNG.
    * If the image asset contains imagery composed of geometric shapes, consider converting it to a vector (SVG) format!
    * If the image asset contains text, stop and reconsider. Text in images is not selectable, searchable, or "zoomable".
    If you need to convey a custom look (for branding or other reasons), use a web font instead.
1. **Are you optimizing a photo, screenshot, or a similar image asset? Use JPEG, lossy WebP, or lossy AVIF.**
    * JPEG uses a combination of lossy and lossless optimization to reduce filesize of the image asset. Try several JPEG quality levels to find the best quality versus filesize tradeoff for your asset.
    * Lossy WebP or lossy AVIF may be acceptable JPEG alternatives, but be aware that WebP's lossy mode in particular discards some chroma information to achieve smaller images. This means that select colors may not be the same as an equivalent JPEG.

Finally, note that if you are using a WebView to render content in your platform-specific application,
then you have full control of the client and can use WebP exclusively!
Facebook and many others use WebP to deliver all of their images within their applications&mdash;
the savings are definitely worth it.

## Impact on Largest Contentful Paint (LCP)

Images may be [LCP candidates](/lcp/#what-elements-are-considered). That means the size of an image affects its [load time](/optimize-lcp/#3-reduce-resource-load-time). When an image is an LCP candidate, efficiently encoding that image is crucial to improving LCP.

You should strive to apply the advice given in this article so that the perceptual performance of a page is as fast as it can possibly be for all users. LCP is part of perceptual performance, as it measures how fast the largest (and therefore most perceivable) element on the page displays.

# Using the Chrome UX Report to look at performance in the field

The [Chrome UX Report](https://developer.chrome.com/docs/crux/) (informally known as CrUX) is a public dataset of real user experience data on millions of websites. Unlike lab data, CrUX data actually comes from [opted-in users](https://developer.chrome.com/docs/crux/methodology/#user-eligibility) in the field. It measures the [Core Web Vitals](/vitals/) metrics, including Largest Contentful Paint (LCP), First Input Delay (FID), and Cumulative Layout Shift (CLS), as well as diagnostic metrics like Time to First Byte (TTFB) and First Contentful Paint (FCP).

The dataset also contains qualitative dimensions
about the user experience, for example, the device and connection types, which
enables drilling down into user experiences grouped by similar technologies.
See the
[CrUX documentation](https://developer.chrome.com/docs/crux/methodology/#metrics)
for the full list of metrics.

Using this data, developers are able to understand the wide distribution of real
world user experiences between websites, segments of the web, or the web as a
whole. This is a big deal! The Chrome UX Report dataset is the first of its kind
to enable web developers to compare their real user performance against the
competition and industry.

## How to use it

There are four primary ways to extract insights from the Chrome UX Report,
ranging in complexity. For quick and easy analysis of website performance, the [CrUX Dashboard](https://developer.chrome.com/docs/crux/dashboard/) and
[PageSpeed Insights](https://pagespeed.web.dev/)
are the recommended tools. [BigQuery](https://developer.chrome.com/docs/crux/bigquery/)
trades some of the simplicity of the analysis for the power of customization
and more granular insights. And the [API](https://developer.chrome.com/docs/crux/api/) enables the integration of high-level data with other applications, and the [History API](https://developer.chrome.com/docs/crux/history-api/) provides historical data for trend analysis.

### CrUX Dashboard

The [CrUX Dashboard](https://developer.chrome.com/docs/crux/dashboard/) is a customizable data
visualization tool of websites' historical performance built on
[Looker Studio](https://cloud.google.com/looker-studio).
The data is sourced from the BigQuery dataset and all of the SQL queries are
handled for you under the hood. The dashboard shows the distribution of user
experiences, as captured by key performance metrics, and how it changes over
time. It also shows how the distributions of qualitative metrics like device
type and effective connection type change over time. Try the
[Looker Studio Dashboard guide](https://developer.chrome.com/docs/crux/dashboard/).

### PageSpeed Insights

[PageSpeed Insights](https://pagespeed.web.dev/)
(PSI) shows the most recent performance distributions broken down by desktop and
mobile users. Performance data is available for individual web pages
(in addition to entire origins) and is aggregated for the most recent 28 days of
data (as opposed to the previous calendar month on BigQuery). Using this tool is
as easy as entering a URL or origin in the search box on the web interface, and
the field performance data is displayed alongside prescriptive suggestions to
optimize the page. Try the
[PageSpeed Insights guide](https://developer.chrome.com/blog/chrome-ux-report-pagespeed-insights).

### CrUX on BigQuery

The CrUX database on [BigQuery](https://developer.chrome.com/docs/crux/bigquery/),
part of the Google Cloud Platform (GCP) with a web and command line interface,
hosts the raw data that aggregates key UX performance metrics for top origins
on the web. New tables are periodically added to the database covering the
previous calendar month. Developers can handcraft queries to mine the dataset
for specific insights. BigQuery requires knowledge of SQL and a GCP project with
billing enabled to run the queries. This is an especially useful tool for power
users who require low-level access to the data to create custom reports,
benchmarks, and reports about the state of the web. Try the
[BigQuery guide](https://developer.chrome.com/blog/chrome-ux-report-bigquery).

### CrUX API

The [CrUX API](https://developer.chrome.com/docs/crux/api/) is a free and RESTful interface for looking up origin or URL-level user experience data. The data is updated daily and aggregates the previous 28 days of data, similar to PageSpeed Insights. You can use this API to build your own applications on top of the real-user experience data in CrUX. Try the [CrUX API guide](https://developer.chrome.com/blog/chrome-ux-report-api).

### CrUX History API

The [CrUX History API](https://developer.chrome.com/docs/crux/history-api/) is a free and RESTful interface, similar to the daily API, for looking up origin or URL-level user experience data. The data is updated weekly and aggregates the previous 28 days of data, over a period of 6 months. You can use this API to build your own applications on top of the real-user experience data in CrUX. Try the [CrUX History API guide](https://developer.chrome.com/blog/chrome-ux-report-history-api/).

## How to get help

If you need any kind of support, there are a few channels to reach someone who
can help. The
[CrUX Google Group](https://groups.google.com/a/chromium.org/forum/#!forum/chrome-ux-report)
is a public forum for users of the dataset to ask questions and share analyses.
There is also a
[CrUX tag for Stack Overflow](https://stackoverflow.com/questions/tagged/chrome-ux-report)
if you need programming help with SQL or API access. And finally,
[@ChromeUXReport](https://twitter.com/ChromeUXReport) is the Twitter account you
can follow to ask questions and listen for product announcements.

## See it in action

To get more acquainted with the available data, walk through
step-by-step guides for using Looker Studio Dashboard, PageSpeed Insights, Bigquery, and both the daily and history API:

- [CrUX: Looker Studio Dashboard](https://developer.chrome.com/docs/crux/dashboard/)
- [CrUX: PageSpeed Insights](https://developer.chrome.com/blog/chrome-ux-report-pagespeed-insights)
- [CrUX: BigQuery](https://developer.chrome.com/blog/chrome-ux-report-bigquery)
- [CrUX: API](https://developer.chrome.com/blog/chrome-ux-report-api)
- [CrUX: History API](https://developer.chrome.com/blog/chrome-ux-report-history-api/)

# Adapt video to image serving based on network quality

{% include 'content/devtools-headsup.njk' %}

In this codelab, you'll learn how to adapt your content based on the network
quality. This page's background video will load only when users are on a fast
network. On slower networks, an image will load instead.

The
[Network Information API](https://developer.mozilla.org/docs/Web/API/NetworkInformation)
enables you to access information about the user's connection quality. You will
use its `effectiveType` property to decide when to serve a video and when to
serve an image. `effectiveType` can be `'slow-2g'`, `'2g'`, `'3g'`, or `'4g'`.

{% BrowserCompat 'api.NetworkInformation' %}

## Step 1: Check connection type

The `index.html` file contains a `<video>` tag to display the background video (line 22). The code in `script.js` loads the video by setting the video tag's `src` attribute. (The video loading code is described in more detail in [Step 2](/codelab-adapt-video-to-image-serving-based-on-network-quality/#step-2-load-video).)

To load the video conditionally, first check if the Network Information API is available; if it is, check the connection type.

1. In `script.js`, add an `if` statement that tests whether the `navigator.connection` object exists and whether it has the `effectiveType` property.
2. Add an `if` statement to check the `effectiveType` of the network.

```js
if (navigator.connection && !!navigator.connection.effectiveType) {
  if (navigator.connection.effectiveType === '4g') {
    // Only load video on the fastest connections.
  } else {
    // In any other case load the image.
  }
}
```

Wrap the existing video loading code in an `else` statement, so that video will
still load in browsers that don't support the Network Information API.

```js
if (navigator.connection && !!navigator.connection.effectiveType) {
  if (navigator.connection.effectiveType === '4g') {
    // video loading code
  } else {
    // image loading code
  }
} else {
  const video = document.getElementById('coverVideo');
  const videoSource = video.getAttribute('data-src');
  video.setAttribute('src', videoSource);

  video.setAttribute('style', 'height: 100%; width: 100%; display:inline');
}
```

## Step 2: Load video

If the `effectiveType` is `'4g'`, use the video loading code from the
beginning of the codelab.

```js
if (navigator.connection.effectiveType === '4g') {
  const video = document.getElementById('coverVideo');
  const videoSource = video.getAttribute('data-src');
  video.setAttribute('src', videoSource);
  video.setAttribute('style', 'height: 100%; width: 100%; display:inline');
} else {
  // image loading code
}
```

Here's how the video loading code works: the `<video>` tag doesn't download or display anything at first because its `src` attribute is not set. The video URL to load is specified using the `data-src` attribute.

```html
<video id="coverVideo" autoplay loop muted data-src="https://cdn.glitch.com/b6491350-b058-4eb6-aa6c-55c93122073e%2FMatrix%2C%20Console%2C%20Hacking%2C%20Code.mp4?1551464245607"></video>
```

[Data attributes](https://developer.mozilla.org/docs/Learn/HTML/Howto/Use_data_attributes) allow you to store extra information on standard HTML elements. A data element can be named anything, as long as it starts with "data-".

To actually display the video on the page, you need to get the value from `data-src` and set it as the video element's `src` attribute.

First, get the DOM element that contains the asset:

```js
const video = document.getElementById('coverVideo');
```

Then get the resource location from the `data-src` attribute:

```js
const videoSource = video.getAttribute('data-src');
```

And finally set that as the `src` attribute of the video element:

```js
video.setAttribute('src', videoSource);
```

The last line takes care of CSS positioning:

```js
video.setAttribute('style', 'height: 100%; width: 100%; display:inline');
```

## Step 3: Load image

To conditionally load an image on slower networks, use the same strategy as for
the video.

Add an image element to `index.html` (right after the video element), and use
the `data-src` attribute instead of the `src` attribute.

```html
<img id="coverImage" data-src="https://cdn.glitch.com/36529535-5976-40f8-979b-40c898b86bd0%2F36529535-5976-40f8-979b-40c898b86bd0_1_Sn80dgiwpMjBVrqjfiDbnA.jpg?1553003835358" />
```

In `script.js`, add code to set the image's `src` attribute depending on the
`effectiveType` of the network.

```js
if (navigator.connection.effectiveType === '4g') {
  const video = document.getElementById('coverVideo');
  const videoSource = video.getAttribute('data-src');
  video.setAttribute('src', videoSource);

  video.setAttribute('style', 'height: 100%; width: 100%; display:inline');
} else {
  const image = document.getElementById('coverImage');
  const imageSource = image.getAttribute('data-src');
  image.setAttribute('src', imageSource);

  image.setAttribute('style', 'height: 100%; width: 100%; display:inline');
}
```

## Try it out

To test it yourself:

{% Instruction 'preview', 'ol' %}
{% Instruction 'devtools-network', 'ol' %}
1. Click the **Throttling** drop-down, which is set to **No throttling** by default. Select **Fast 3G**.

{% Img src="image/admin/um0BRQOmsqGgZzWeLDHY.png", alt="DevTools Network tab with Fast 3G throttling option highlighted", width="723", height="198" %}

Now reload the page with Fast 3G still enabled. The app loads an image in the background instead of the video:

{% Img src="image/admin/wtQs6oCU8c5q23SQOnHo.png", alt="Matrix-like image background with 'NETWORK INFORMATION' text overlay", width="800", height="456" %}

## Extra Credit: Respond to changes

Remember how this API has an `onchange`
[event listener](/adaptive-serving-based-on-network-quality#how-it-works)?
You can use it for many things: dynamically adapting content such as video quality; restarting deferred data transfers when a change to a high-bandwidth network type is detected; or notifying users when the network quality changes.

Here's a simple example of how to use this listener. Add it to `script.js`. This
code will call the `displayNetworkInfo` function whenever the network
information changes.

```js
navigator.connection.addEventListener('change', displayNetworkInfo);
```

There's already an empty `<h2>` element on the `index.html` page. Now define the
`displayNetworkInfo` function so that it displays the network information in the
`<h2>` element and invoke the function.

```js
function displayNetworkInfo() {
  document.getElementById('connection').innerHTML = navigator.connection.effectiveType;
}

displayNetworkInfo();
```

Here's the final state of the [app on Glitch](https://glitch.com/~adaptive-serving-netinfo).

{% Img src="image/admin/0cTTeeAMgl5lD1PKRjy2.png", alt="Matrix-like video background with 'NETWORK INFORMATION 4g' text overlay", width="800", height="447" %}

To test it again:

{% Instruction 'preview', 'ol' %}
{% Instruction 'devtools-network', 'ol' %}
1. Click the **Throttling** drop-down, which is set to **No throttling** by default. Select **Fast 3G**.
{% Instruction 'reload-app', 'ol' %}

The app will update the network information to **3g**:

{% Img src="image/admin/WRp4ceBiuDQZoWmuaZx4.png", alt="Matrix-like video background with 'NETWORK INFORMATION 3g' text overlay", width="800", height="447" %}

# Art direction

{% Instruction 'preview' %}
- Reload the app using different browser sizes. Notice how different the images
  are: they're not only different sizes but also different croppings and
  aspect ratios.

## What's going on here?

[Art direction](https://developer.mozilla.org/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#Art_direction)
shows different images on different display sizes.

A responsive image loads different sizes of the same image. Art direction takes
this a step further and loads completely different images depending on the
display.

Use art direction to improve visual presentation. For example, the different
image croppings in this demo ensure that the point of interest (the flower) is
always shown in detail, regardless of the display. Art direction's benefits are
purely aesthetic; it provides no performance benefit over responsive images.

## View the code

- View `index.html` to see the art direction code for this demo.

## How the code works

Art direction uses the
[`<picture>`](https://developer.mozilla.org/docs/Web/HTML/Element/picture),
[`<source>`](https://developer.mozilla.org/docs/Web/HTML/Element/source),
and `<img>` tags.

**picture**

The `<picture>` tag provides a wrapper for zero or more `<source>` tags and one `<image>` tag.

**source**

The `<source>` tag specifies a media resource.

The browser uses the first `<source>` tag with a [media query](https://developer.mozilla.org/docs/Web/CSS/Media_Queries/Using_media_queries)
 that returns true. If none of the media queries match, the
browser falls back to loading the image specified by the `<img>`.
tag.

{% Aside 'gotchas' %}
- Be careful when ordering source tags. The browser uses the first
`<source>` tag with a matching media query, even if subsequent
`<source>` tags also have matching media queries.
- The value of the `srcset` attribute is an image filepath.
- Use images that are appropriately sized. Just because art
direction is used for aesthetic purposes, doesn't mean that it shouldn't be
performant too.
{% endAside %}

**img**

The `<img>` tag makes this code work on browsers that don't
support the `<picture>` tag.

If a browser does not support the `<picture>` tag, it loads the
image specified by the `<img>` tag.

{% Aside 'gotchas' %}
- The `<img>` tag should always be included, and it should always be listed
  last, after all `<source>` tags.
- The resource specified by the `<img>` tag should be a size that works on all
  devices, so it can be used as a fallback.
{% endAside %}

# Avoid flash of invisible text

This code lab shows you how to display text immediately using [Font Face
Observer](https://github.com/bramstein/fontfaceobserver).

## Add Font Face Observer

[Font Face Observer](https://github.com/bramstein/fontfaceobserver) is a script
that detects when a font loads. The
[`fontfaceobserver.js`](https://github.com/bramstein/fontfaceobserver/blob/master/fontfaceobserver.js)
file has already been saved to the project directory, so you don't need to add it
separately. However, you do need to add a script tag for it.

{% Instruction 'remix' %}
- Add a script tag for `fontfaceobserver.js` to `index.html`:

```html/1
  <div class="text">Some text.</div>
  <script src="fontfaceobserver.js" type="text/javascript"></script>
</body>
```

## Use Font Face Observer

### Create Observers

Create an observer for each font family that it is used on the page.

- Add the following script after the `fontfaceobserver.js` script. This creates
observers for the "Pacifico" and "Roboto" font families:

```html/1-4
  <script src="fontfaceobserver.js" type="text/javascript"></script>
  <script type="text/javascript">
    const pacificoObserver = new FontFaceObserver('Pacifico');
    const robotoObserver = new FontFaceObserver('Roboto');
  </script>
<body>
```

If you're ever unsure what font face observers you need to create, just look for
the `font-family` declarations in your CSS. Pass the `font-family` name of these declarations to
`FontFaceObserver()`. There is no need to create a font observer for
[fallback fonts](https://developer.mozilla.org/docs/Web/CSS/font-family#%3Cgeneric-name%3E).

For example, if your CSS was:

```css
font-family: "Times New Roman", Times, serif;
```

you would add `FontFaceObserver('Times New Roman')`. Times and serif are
fallback fonts, so you would not need to declare FontFaceObservers for them.

### Detect font load

The code for detecting a font load looks like this:

```js
robotoObserver.load().then(function(){
  console.log("Hooray! Font loaded.");
});
```

`robotoObserver.load()` is a promise that resolves when the font loads.

The demo site uses two different fonts, so you need to use [`Promise.all()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)
to wait until both fonts have loaded.

- Add this promise to your script, right below the FontFaceObservers that you
just declared:

```js
Promise.all([
  pacificoObserver.load(),
  robotoObserver.load()
]).then(function(){
  /* Do things */
});
```

#### ✔️Check-in

Your script should now look like this:

```html
<script type="text/javascript">
const pacificoObserver = new FontFaceObserver('Pacifico');
const robotoObserver = new FontFaceObserver('Roboto');

Promise.all([
  pacificoObserver.load(),
  robotoObserver.load()
]).then(function(){
  /* Do things */
});
</script>
```

### Apply `fonts-loaded` class

- Replace the `/* Do things */` comment in the script with this line:

```js
document.documentElement.classList.add("fonts-loaded");
```

This adds the `fonts-loaded` class to the document's root element (the
`<html>` tag) once both fonts have loaded.

#### ✔️Check-in

Your completed script should look like this:

```html
<script type="text/javascript">
  const pacificoObserver = new FontFaceObserver('Pacifico');
  const robotoObserver = new FontFaceObserver('Roboto');

  Promise.all([
    pacificoObserver.load(),
    robotoObserver.load()
  ]).then(function(){
    document.documentElement.className += " fonts-loaded";
  });
</script>
```

## Update CSS

Your page should be styled to use a system font initially and custom fonts once
the `fonts-loaded` class has been applied.

- Update the CSS:

```css/1-3,6-8/0,5
.header {
html.fonts-loaded .header {
  font-family: 'Pacifico', cursive;
}

.text
html.fonts-loaded .text {
  font-family: 'Roboto', sans-serif;
}
```

## Verify

{% Instruction 'preview' %}

If the page looks like this, then you've successfully implemented Font Face
Observer and gotten rid of the "Flash of Invisible Text."

{% Img src="image/admin/st2aEbPzhPIwyJiobkgO.png", alt="A heading in a cursive font.", width="572", height="246" %}

# Reduce JavaScript payloads with code splitting

{% include 'content/devtools-headsup.njk' %}

Most web pages and applications are made up of many different parts. Instead of
sending all the JavaScript that makes up the application as soon as the first
page is loaded, splitting the JavaScript into multiple chunks
improves page performance.

This codelab shows how to use **code splitting** to improve the performance of a
simple application that sorts three numbers.

{% Img src="image/admin/1lb0XbGP6M4eShkmYaQW.png", alt="A browser window shows an application titled Magic Sorter with three fields for inputting numbers and a sort button.", width="800", height="504" %}

## Measure

{% Aside %}
Since webpack is used in this application, any changes made to the code will trigger a new build which can take a few seconds. Once it completes, you should see your changes reflected in the application.
{% endAside %}

Like always, it's important to first measure how well a website performs before
attempting to add any optimizations.

{% Instruction 'preview', 'ol' %}
{% Instruction 'devtools-network', 'ol' %}
{% Instruction 'disable-cache', 'ol' %}
{% Instruction 'reload-app', 'ol' %}

{% Img src="image/admin/pyzLwutSzSx2qztQMXTM.png", alt="Network panel showing 71.2 KB JavaScript bundle.", width="800", height="153" %}

71.2 KB worth of JavaScript just to sort a few numbers in a simple application.
What gives?

In the source code (`src/index.js`), the `lodash` library is imported and used
in this application. [Lodash](https://lodash.com/) provides many useful utility
functions, but only a single method from the package is being used here.
Installing and importing entire third-party dependencies where only a small
portion of it is being utilized is a common mistake.

## Optimize

There are a few ways the bundle size can be trimmed:

1.  Write a custom sorting method instead of importing a third-party library
2.  Use the built in `Array.prototype.sort()` method to sort numerically
3.  Only import the `sortBy` method from `lodash` and not the entire library
4.  Download the code for sorting only when the user clicks the button

Options 1 and 2 are perfectly appropriate methods to reduce the bundle size (and
would probably make the most sense for a real application). However, those are
not used in this tutorial for the sake of teaching 😈.

{% Aside %}
The concept of removing unused code is explored in further detail in a
[separate guide](/remove-unused-code).
{% endAside %}

Both options 3 and 4 help improve the performance of this application. The
next few sections of this codelab cover these steps. Like any coding
tutorial, always try to write the code yourself instead of copy and pasting.

### Only import what you need

A few files need to be modified to only import the single method from `lodash`.
To begin with, replace this dependency in `package.json`:

```json
"lodash": "^4.7.0",
```

with this:

```json
"lodash.sortby": "^4.7.0",
```

Now in `src/index.js`, import this specific module:

```js/2/1
import "./style.css";
import _ from "lodash";
import sortBy from "lodash.sortby";
```

And update how the values are sorted::

```js/4/3
form.addEventListener("submit", e => {
  e.preventDefault();
  const values = [input1.valueAsNumber, input2.valueAsNumber, input3.valueAsNumber];
  const sortedValues = _.sortBy(values);
  const sortedValues = sortBy(values);

  results.innerHTML = `
    <h2>
      ${sortedValues}
    </h2>
  `
});
```

Reload the application, open DevTools, and take a look at the **Network** panel
once again.

{% Img src="image/admin/J8c1PhqMDOJKjoenzMCN.png", alt="Network panel showing 15.2 KB JavaScript bundle.", width="800", height="148" %}

For this application, the bundle size was reduced by over 4X with very little
work, but there's still more room for improvement.

### Code splitting

[**webpack**](https://webpack.js.org/) is one of the most popular open-source
module bundlers used today. In short, it _bundles_ all JavaScript modules (as
well as other assets) that make up a web application into static files that can
be read by the browser.

The single bundle used in this application can be split into two separate
chunks:

- One responsible for the code that makes up our initial route
- A secondary chunk that contains our sorting code

With the use of **dynamic imports**, a secondary chunk can be _lazy loaded,_ or
loaded on demand. In this application, the code that makes up the chunk can be
loaded only when the user presses the button.

Begin by removing the top-level import for the sort method in `src/index.js`:

```js//0
import sortBy from "lodash.sortby";
```

And import it within the event listener that fires when the button is pressed:

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

The `import()` feature is part of a
[proposal](https://github.com/tc39/proposal-dynamic-import) (currently at stage
3 of the TC39 process) to include the capability to dynamically import a module.
webpack has already included support for this and follows the same syntax laid
out by the proposal.

{% Aside %}
Read more about how dynamic imports work in this [Web Updates
article](https://v8.dev/features/dynamic-import).
{% endAside %}


`import()` returns a promise and when it resolves, the selected
module is provided which is split out into a separate chunk. After the module is
returned, `module.default` is used to reference the default
export provided by lodash. The promise is chained with another `.then` that
calls a `sortInput` method to sort the three input values. At the end of the
promise chain, .`catch()` is used to handle cases where the promise is rejected
due to an error.

{% Aside 'caution' %}
In a production application, always handle dynamic import
errors appropriately. A simple alert message similar to what is used here may
not provide the best user experience to let the user know something has
failed.
{% endAside %}

{% Aside %}
You may see a linting error that says:

```bash
Parsing error: 'import' and 'export' may only appear at the top level.
```

This is due to the fact that
the dynamic import syntax is still in the proposal stage and has not been
finalized. Although webpack already supports it, the settings for
[ESLint](https://eslint.org) (a JavaScript linting utility) used by
Glitch has not been updated to include this syntax yet, but it still works!
{% endAside %}

The last thing that needs to be done is to write the `sortInput` method at the
end of the file. This needs to be a function that _returns_ a function that
takes in the imported method from `lodash.sortBy`. The nested function can then
sort the three input values and update the DOM.

```js
const sortInput = () => {
  return (sortBy) => {
    const values = [
      input1.valueAsNumber,
      input2.valueAsNumber,
      input3.valueAsNumber
    ];
    const sortedValues = sortBy(values);

    results.innerHTML = `
      <h2>
        ${sortedValues}
      </h2>
    `
  };
}
```

## Monitor

Reload the application one last time and keep a close eye on the **Network**
panel again. Only a small initial bundle is downloaded as soon as the app
loads.

{% Img src="image/admin/f1QZcSozkaA1rj52YWGV.png", alt="Network panel showing 2.7 KB JavaScript bundle.", width="800", height="151" %}

After the button is pressed to sort the input numbers, the chunk that contains
the sorting code gets fetched and executed.

{% Img src="image/admin/LPNj3JpAmzsGppwJl5fs.png", alt="Network panel showing 2.7 KB JavaScript bundle followed by a 13.9 KB JavaScript bundle.", width="800", height="211" %}

Notice how the numbers still get sorted!

## Conclusion

Code splitting and lazy loading can be extremely useful techniques to trim down
the initial bundle size of your application, and this can directly result in
much faster page load times. However, there are some important things that need
to be considered before including this optimization in your application.

### Lazy loading UI

When lazy loading specific modules of code, it's important to consider how the
experience would be for users with weaker network connections. Splitting and
loading a very large chunk of code when a user submits an action can make it
seem like the application may have stopped working, so consider showing a
loading indicator of some sort.

### Lazy loading third-party node modules

It is not always the best approach to lazy load third-party dependencies in your
application and it depends on where you use them. Usually, third party
dependencies are split into a separate `vendor` bundle that can be cached since
they don't update as often. Read more about how the
[SplitChunksPlugin](https://webpack.js.org/plugins/split-chunks-plugin/) can
help you do this.

### Lazy loading with a JavaScript framework

Many popular frameworks and libraries that use webpack provide abstractions to
make lazy loading easier than using dynamic imports in the middle of your
application.

+  [Lazy loading modules with Angular](https://angular.io/guide/lazy-loading-ngmodules)
+  [Code splitting with React Router](https://reacttraining.com/react-router/web/guides/code-splitting)
+  [Lazy loading with Vue Router](https://router.vuejs.org/guide/advanced/lazy-loading.html)

Although it is useful to understand how dynamic imports work, always use the
method recommended by your framework/library to lazy load specific modules.

### Preloading and prefetching

Where possible, take advantage of browser hints such as `<link rel="preload">`
or `<link rel="prefetch">` to try and load critical modules even
sooner. webpack supports both hints through the use of magic comments in import
statements. This is explained in more detail in the
[Preload critical chunks](/preload-critical-assets) guide.

### Lazy loading more than code

Images can make up a significant part of an application. Lazy loading those that
are below the fold, or outside the device viewport, can speed up a website. Read
more about this in the
[Lazysizes](/use-lazysizes-to-lazyload-images) guide.

# Use density descriptors

{% Instruction 'preview' %}
- Reload the page using different devices to see the browser load different
  images.

You can use the device emulator for this. If you're looking for specific display
densities, here are some devices to try:

<div class="table-wrapper">
  <table>
    <tbody>
      <tr>
        <td>1x density</td>
        <td>Blackberry Playbook, many external monitors</td>
      </tr>
      <tr>
        <td>2x density</td>
        <td>iPad or IPhone 5/6</td>
      </tr>
      <tr>
        <td>3x density</td>
        <td>Galaxy S5 or iPhone X</td>
      </tr>
    </tbody>
  </table>
</div>

- Checkout `index.html` for the code that makes this work.

## How does it work?

The concept of density descriptors may be unfamiliar to most folks. To better
understand them, it helps to have a bit of background on how the browser works
with pixels.

## What are pixels

Let's start at the very beginning by defining what a pixel is. This sounds
simple, but "pixel" can actually have many meanings:

<dl>
  <dt>
    Device pixel (a.k.a. "physical pixel")
  </dt>
  <dd>
    The smallest dot of color that can be displayed on a device.
  </dd>
  <dt>
    Logical pixel
  </dt>
  <dd>
    Information that specifies the color at a particular location on a grid.
    This type of pixel has no inherent physical size.
  </dd>
  <dt>
    CSS pixel
  </dt>
  <dd>
    The CSS spec defines a pixel as a unit of physical measurement. 1 pixel =
1/96th of an inch.
  </dd>
</dl>

## Pixel Density

Pixel density (also referred to as "screen density" or "display density")
measures _the density of device pixels in a given physical area_. This is
commonly measured using pixels per inch (ppi).

For many years, 96 ppi was a very common display density (hence CSS defining a
pixel as 1/96th of an inch). Starting in the 1980s it was the default resolution
of Windows. In addition, it was the resolution of [CRT
monitors](https://en.wikipedia.org/wiki/Cathode_ray_tube).

This began to change as LED monitors became common in the 2000s. In particular,
Apple made a big splash in 2010 when it introduced Retina displays. These
displays had a minimum resolution of 192 ppi, which was twice the resolution of
"regular" displays (192 ppi/96 ppi = 2).

## window.devicePixelRatio

With the introduction of newer display technology, "device pixels" began to vary
in physical size and [shape](https://en.wikipedia.org/wiki/Pixel_aspect_ratio)
and were no longer the same size as "CSS pixels". The need to define the
relationship between the size of "device pixels" and "CSS pixels" is what led to
the introduction of the `devicePixelRatio` (sometimes called the "CSS Pixel
Ratio").

`devicePixelRatio` defines the relationship between device pixels and CSS pixels
for a particular device. A 192 ppi device has a `devicePixelRatio` of 2 (192
ppi/96 ppi = 2) because "2 of its display pixels are the size of 1 CSS pixel".

These days most devices have a device-pixel-ratio between 1.0 and 4.0.

{% Aside %}
This ratio doesn't have to be a whole number. `1.5`, `2.4`, and `2.5` are all
device-pixel-ratios of common devices.
{% endAside %}

- Determine the pixel density of a device by typing `window.devicePixelRatio`
in the console.

- View [this table](https://www.mydevice.io/#tab1) to see the pixel ratios of
common devices. Most are between 1.0 and 4.0.

So how do you actually apply this information?

## Size images based on device-pixel-ratios

In order for images to look their very best on high resolution screens, it's
necessary to provide different image versions for different `devicePixelRatios`.

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>Device Pixel Ratio</th>
        <th>Indicates that:</th>
        <th>
          On this device, an &lt;img&gt; tag with a CSS width of 250 pixels, will
          look best when the source image is...
        </th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>1 device pixel = 1 CSS pixel</td>
        <td>250 pixels wide</td>
      </tr>
      <tr>
        <td>2</td>
        <td>2 device pixels = 1 CSS pixel</td>
        <td>500 pixels wide</td>
      </tr>
      <tr>
        <td>3</td>
        <td>3 device pixels = 1 CSS pixel</td>
        <td>750 pixels wide</td>
      </tr>
    </tbody>
  </table>
</div>

Things to note:

+  The pixel dimensions listed in image editors, file directories, and
    other places are a measurement of logical pixels.
+  For higher resolution screens and larger displays you'll need images with
    larger dimensions. Merely enlarging smaller images defeats the purpose of
    serving multiple image versions. The browser would have done this anyway if
    a high resolution image was not provided.

{% Aside %}
Tools like [sharp](https://www.npmjs.com/package/sharp) make it easy
to create multiple sizes of an image. This is covered in more detail here.
{% endAside %}

## Use Density Descriptors to serve multiple <br> images

Density descriptors, in conjunction with the "srcset " attribute, can be used to
serve different images to different devicePixelRatios.

- Take a look at the `index.html` file and note the `<img>` element.

```html
<img src="flower.jpg"
  srcset="flower-1x.jpg 1x,
          flower-2x.jpg 2x,
          flower-3x.jpg 3x">
```

This example put into words:

-  `1x`, `2x`, and `3x` are all density descriptors that tell the browser
    the pixel density that an image is intended for. This saves the browser
    from needing to download an image to determine this information.
-  The browser can choose between three images: `flower-1x.jpg` (intended
    for browsers with a `1.0` pixel density), `flower-2x.jpg` (intended for
    browsers with a `2.0` pixel density), and `flower-3x.jpg` (intended for
    browsers with a `3.0` pixel density).
-  `flower.jpg` is the fallback image for browsers that do not support
    `srcset`.

How to use this:
- Use a devicePixelRatio and the `x` unit to write density descriptors. For
example, the density descriptor for many Retina screens
(`window.devicePixelRatio = 2`) would be written as `2x`.
- If a density descriptor isn't provided, it is assumed to be `1x`.
- Including density descriptors in filenames is a common convention (and will
help you keep track of files) but is not necessary. Images can have any
filename.
- There is no need to include a `sizes` attribute. The `sizes` attribute is only
used with width descriptors.

# Extract and inline critical CSS with Critical

{% include 'content/devtools-headsup.njk' %}

Whether you use a UI library or handcraft your styles, shipping a significant amount of CSS delays rendering because the browser must download and parse CSS files before it can show the page.

This responsive ice cream gallery is built with [Bootstrap](https://getbootstrap.com/). UI libraries like Bootstrap speed up development, but that often comes at the expense of bloated and unnecessary CSS, which can slow down your load times. Bootstrap 4 is 187 KB, while [Semantic UI](https://semantic-ui.com/), another UI library, is a whopping 730 KB uncompressed. Even when minified and gzipped, Bootstrap still weighs around 20 KB, well over the [14 KB threshold](/extract-critical-css/#14KB) for the first roundtrip.

[Critical](https://github.com/addyosmani/critical) is a tool that extracts, minifies and inlines [above-the-fold](/extract-critical-css) CSS. This allows above-the-fold content to be rendered as soon as possible, even if CSS for other parts of the page has not yet loaded. In this codelab, you'll learn how to use Critical's npm module.

## Measure

{% Instruction 'preview' %}

To run a Lighthouse audit on this site:

{% Instruction 'devtools-lighthouse', 'ol' %}
1. Click **Mobile**.
1. Select the **Performance** checkbox.
1. Clear the rest of the checkboxes in the Audits section.
1. Click **Simulated Fast 3G, 4x CPU Slowdown**.
1. Select the **Clear Storage** checkbox. With this option selected, Lighthouse will not load resources from the cache, which simulates how first-time visitors would experience the page.
1. Click **Run Audits**.

{% Img src="image/admin/bk0impRv9Mo0yDpbuIQi.png", alt="Audits panel of Chrome DevTools, powered by Lighthouse", width="800", height="1068" %}

When you run an audit on your machine, the exact results may vary, but in the filmstrip view, you'll notice the app has a blank screen for quite a while before finally rendering the content. This is why [First Contentful Paint (FCP)](/fcp/) is high and why overall performance score is not great.

{% Img src="image/admin/otfxnEjhjP9cNpSF8W4o.png", alt="Lighthouse audit showing performance score of 84, FCP 3 seconds and a filmstrip view of loading the app", width="710", height="356" %}

Lighthouse is here to help you fix performance issues, so look for solutions in the **Opportunities** section. **Eliminate render-blocking resources** is listed as an opportunity and that's where Critical shines!


{% Img src="image/admin/L0i6jb8G1eT0vr2V45vH.png", alt="Lighthouse audit 'Opportunities' section listing 'Eliminate render-blocking resources'", width="743", height="449" %}

## Optimize

{% Instruction 'remix' %}

To speed things up, Critical is already installed and an empty configuration file is included in the codelab.

In the configuration file `critical.js`, add a reference to Critical and then invoke the `critical.generate()` function. This function accepts an object containing the configuration.

```js
const critical = require('critical');

critical.generate({
	// configuration
},(err, output) => {
  if (err) {
    console.error(err);
  } else if (output) {
    console.log('Generated critical CSS');
  }
});
```

Error handling isn't mandatory, but it's an easy way to gauge the operation success in the console.

### Configure Critical

The table below contains some useful Critical options. You can check out all of the [available options on GitHub](https://github.com/addyosmani/critical#usage).

<table>
    <th>Option</th>
    <th>Type</th>
    <th>Explanation</th>
  <tr>
    <td><code>base</code></td>
    <td>string</td>
    <td>The base directory for your files.</td>
  </tr>
  <tr>
    <td><code>src</code></td>
    <td>string</td>
    <td>HTML source file.</td>
  </tr>
  <tr>
    <td><code>dest</code></td>
    <td>string</td>
    <td>The target for the output file. If CSS is inlined the output file is HTML. If not, the output is a CSS file.</td>
  </tr>
  <tr>
    <td><code>width</code>, <code>height</code></td>
    <td>numbers</td>
    <td>Viewport width and height in pixels.</td>
  </tr>
  <tr>
    <td><code>dimensions</code></td>
    <td>array</td>
    <td>Contains objects with width and height properties. These objects represent the viewports you want to target with above-the-fold CSS. If you have media queries in your CSS, this allows you to generate critical CSS that covers multiple viewport sizes.</td>
  </tr>
  <tr>
    <td><code>inline</code></td>
    <td>boolean</td>
    <td>When set to true, the generated critical CSS is inlined in the <head> of the HTML source file.</td>
  </tr>
  <tr>
    <td><code>minify</code></td>
    <td>boolean</td>
    <td>When set to true, Critical minifies the generated critical CSS. Can be omitted when extracting critical CSS for multiple resolutions because Critical automatically minifies it to avoid duplicate rule inclusion.</td>
  </tr>
</table>


Below is a configuration example for extracting critical CSS for multiple resolutions. Add it to `critical.js` or play around and tweak the options.

```js/1-14/
critical.generate({
  base: 'public/',
  src: './index.html',
  dest: './index.html',
  inline: true,
  dimensions: [
    {
      height: 500,
      width: 300,
    },
    {
      height: 720,
      width: 1280,
    },
  ]
}, (err, output) => {
  if (err) {
    console.error(err);
  } else if (output) {
    console.log('Generated critical CSS');
  }
});
```

In this example, `index.html` is both the source file and the destination file because the `inline` option is set to true. Critical first reads the HTML source file, extracts critical CSS and then overwrites `index.html` with critical CSS inlined in the `<head>`.

`dimensions` array has two viewport sizes specified: 300 x 500 for extra small screens and 1280 x 720 for standard laptop screens.

`minify` option is omitted because Critical automatically minifies the extracted CSS when there are multiple viewport sizes specified.

### Run Critical

Add Critical to your scripts in `package.json`:

```js/2-2/
scripts: {
  "start": "node server.js",
  "critical": "node critical.js"
}
```

{% Instruction 'console', 'ol' %}

To generate critical CSS, in the console, run:

```bash
npm run critical
refresh
```

<figure data-size="full">
  {% Img src="image/admin/alfJqwxI2WU3bUOT4xUZ.png", alt="Success message saying 'Generated critical CSS' in the console]()", width="243", height="45", class="fixed-width-img" %}
  <figcaption>Success message in the console</figcaption>
</figure>

{% Aside 'note' %}
Glitch console and editor don't automatically sync, so `refresh` command is neccessary to update the editor with files generated from the console.
{% endAside %}

Now in the `<head>` tag of `index.html`, generated critical CSS is inlined between `<style>` tags, followed by a script that loads the rest of the CSS asynchronously.

<figure>
  {% Img src="image/admin/NEQAkJJDV712y944PAxo.png", alt="index.html with inlined critical CSS", width="800", height="325" %}
  <figcaption>Inlined critical CSS</figcaption>
</figure>

## Measure again

Follow the steps from the beginning of the codelab to run Lighthouse performance audit again. The results you get will look similar to this:

 {% Img src="image/admin/7be1SdC4JpavF7338VuT.png", alt="Lighthouse audit showing performance score of 100, FCP 0.9 seconds and improved filmstrip view of loading the app", width="716", height="407" %}

{% Aside 'success' %}
The filmstrip view shows that content is rendered much sooner and this is reflected in improved paint metrics. And "Eliminate render-blocking resources" has been eliminated! 🎉
{% endAside %}

# Using Imagemin with Grunt

This Glitch already contains `grunt`, `grunt-cli`, and the `grunt-contrib-imagemin`
plugin. To add the configuration for Imagemin, you'll need to edit your
`gruntfile.js` file.

{% Instruction 'remix' %}
- In `gruntfile.js`, replace the `//Add configuration here` comment
with this code block:

```javascript
imagemin: {
  dynamic: {
    files: [{
      cwd: 'images/',
      expand: true,
      src: ['**/*.{png,jpg}'],
    }]
  }
}
```

This code block tells Grunt which files should be compressed with Imagemin.

- `dynamic` indicates that the list of files to compress will be *dynamically*
  generated by matching the filenames against the specified file pattern.

- The file pattern `{cwd: 'images/', expand: true, src: ['**/*.{png,jpg}']}`
  will match all the JPEG and PNG images in the `images/` directory.

{% Aside %}
Have more questions about this?
You can
[read more about the Grunt file object here](https://gruntjs.com/configuring-tasks#building-the-files-object-dynamically).
{% endAside %}

- Load the Imagemin task by adding this line immediately before
  `grunt.registerTask(...)`:

<pre class="prettyprint">
<strong>grunt.loadNpmTasks('grunt-contrib-imagemin');</strong>
grunt.registerTask('default', [/* list plugins here */]);
</pre>


- Lastly, add Imagemin as the default Grunt task by replacing the `/* list plugins here */` comment with `'imagemin'`. That line should now look like this:

<pre class="prettyprint">
grunt.registerTask('default', <strong>['imagemin']</strong>);
</pre>

## ✔︎ Check-in

The complete `gruntfile.js` file should now look like this:

```javascript
const grunt = require('grunt')
grunt.initConfig({
  imagemin: {
    dynamic: {
      files: [{
        cwd: 'images/'
        expand: true,
        src: ['**/*.{png,jpg}'],
      }]
    }
  }
});
grunt.loadNpmTasks('grunt-contrib-imagemin');
grunt.registerTask('default', ['imagemin']);
```

## Customize your Imagemin Configuration

`imagemin-pngquant` is a plugin for specifying compression quality levels.
We've already added `imagemin-pngquant` to this project in the `package.json`
file so that you can use it to compress your PNGs. To use it, declare the plugin
and specify a compression quality level in your Gruntfile.


- Declare the `imagemin-pngquant` plugin by adding this line to the top of your
`gruntfile.js`:

<pre class="prettyprint">
<strong>const pngquant = require('imagemin-pngquant');</strong>
const grunt = require('grunt')
grunt.initConfig({
  ...
</pre>

- Add settings for compressing PNG images by adding an `options` property to the
  `imagemin` object. That `options` property should look like this:

<pre class="prettyprint">
grunt.initConfig({
  imagemin: {
    <strong>options: {</strong>
      <strong>use: [</strong>
        <strong>pngquant({quality: [0.5, 0.5]}),</strong>
      <strong>]</strong>
    <strong>},</strong>
    dynamic: {
  ...
</pre>

This code tells Imagemin to compress PNGs using the Pngquant plugin. The
`quality` field uses a `min` and `max` range of values to determine the
compression level—0 is the lowest and 1 is the highest. To force all images to
be compressed at 50% quality, pass `0.5` as both the min and max value.

## ✔︎ Check-in

Your `gruntfile.js` file should now look like this:

```javascript
const pngquant = require('imagemin-pngquant');
const grunt = require('grunt')
grunt.initConfig({
  imagemin: {
    options: {
      use: [
        pngquant({quality: [0.5, 0.5]}),
      ]
    },
    dynamic: {
      files: [{
        cwd: 'images/',
        expand: true,
        src: ['**/*.{png,jpg}'],
      }]
    }
  }
});
grunt.loadNpmTasks('grunt-contrib-imagemin');
grunt.registerTask('default', ['imagemin']);
```

But what about JPEGs? The project also has JPEG images, so you should specify how
they are compressed as well.

## Customize your Imagemin configuration (continued)

Use the `imagemin-mozjpeg` plugin, which has already been installed for you, to
compress JPEG images.

- Declare the `imagemin-mozjpeg` plugin by putting this line at the top your
  `gruntfile.js`.

<pre class="prettyprint">
<strong>const mozjpeg = require('imagemin-mozjpeg');</strong>
const pngquant = require('imagemin-pngquant');
const grunt = require('grunt');
</pre>

- Next, add `mozjpeg({quality: 50})` to the array in the `options` object.
  That array should now look like this:

<pre class="prettyprint">
grunt.initConfig({
  imagemin: {
    options: {
      use: [
        pngquant({quality: [0.5, 0.5]}),
        <strong>mozjpeg({quality: 50})</strong>
      ]
    },
    dynamic: {
</pre>

## ✔︎ Check-in

Your `gruntfile.js` file should now look like this:

```javascript
const mozjpeg = require('imagemin-mozjpeg');
const pngquant = require('imagemin-pngquant');
const grunt = require('grunt');
grunt.initConfig({
  imagemin: {
    options: {
      use: [
        pngquant({quality: [0.5, 0.5]}),
        mozjpeg({quality: 50})
      ]
    },
    dynamic: {
      files: [{
        cwd: 'images/',
        expand: true,
        src: ['**/*.{png,jpg}'],
      }]
    }
  }
});
grunt.loadNpmTasks('grunt-contrib-imagemin');
grunt.registerTask('default', ['imagemin']);
```

## Run Grunt & verify results with Lighthouse

1. Click the **Tools** button.
1. Then click the **Console** button.
1. Run Grunt by typing the following command into the console:

<pre class="devsite-terminal devsite-click-to-copy">
grunt
</pre>

When Grunt completes you should see a message like this in console:

`Minified 6 images (saved 667 kB - 66.5%)`

Hooray! These results are much better.

Lastly, it's a good idea to use Lighthouse to verify the changes that you just
made. Lighthouse's "Efficiently encode images" performance audit will let you
know if the JPEG images on your page are optimally compressed.

{% Instruction 'preview' %}
- Run the Lighthouse performance audit (Lighthouse > Options > Performance) on
  the live version of your Glitch and verify that the "Efficiently encode
  images" audit was passed.

{% Img src="image/admin/TTrEG19zxZRSWNv2pRG4.png", alt="Passing 'Efficiently encode images' audit in Lighthouse", width="766", height="976", class="screenshot" %}

Success! You have used Imagemin to optimally compress the images on your page.

# Using Imagemin with Gulp

This Glitch already contains `gulp`, `gulp-cli`, and the `gulp-imagemin` plugin.
To add the configuration for Imagemin, you'll need to edit your `gulpfile.js` file.

{% Instruction 'remix' %}
- First, initialize the `gulp-imagemin` plugin by adding this code at the top of
`gulpfile.js`:

```javascript
const imagemin = require('gulp-imagemin');
```

- Next, replace the `//Add tasks here` comment in `gulpfile.js` with this code
block:

```javascript
gulp.src('images/*')
  .pipe(imagemin())
  .pipe(gulp.dest('images/'))
```

This code adds a Gulp task that uses Imagemin to compress the images in the
`images/` directory. The original images are overwritten and saved in the same
`images/` directory.

## ✔︎ Check-in

Your `gulpfile.js` file should now look like this:

```javascript
const imagemin = require('gulp-imagemin');
const gulp = require('gulp');

gulp.task('default', () => {
  gulp.src('images/*')
    .pipe(imagemin())
    .pipe(gulp.dest('images/'))
});
```

You now have a `gulpfile.js` that can be used to compress images.

## Run Gulp

1. Click the **Tools** button.
1. Then click the **Console** button.
1. Run Gulp to compress your images by typing the following command into the
console:

<pre class="devsite-terminal devsite-click-to-copy">
gulp
</pre>

When Gulp completes, you should see a message like this in the terminal:

```bash
gulp-imagemin: Minified 6 images (saved 50 kB—14.8%)
```

A 15% improvement in file size is a good start; however, more improvements can
be made by using different compression settings.

## Customize your Imagemin Configuration

`imagemin-pngquant` is a plugin for specifying compression quality levels.
We've already added `imagemin-pngquant` to this project in the `package.json`
file so that you can use it to compress your PNGs. To use it, declare the plugin
and specify a compression quality level in your gulpfile.

- Declare the `imagemin-pngquant` plugin by adding this line to the top of your
`gulpfile.js`:

```javascript
const pngquant = require('imagemin-pngquant');
```

- Add the `imagemin-pngquant` plugin (and its settings) by passing the following
array to `ImageminPlugin()`:

```javascript
[pngquant({quality: [0.5, 0.5]})]
```

This code tells Imagemin to compress PNGs using the Pngquant plugin. The
`quality` field uses a `min` and `max` range of values to determine the
compression level—0 is the lowest and 1 is the highest. To force all images to
be compressed at 50% quality, pass `0.5` as both the min and max value.

## ✔︎ Check-in

Your `gulpfile.js` file should now look like this:

```javascript
const pngquant = require('imagemin-pngquant');
const imagemin = require('gulp-imagemin');
const gulp = require('gulp');

gulp.task('default', () => {
  gulp.src('images/*')
    .pipe(imagemin([
      pngquant({quality: [0.5, 0.5]})
    ]))
    .pipe(gulp.dest('images/'))
});
```

But what about JPGs? The project also has JPG images; these need to be
compressed too.

## Customize your Imagemin Configuration (continued)

Use the `imagemin-mozjpeg` plugin, which has already been installed for you, to compress
JPG images.

- Declare the `imagemin-mozjpeg` plugin by putting this line at the top your `gulpfile.js`.

```javascript
const mozjpeg = require('imagemin-mozjpeg');
```

- Next, add `mozjpeg({quality: 50})` to the array that's passed to
  `ImageminPlugin()`:

```javascript
[
  pngquant({quality: [0.5, 0.5]}),
  mozjpeg({quality: 50})
]
```

## ✔︎ Check-in

Your `gulpfile.js` file should now look like this:

```javascript
const mozjpeg = require('imagemin-mozjpeg')
const pngquant = require('imagemin-pngquant');
const imagemin = require('gulp-imagemin');
const gulp = require('gulp');

gulp.task('default', () => {
  gulp.src('images/*')
    .pipe(imagemin([
      pngquant({quality: [0.5, 0.5]}),
      mozjpeg({quality: 50})
    ]))
    .pipe(gulp.dest('images/'))
});
```

## Re-run Gulp & verify results with Lighthouse

- Re-run Gulp:

<pre class="devsite-terminal devsite-click-to-copy">
gulp
</pre>

When Gulp completes, you should see a message like this in terminal:

```shell
gulp-imagemin: Minified 6 images (saved 667 kB—66.5%)
```

Hooray! These results are much better.

Lastly, it's a good idea to use Lighthouse to verify the changes that you just
made.

Lighthouse's "Efficiently encode images" performance audit can let you know if
the JPEG images on your page are optimally compressed.

{% Instruction 'preview' %}
- Run the Lighthouse performance audit (Lighthouse > Options > Performance) on
  the live version of your Glitch and verify that the "Efficiently encode
  images" audit was passed.

{% Img src="image/admin/vNZSjlHVQpG3R8SmojFi.png", alt="Passing 'Efficiently encode images' audit in Lighthouse", width="766", height="976", class="screenshot" %}

Success! You have used Imagemin to optimally compress the images on your page.

# Using Imagemin with webpack

This Glitch already contains `webpack`, `webpack-cli`, and
`imagemin-webpack-plugin`. To add the configuration for Imagemin, you'll need
to edit your `webpack.config.js` file.

The existing `webpack.config.js` for this project has been copying images from
the `images/` directory to the `dist/` directory but it hasn't been
compressing them.

{% Instruction 'remix' %}

{% Aside %}
Why would you copy images to a new `dist/` folder? `dist/` is short for
"distribution" and it's fairly common practice to keep original code, images,
etc. separate from their distributed versions because they may be slightly
different.
{% endAside %}

- First, declare the Imagemin plugin by adding this code at the top of
`webpack.config.js`:

```javascript
const ImageminPlugin = require('imagemin-webpack-plugin').default;
```

- Next, add the following code as the last item in the `plugins[]` array. This
adds Imagemin to the list of plugins that webpack uses:

```javascript
new ImageminPlugin()
```

{% Aside %}
Why add it at the end of the array? Adding it there ensures that Imagemin runs
last, after all the other plugins.
{% endAside %}

## ✔︎ Check-in

Your complete `webpack.config.js` file should now look like this:

```javascript
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');

module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
     new CopyWebpackPlugin([{
       from: 'img/**/**',
       to: path.resolve(__dirname, 'dist')
     }]),
     new ImageminPlugin()
  ]
}
```

You now have a webpack config that compresses images using Imagemin.

## Run webpack

{% Instruction 'console' %}
1. To compress your images, run webpack by typing the following command into the
console:

```bash
webpack --config webpack.config.js --mode development
```

But what happens if you run webpack in production mode?

- Re-run webpack, but this time in production mode:

```bash
webpack --config webpack.config.js --mode production
```

This time around, webpack displays a warning letting you know that your PNG
files, in spite of some compression, still exceed the recommended size limit.
(webpack's `development` & `production` modes prioritize different things, which
is why you only see this warning while running webpack in production mode.)

Customize our Imagemin configuration to fix this warning.

## Customize your Imagemin Configuration

Add settings for compressing PNG images by passing the following object to `ImageminPlugin()`:

```javascript
{pngquant: ({quality: [0.5, 0.5]})}
```

This code tells Imagemin to compress PNGs using the Pngquant plugin. The
`quality` field uses a `min` and `max` range of values to determine the
compression level—0 is the lowest and 1 is the highest. To force all images to
be compressed at 50% quality, pass `0.5` as both the min and max value.

## ✔︎ Check-in

Your `webpack.config.js` file should now look like this:

```javascript
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');

module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new CopyWebpackPlugin([{
		from: 'img/**/**',
    	to: path.resolve(__dirname, 'dist')
    }]),
    new ImageminPlugin({
      pngquant: ({quality: [0.5, 0.5]}),
	  })
  ]
}
```

But what about JPEGs? The project also has JPEG images, so you should specify
how they are compressed as well.

## Customize your Imagemin Configuration (continued)

Instead of using `imagemin-webpack-plugin`'s default plugin for JPG compression
(`imagemin-jpegtran`), use the `imagemin-mozjpeg` plugin. Unlike Jpegtran,
Mozjpeg let's you specify a compression quality for your JPG compression. We've
already installed the Mozjpeg plugin for you in this Glitch, but you'll need to
edit your `webpack.config.js` file:

- Initialize the `imagemin-mozjpeg` plugin by adding the following line at the
  top of your `webpack.config.js` file:

```javascript
const imageminMozjpeg = require('imagemin-mozjpeg');
```

- Add a `plugins` property to the object passed to `ImageminPlugin()`, such that
  the object now looks like this:

```javascript
new ImageminPlugin({
  pngquant: ({quality: [0.5, 0.5]}),
  plugins: [imageminMozjpeg({quality: 50})]
})
```

This code tells webpack to compress JPGs to a quality of 50 (0 is the worst;
100 is the best) using the Mozjpeg plugin.

{% Aside %}
Are you wondering why Mozjpeg is added to the plugins array, but Pngquant isn't?
Good question.
{% endAside %}

If you're adding settings for a plugin that is a default plugin of
`imagemin-webpack-plugin`, they can be added as a key-object pair on the object
passed to `ImageminPlugin()`. The settings for Pnquant are a good example of
this.

However, if you're adding settings for non-default plugins (for example,
Mozjpeg), they should be added by including them in the array corresponding to
the `plugins` property.

## ✔︎ Check-in

Your code should now look like this:

```javascript
const imageminMozjpeg = require('imagemin-mozjpeg');
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');

module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new CopyWebpackPlugin([{
      from: 'img/**/**',
      to: path.resolve(__dirname, 'dist')
    }]),
    new ImageminPlugin({
      pngquant: ({quality: [0.5, 0.5]}),
      plugins: [imageminMozjpeg({quality: 50})]
    })
  ]
}
```

## Re-run webpack and verify results with Lighthouse

- In the console, re-run webpack:

```bash
webpack --config webpack.config.js --mode production
```

Hooray! Your changes should have fixed the webpack warnings.

webpack warns you about large images, but it can't tell you if images are
uncompressed or undercompressed. This is why it's always a good idea to use
Lighthouse to verify your changes.

Lighthouse's "Efficiently encode images" performance audit can let you know if
the JPEG images on your page are optimally compressed.

{% Instruction 'preview' %}
- Run the Lighthouse performance audit (**Lighthouse > Options > Performance**)
  on the live version of your Glitch and verify that the **Efficiently encode
  images** audit was passed.

{% Img src="image/admin/abAeMMdJEj9j2osonskn.png", alt="Passing 'Efficiently encode images' audit in Lighthouse", width="766", height="976", class="screenshot" %}

Success! You have used Imagemin to optimally compress the images on your page.

# Optimize third-party JavaScript

{% include 'content/devtools-headsup.njk' %}

[Third-party scripts impact performance](/third-party-javascript), which is why it's important to [audit them regularly](/identify-slow-third-party-javascript) and use [efficient techniques for loading](/efficiently-load-third-party-javascript) them. This codelab shows you how to optimize the loading of third-party resources. It covers the following techniques:

* Deferring script loading

* Lazy loading non-critical resources

* Preconnecting to required origins

The included sample app features a simple web page with three features coming from third-party sources:

* A video embed

* A data-visualization library for rendering a line graph

* A social media sharing widget

<figure>
  {% Img src="image/admin/cuWcC16X6oKRbXJXIIt7.png", alt="Screenshot of the page with third-party resources highlighted.", width="800", height="1294" %}
  <figcaption>Third-party resources in the sample app.</figcaption>
</figure>

You'll start by measuring the performance of the app and then apply each technique to improve different aspects of app performance.

## Measure performance

First open the sample app in the fullscreen view:
{% Instruction 'remix', 'ol' %}
{% Instruction 'preview', 'ol' %}

Run a [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) [performance audit](https://developer.chrome.com/docs/lighthouse/performance/) on the page to establish baseline performance:

{% Instruction 'devtools-lighthouse', 'ol' %}
1. Click **Mobile**.
1. Select the **Performance** checkbox. (You can clear the rest of the checkboxes in the Audits section.)
1. Click **Simulated Fast 3G, 4x CPU Slowdown**.
1. Select the **Clear Storage** checkbox.
1. Click **Run audits**.

When you run an audit on your machine, [the exact results may vary](https://developers.google.com/web/tools/lighthouse/variability), but you should notice that the [First Contentful Paint (FCP)](/fcp/) time is pretty high, and that Lighthouse suggests two opportunities to investigate: **Eliminate render-blocking resources** and **Preconnect to required origins**. (Even if the metrics are all in the green, optimizations will still yield improvements.)

<figure>
  {% Img src="image/admin/gIkIJM2OaocxImjLLjev.png", alt="Screenshot of Lighthouse audit showing 2.4 second FCP and two opportunities: Eliminate render-blocking resources and Preconnect to required origins.", width="741", height="700" %}
</figure>

## Defer third-party JavaScript

The **Eliminate render-blocking resources** audit identified that you can save some time by deferring a script coming from d3js.org:

<figure>
  {% Img src="image/admin/P9ejh4JMzdpu8N3aZ7bC.png", alt="Screenshot of Eliminate render-blocking resources audit with the d3.v3.min.js script highlighted.", width="718", height="337" %}
</figure>

[D3.js](https://d3js.org/) is a JavaScript library for creating data visualizations. The `script.js` file in the sample app uses D3 utility functions to create the SVG line chart and append it to the page. The order of operations here matters: `script.js` has to run after the document is parsed and the D3 library has loaded, which is why it's included right before the closing `</body>` tag in `index.html`.

However, the D3 script is included in the page's `<head>`, which blocks the parsing of the rest document:

<figure>
  {% Img src="image/admin/vRP2oYmijq0sVyLRb2nU.png", alt="Screenshot of index.html with highlighted script tag in the head.", width="718", height="265" %}
</figure>

Two magic attributes can unblock the parser when added to the script tag:

* [`async`](https://developer.mozilla.org/docs/Web/HTML/Element/script#attr-async) ensures that scripts download in the background and execute **at the first opportunity** after they finish downloading.

* [`defer`](https://developer.mozilla.org/docs/Web/HTML/Element/script#attr-defer) ensures that scripts download in the background and execute **after parsing** is completely finished.

Since this chart is not really critical to the overall page and will most likely be below the fold, use `defer` to make sure there's no parser blocking.

### Step 1: Load the script asynchronously with the `defer` attribute


On line 17 in `index.html`, add the `defer` attribute to the `<script>` element:


```html
<script src="https://d3js.org/d3.v3.min.js" defer></script>
```

### Step 2: Ensure the correct order of operations

Now that D3 is deferred, `script.js` will run before D3 is ready, resulting in an error.

Scripts with the `defer` attribute execute in the order in which they were specified. To ensure `script.js` gets executed after D3 is ready, add `defer` to it and move it up to the `<head>` of the document, right after the D3 `<script>` element. Now it no longer blocks the parser, and the download starts sooner.

```html
<script src="https://d3js.org/d3.v3.min.js" defer></script>
<script src="./script.js" defer></script>
```

## Lazy-load third-party resources

All resources that are below the fold are good candidates for [lazy loading](/efficiently-load-third-party-javascript/#lazy-load-third-party-resources).

The sample app has a YouTube video embedded in an [iframe](https://developer.mozilla.org/docs/Web/HTML/Element/iframe). To check out how many requests the page makes and which come from the embedded YouTube iframe:

{% Instruction 'preview', 'ol' %}
{% Instruction 'devtools-network', 'ol' %}
{% Instruction 'disable-cache', 'ol' %}
1. Select *Fast 3G* in the **Throttling** dropdown menu.
{% Instruction 'reload-page', 'ol' %}

{% Img src="image/admin/DjnRutx27trV4YkYcKhr.png", alt="Screenshot of DevTools Network panel.", width="783", height="618" %}

The **Network** panel reveals that the page made a total of 28 requests and transferred almost 1 MB of compressed resources.

To identify the requests that the YouTube `iframe` made, look for the video ID `6lfaiXM6waw` in the **Initiator** column. To group together all the requests by domain:

* In the **Network** panel, right-click a column title.

* In the dropdown menu, select the **Domains** column.

* To sort the requests by domain, click the **Domains** column title.

The new sorting reveals that there are additional requests to Google domains. In total, the YouTube iframe makes 14 requests for scripts, stylesheets, images, and fonts. But unless users actually scroll down to play the video, they don't really need all those assets.

By waiting to lazy-load the video until a user scrolls down to that section of the page, you cut the number of requests the page initially makes. This approach saves users' data and speeds up the initial load.

One way to implement lazy loading is by using the [Intersection Observer](https://developer.mozilla.org/docs/Web/API/Intersection_Observer_API), a browser API that notifies you when an element enters or exits the browser's viewport.

### Step 1: Prevent video from loading initially

To lazy-load the video iframe, you must first prevent it from loading in the usual way. Do that by replacing the `src` attribute with the `data-src` attribute to specify the video URL:

```html
<iframe width="560" height="315" data-src="https://www.youtube.com/embed/lS9D6w1GzGY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
```

`data-src` is a [data attribute](https://developer.mozilla.org/docs/Learn/HTML/Howto/Use_data_attributes), which allows you to store extra information on standard HTML elements. A data attribute can be named anything, as long as it starts with "data-".

An iframe without a `src` simply won't load.

### Step 2: Use Intersection Observer to lazy-load the video

To load the video when a user scrolls to it you need to know when that happens. That's where the Intersection Observer API steps in. The Intersection Observer API lets you register a callback function that's executed whenever an element you want to track enters or exits the viewport.

To get started, create a new file and name it `lazy-load.js`:

{% Instruction 'create' %}

Add the script tag to your document head:

 ```html
 <script src="/lazy-load.js" defer></script>
 ```

In `lazy-load.js`, create a new `IntersectionObserver` and pass it a callback function to run:

```js
// create a new Intersection Observer
let observer = new IntersectionObserver(callback);
```

Now give `observer` a target element to watch (the video iframe in this case) by passing it as an argument in the `observe` method:

```js
// the element that you want to watch
const element = document.querySelector('iframe');

// register the element with the observe method
observer.observe(element);
```

`callback` receives a list of [`IntersectionObserverEntry`](https://developer.mozilla.org/docs/Web/API/IntersectionObserverEntry) objects and the [`IntersectionObserver`](https://developer.mozilla.org/docs/Web/API/IntersectionObserver) object itself. Each entry contains a [`target`](https://developer.mozilla.org/docs/Web/API/IntersectionObserverEntry/target) element and properties that describe its dimensions, position, the time it entered the viewport, and more. One of the properties of `IntersectionObserverEntry` is [`isIntersecting`](https://developer.mozilla.org/docs/Web/API/IntersectionObserverEntry/isIntersecting)—a boolean value that equals `true` when the element enters the viewport.

In this example, the `target` is the `iframe`. `isIntersecting` equals `true` when `target` enters the viewport. To see this in action, replace `callback` with the following function:

```js/1-7/0
let observer = new IntersectionObserver(callback);
let observer = new IntersectionObserver(function(entries, observer) {
    entries.forEach(entry => {
      console.log(entry.target);
      console.log(entry.isIntersecting);
    });
  });
```
{% Instruction 'preview', 'ol' %}
{% Instruction 'devtools-console', 'ol' %}

Try scrolling up and down. You should see the value of `isIntersecting` change and the target element logged to the console.

To load the video when the user scrolls to its position, use `isIntersecting` as a condition to run a `loadElement` function, which gets the value from the `iframe` element's `data-src` and sets it as the `iframe` element's `src` attribute. That replacement triggers the loading of the video. Then, once the video is loaded, call the [`unobserve`](https://developer.mozilla.org/docs/Web/API/IntersectionObserver/unobserve) method on the `observer` to stop watching the target element:

```js/6-13,15-18/2-5
let observer = new IntersectionObserver(function (entries, observer) {
  entries.forEach(entry => {
    console.log(entry.target);
    console.log(entry.isIntersecting);
  });
});
    if (entry.isIntersecting) {
      // do this when the element enters the viewport
      loadElement(entry.target);
      // stop watching
      observer.unobserve(entry.target);
    }
  });
});

function loadElement(element) {
  const src = element.getAttribute('data-src');
  element.src = src;
}
```

### Step 3: Reevaluate performance

To see how the size and number of resources changed, open the DevTools **Network** panel and reload the page again. The **Network** panel reveals that the page made 14 requests and only 260 KB. That's a meaningful improvement!

Now scroll down the page and keep an eye on the **Network** panel. When you get to the video, you should see the page trigger additional requests.

{% Video src="video/tcFciHGuF3MxnTr1y5ue01OGLBn2/bJHFfNjxvIthfRWeidbx.mp4", autoplay=true, loop=true, muted=true, playsinline=true %}

{% Aside %}
In this example, the element is loaded when it enters the viewport, which you can see happening in the video above. You can avoid that delay and create a smoother user experience by loading elements a little before they enter the viewport. To do that, use the [`rootMargin`](https://developer.mozilla.org/docs/Web/API/Intersection_Observer_API#Creating_an_intersection_observer) property to define margins around the target element, which effectively grows (or shrinks) the area that triggers the `isIntersecting` change.
{% endAside %}

## Preconnect to required origins

You've deferred non-critical JavaScript and lazy-loaded the YouTube requests, so now it's time to optimize the remaining third-party content.

Adding the `rel=preconnect` attribute to a link tells the browser to establish a connection to a domain before the request for that resource is made. This attribute is best used on origins that provide resources you are certain the page needs.

The Lighthouse audit you ran in the first step suggested in **Preconnect to required origins** that you can save around 400 ms by [establishing early connections](/preconnect-and-dns-prefetch/) to staticxx.facebook.com and youtube.com:

{% Img src="image/admin/k4PDdjh77dXS1ZdGBSko.png", alt="Preconnect to required origins audit with the staticxx.facebook.com domain highlighted.", width="720", height="279" %}

Since the YouTube video is now lazy-loaded, that leaves only staticxx.facebook.com, the source of the social media sharing widget. Establishing an early connection to this domain is as simple as adding a `<link>` tag to the document's `<head>`:

```html
  <link rel="preconnect" href="https://staticxx.facebook.com">
```

## Reevaluate performance

Here's the state of the page [after optimization](https://glitch.com/~3rd-party-optimizations). Follow the steps from the [Measure performance](#measure-performance) section of the codelab to run another Lighthouse audit.

{% Img src="image/admin/Iv12shtGoURq2CTqO6jN.png", alt="Lighthouse audit showing 1 second FCP and the performance score of 99.", width="727", height="511" %}

{% Aside 'success' %}
You should see that FCP and the performance score have improved! 🎉 The page now loads faster and consumes less data⁠—and all without negatively affecting the user-experience. 🤝
{% endAside %}

# Codelab: Preload critical assets to improve loading speed

{% include 'content/devtools-headsup.njk' %}

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

{% Img src="image/admin/Ln5Oxy5vp8QWTn0GoOFh.png", alt="App Screenshot", width="800", height="578" %}

## Measure

First measure how the website performs before adding any optimizations.

{% Instruction 'preview' %}

Run the Lighthouse performance audit (**Lighthouse > Options > Performance**) on
the live version of your Glitch (see also
[Discover performance opportunities with Lighthouse](/discover-performance-opportunities-with-lighthouse)).

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

<figure>
  {% Img src="image/admin/AMhzorWlWYejd0tA11rY.png", alt="Lighthouse: Preload key requests audit", width="800", height="231" %}
</figure>

{% Instruction 'devtools-network' %}

<figure>
  {% Img src="image/admin/pO1f6x9pOqzYwPciKnTy.png", alt="Network panel with late-discovered resource", width="800", height="166" %}
</figure>

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:

```bash
├─┬ / (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:

```html/2
<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.

<figure>
  {% Img src="image/admin/3xjh4kLTps0fCqs7wRwP.png", alt="Network panel with preloaded resource", width="800", height="166" %}
</figure>

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.

{% Aside %}
If this was a real production app, it would make more sense to just place a
`<link>` element in index.html to fetch the CSS file instead of using JavaScript
to append it. Browsers already know to fetch a CSS file defined at the head of
an HTML document with a high priority as soon as possible. However, preload is
used in this codelab to demonstrate the best course of action for files that are
fetched late in the request chain. For a large application, this can happen
quite often.
{% endAside %}

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.

```html/3
<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.

<figure>
  {% Img src="image/admin/vOyq8M3X2UPd5JUTvex0.png", alt="Network panel with unecessary preload", width="800", height="189" %}
</figure>

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.

<figure>
  {% Img src="image/admin/elfGvORyRu1s0slntYHo.png", alt="Preload warning in console", width="800", height="150" %}
</figure>

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.

```html//3
<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](https://developer.mozilla.org/docs/Web/HTML/Preloading_content#What_types_of_content_can_be_preloaded).

{% Aside %}
Cross-origin resources can also be preloaded using the `crossorigin` attribute.
Moreover, same-origin font resources must be fetched using anonymous mode CORS
which is why the `crossorigin` attribute is also used in this preload tag.
The [Cross Origin Resource Sharing](/cross-origin-resource-sharing)
guide explains the topic of same-origin and cross-origin requests in more detail.
{% endAside %}

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

{% Img src="image/admin/KLzVOClmdtwTZ3nhzGz5.png", alt="Details route", width="800", height="585" %}

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.

```html/2
<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.

<figure>
  {% Img src="image/admin/dddEy2ERtrha2WZKN5Ou.png", alt="Disable cache in Chrome DevTools", width="800", height="145" %}
</figure>

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

<figure>
  {% Img src="image/admin/STiBaCDoaZ1Ghzbp5fOH.png", alt="Network panel with prefetched resource", width="800", height="211" %}
</figure>

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.

<figure>
  {% Img src="image/admin/AfkaIGXdHifq8n1xD8YQ.png", alt="Details page network requests", width="800", height="106" %}
</figure>

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.

<figure>
  {% Img src="image/admin/3pPpPCjbwugX1nnqCf5e.png", alt="Details request fetched from disk cache", width="800", height="170" %}
</figure>

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](/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](https://lodash.com/) when a form is submitted.

<figure>
  {% Img src="image/admin/yg2lFaDyWQFBvw7YaJ3g.gif", alt="Magic Sorter app that demonstrates code splitting", width="600", height="375" %}
</figure>

You can access [the Glitch for this application here](https://glitch.com/edit/#!/code-splitting).

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

```js
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.

```js/2
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.

{% Img src="image/admin/5Zs5L6UMnkUJXyfwtru7.png", alt="Elements panel with prefetch tag", width="800", height="520" %}

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.

<figure>
  {% Img src="image/admin/nGh06M2LqgTfBUwPaXK7.png", alt="Network panel with prefetched request", width="800", height="181" %}
</figure>

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

```js
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.

+ [Browser support: Preload](https://caniuse.com/#feat=link-rel-preload)
+ [Browser support: Prefetch](https://caniuse.com/#feat=link-rel-prefetch)

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

+ [Preload, Prefetch and Priorities in Chrome](https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf)
+ [&lt;link rel="prefetch/preload"&gt; in webpack](https://medium.com/webpack/link-rel-prefetch-preload-in-webpack-51a52358f84c)

# Preload web fonts to improve loading speed

{% include 'content/devtools-headsup.njk' %}

This codelab shows you how to preload web fonts using `rel="preload"` to remove
any flash of unstyled text (FOUT).

## Measure

First measure how the website performs before adding any optimizations.
{% Instruction 'preview', 'ol' %}
{% Instruction 'audit-performance', 'ol' %}

The Lighthouse report that is generated will show you the fetching sequence of resources under **Maximum critical path latency**.

{% Img src="image/admin/eperh8ZUnjhsDlnJdNIG.png", alt="Webfonts are present in the critical request chain.", width="704", height="198" %}

In the above audit the web fonts are part of the critical request chain and fetched last. The [**critical request chain**](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains/) represents the order of resources that are prioritized and fetched by the browser. In this application, the web fonts (Pacfico and Pacifico-Bold) are defined using the [@font-face](/font-best-practices/#understanding-font-face) rule and are the last resource fetched by the browser in the critical request chain. Typically, webfonts are lazy loaded which means that they are not loaded until the critical resources are downloaded (CSS, JS).

Here is the sequence of the resources fetched in the application:

{% Img src="image/admin/9oBNjZORrBj6X8RVlr9t.png", alt="Webfonts are lazy loaded.", width="583", height="256" %}

## Preloading Web fonts
To avoid FOUT, you can preload web fonts that are required immediately. Add the `Link` element for this application at the head of the document:

```html
<head>
 <!-- ... -->
 <link rel="preload" href="/assets/Pacifico-Bold.woff2" as="font" type="font/woff2" crossorigin>
</head>
```

The `as="font" type="font/woff2"` attributes tell the browser to download this resource as a font and helps in prioritization of the re­source queue.

The `crossorigin` attribute indicates whether the resource should be fetched with a CORS request as the font may come from a different domain. Without this attribute, the preloaded font is ignored by the browser.

Since Pacifico-Bold is used in the page header, we added a preload tag to fetch it even sooner. It isn't important to preload the Pacifico.woff2 font because it styles the text that is below the fold.

Reload the application and run lighthouse again. Check the **Maximum critical path latency** section.

{% Img src="image/admin/lC85s7XSc8zEXgtwLsFu.png", alt="Pacifico-Bold webfont is preloaded and removed from the cricical request chain", width="645", height="166" %}

Notice how the `Pacifico-Bold.woff2` is removed from the critical request chain. It is fetched earlier in the application.

{% Img src="image/admin/BrXidcKZfCbbUbkcSwas.png", alt="Pacifico-Bold webfont is preloaded", width="553", height="254" %}

 With preload, the browser knows that it needs to download this file earlier. It is important to note that if not used correctly, preload can harm performance by making unnecessary requests for resources that are not used.

# Remove unused code

{% include 'content/devtools-headsup.njk' %}

In this codelab, improve the performance of the following application by
removing any unused and unneeded dependencies.

{% Img src="image/admin/A6WweLPrgyud8VNCP3Dc.png", alt="App screenshot", width="800", height="567" %}

## Measure

{% Aside %}
Since webpack is used in this application, any changes made to the code will
trigger a new build which can take a few seconds. Once it completes, you
should see your changes reflected in the application.
{% endAside %}

It's always a good idea to first measure how well a website performs before
adding optimizations.

{% Instruction 'preview' %}

Go ahead and click on your favorite kitten! Firebase's
[Realtime Database](https://firebase.google.com/products/realtime-database/) is
used in this application which is why the score updates in real-time and is
synchronized with every other person using the application. 🐈

{% Instruction 'devtools-network', 'ol' %}
{% Instruction 'disable-cache', 'ol' %}
{% Instruction 'reload-app', 'ol' %}

{% Img src="image/admin/GnvBzXKQwy2az8Gyxl6D.png", alt="Original bundle size of 992 KB", width="800", height="169" %}

Almost 1 MB worth of JavaScript is being shipped to load this simple application!

Take a look at the project warnings in DevTools.

- Click on the **Console** tab.
- Make sure that `Warnings` is enabled in the levels dropdown next to the
  `Filter` input.

{% Img src="image/admin/DMdUUxzxNMEGZ6HsTd3P.png", alt="Warnings filter", width="800", height="244", class="screenshot" %}

- Take a look at the displayed warning.

{% Img src="image/admin/QGn9dHsaWNjTpMCwc0Rc.png", alt="Console warning", width="800", height="295", class="screenshot" %}

Firebase, which is one of the libraries used in this application, is being a
good samaritan by providing a warning to let developers know to not import its
entire package but only the components that are used. In other words, there are
unused libraries that can be removed in this application to make it load
faster.

There are also instances when a particular library is used, but where there may be
a simpler alternative. The concept of removing **unneeded libraries** is
explored later in this tutorial.

## Analyzing the bundle

There are two main dependencies in the application:

- [Firebase](https://firebase.google.com/): a platform that provides a number of
  useful services for iOS, Android or web applications. Here its [Realtime
  Database](https://firebase.google.com/products/realtime-database/) is used to
  store and sync the information for each kitten in real time.
- [Moment.js](https://momentjs.com/): a utility library that makes it easier to
  handle dates in JavaScript. The birth date of each kitten is stored in the
  Firebase database, and `moment` is used to calculate its age in weeks.

How can just two dependencies contribute to a bundle size of almost 1 MB? Well,
one of the reasons is that any dependency can in turn have their own
dependencies, so there are a lot more than just two if every depth/branch of the
dependency "tree" is considered. It's easy for an application to become large
relatively quickly if many dependencies are included.

Analyze the bundler to get a better idea of what is going. There are a number of
different community-built tools that can help do this, such as
[`webpack-bundle-analyzer`](https://www.npmjs.com/package/webpack-bundle-analyzer).

The package for this tool is already included in the app as a `devDependency`.

```js
"devDependencies": {
  //...
  "webpack-bundle-analyzer": "^2.13.1"
},
```

This means that it can be used directly in the webpack configuration file.
Import it at the very beginning of `webpack.config.js`:

```js/3
const path = require("path");

//...
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
  .BundleAnalyzerPlugin;
```

Now add it as a plugin at the very end of the file within the `plugins` array:

```js/4
module.exports = {
  //...
  plugins: [
    //...
    new BundleAnalyzerPlugin()
  ]
};
```

When the application reloads, you should see a visualization of the entire
bundle instead of the app itself.

{% Img src="image/admin/QhYbNr23tGbcHwFZGKIQ.png", alt="Webpack Bundle Analyzer", width="800", height="468" %}

Not as cute as seeing some kittens 🐱, but incredibly helpful nonetheless.
Hovering over any of the packages shows its size represented in three
different ways:

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th><strong>Stat size</strong></th>
        <th>Size before any minification or compression.</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><strong>Parsed size</strong></td>
        <td>
          Size of actual package within the bundle after it has been compiled.
          Version 4 of webpack (which is used in this application) minifies the
          compiled files automatically which is why this is smaller than the stat
          size.
        </td>
      </tr>
      <tr>
        <td><strong>Gzipped size</strong></td>
        <td>
          Size of package after it has been compressed with gzip encoding. This
          topic is covered in a separate guide.
        </td>
      </tr>
    </tbody>
  </table>
</div>

With the webpack-bundle-analyzer tool, it is easier to identify unused or
unneeded packages that make up a large percentage of the bundle.

## Removing unused packages

The visualization shows that the `firebase` package consists of a _lot_ more
than just a database. It includes additional packages such as:

- `firestore`
- `auth`
- `storage`
- `messaging`
- `functions`

These are all amazing services provided by Firebase (and refer to the
[documentation](https://firebase.google.com/docs/web/setup#use_firebase_services)
to learn more), but none of them are being used in the application, so there's
no reason to have them all imported.

Revert the changes in `webpack.config.js` to see the application again:

- Remove `BundleAnalyzerPlugin` in the list of plugins:

```js//2
plugins: [
  //...
  new BundleAnalyzerPlugin()
];
```

- And now remove the unused import from the top of the file:

```js//3
const path = require("path");

//...
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
```

The application should load normally now. Modify `src/index.js` to update the
Firebase imports.

```js/1-2/0
import firebase from 'firebase';
import firebase from 'firebase/app';
import 'firebase/database';
```

Now when the app reloads, the DevTools warning does not show. Opening the
DevTools **Network** panel also shows a _nice_ reduction in bundle size:

{% Img src="image/admin/8agHvjw7351Yj8U1rIla.png", alt="Bundle size reduced to 480 KB", width="800", height="152", class="screenshot" %}

More than half the bundle size was removed. Firebase provides many different
services and gives developers the option to only include those that are actually
needed. In this application, only `firebase/database` was used to store and sync
all of the data. The `firebase/app` import, which sets up the API surface for
each of the different services, is always required.

Many other popular libraries, such as `lodash`, also allow developers to
selectively import different parts of their packages. Without doing much work,
updating library imports in an application to only include what is being used
can result in significant performance improvements.

Although the bundle size has been reduced by quite a bit, there's still more
work to do! 😈

## Removing unneeded packages

Unlike Firebase, importing parts of the `moment` library cannot be done as
easily, but maybe it can be removed entirely?

The birthday of each cute kitten is stored in **Unix** format (milliseconds) in
the Firebase database.

{% Img src="image/admin/D1ufo1PdyzW3wFSu9sE1.png", alt="Birthdates stored in Unix format", width="560", height="236", class="screenshot" %}

This is a timestamp of a particular date and time represented by the number of
milliseconds that have elapsed since January 1, 1970 00:00 UTC. If the current
date and time can be calculated in the same format, a small function to find the
age of each kitten in weeks can probably be constructed.

Like always, try not to copy and paste as you follow along here. Begin by
removing `moment` from the imports in `src/index.js`.

```js//2
import firebase from 'firebase/app';
import 'firebase/database';
import * as moment from 'moment';
```

There is a Firebase event listener that handles value changes in our database:

```js
favoritesRef.on("value", (snapshot) => { ... })
```

Above this, add a small function to calculate the number of weeks from a
given date:

```js
const ageInWeeks = birthDate => {
  const WEEK_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 7;
  const diff = Math.abs((new Date).getTime() - birthDate);
  return Math.floor(diff / WEEK_IN_MILLISECONDS);
}
```

In this function, the difference in milliseconds between the current date and
time `(new Date).getTime()` and the birth date (the `birthDate` argument, already
in milliseconds) is calculated and divided by the number of milliseconds in a
single week.

Finally, all instances of `moment` can be removed in the event listener by
leveraging this function instead:

<pre>
favoritesRef.on("value", (snapshot) => {
  const { kitties, favorites, names, birthDates } = snapshot.val();
  favoritesScores = favorites;

  kittiesList.innerHTML = kitties.map((kittiePic, index) => {
    <s>const birthday = moment(birthDates[index]);</s>

    return `
      &lt;li&gt;
        &lt;img src=${kittiePic} onclick=&quot;favKittie(${index})&quot;&gt;
        &lt;div class=&quot;extra&quot;&gt;
          &lt;div class=&quot;details&quot;&gt;
            &lt;p class=&quot;name&quot;&gt;${names[index]}&lt;/p&gt;
            <s>&lt;p class=&quot;age&quot;&gt;${moment().diff(birthday, 'weeks')} weeks old&lt;/p&gt;</s>
            <strong>&lt;p class=&quot;age&quot;&gt;${ageInWeeks(birthDates[index])} weeks old&lt;/p&gt;</strong>
          &lt;/div&gt;
          &lt;p class=&quot;score&quot;&gt;${favorites[index]} ❤&lt;/p&gt;
        &lt;/div&gt;
      &lt;/li&gt;
    `})
});
</pre>

Now reload the application and take a look at the **Network** panel once more.

{% Img src="image/admin/ixTpGQBkJimzUmEFBMsR.png", alt="Bundle size reduced to 225 KB", width="800", height="154", class="screenshot" %}

The size of our bundle was reduced by more than half again!

## Conclusion

With this codelab, you should have a decent understanding of how to analyze a
particular bundle and why it can be so useful to remove unused or unneeded
packages. Before you begin optimizing an application with this technique, **it's
important to know that this can be significantly more complex in larger
applications**.

With regards to **removing unused libraries**, try to find out which parts of a
bundle are being used and which parts are not. For a mysterious looking
package that looks like it is not being used anywhere, take a step back and check
which top-level dependencies might need it. Try to find a way to possibly
decouple them from each other.

When it comes to **removing unneeded libraries**, things can be a little more
complicated. It's important to work closely with your team and see if there is
potential to simplify parts of the codebase. Removing `moment` in this
application may look like it would be the right thing to do every time, but what
if there were time zones and different locales that needed to be handled? Or
what if there were more complicated date manipulations? Things can get very
tricky when manipulating and parsing dates/times, and libraries like `moment`
and [`date-fns`](https://date-fns.org/) simplify this significantly.

**Everything is a tradeoff, and it's important to gauge whether it's even worth
the complexity and effort to roll out a custom solution instead of relying on a
third-party library.**

# Replace GIFs with video

{% include 'content/devtools-headsup.njk' %}

In this codelab, improve performance by replacing an animated GIF with a
video.

## Measure first

First measure how the website performs:

{% Instruction 'preview', 'ol' %}
{% Instruction 'audit-performance', 'ol' %}

When you're finished, you should see that Lighthouse has flagged the GIF as an
issue in its "Use video formats for animated content" audit.

## Get FFmpeg

There are a number of ways you can convert GIFs to video; this guide uses
**[FFmpeg](https://www.ffmpeg.org/)**. It's already installed in the Glitch VM,
and, if you want, you can follow [these instructions to install it on your
local
machine](/replace-gifs-with-videos/#create-mpeg-videos)
as well.

## Open the console

Double-check that FFmpeg is installed and working:

{% Instruction 'remix', 'ol' %}
{% Instruction 'console', 'ol' %}
1. In the console, run:

```bash
which ffmpeg
```

You should get a file path back:

```bash
/usr/bin/ffmpeg
```

## Change GIF to video

- In the console, run `cd images` to enter the images directory.
- Run `ls` to see the contents.

You should see something like this:

```bash
$ ls
cat-herd.gif
```

- In the console, run:

```bash
ffmpeg -i cat-herd.gif -b:v 0 -crf 25 -f mp4 -vcodec libx264 -pix_fmt yuv420p cat-herd.mp4
```

This tells FFmpeg to take the **input**, signified by the `-i` flag, of
cat-herd.gif and convert it to a video called cat-herd.mp4. This should take a
second to run. When the command finishes, you should be able to type `ls` again
and see two files:

```bash
$ ls
cat-herd.gif  cat-herd.mp4
```

## Create WebM videos

While MP4 has been around since 1999, WebM is a relative newcomer having been
initially released in 2010. WebM videos can be much smaller than MP4 videos, so
it makes sense to generate both. Thankfully the `<video>` element will let you
add multiple sources, so if a browser doesn't support WebM, it can fallback to
MP4.

- In the console, run:

```bash
ffmpeg -i cat-herd.gif -c vp9 -b:v 0 -crf 41 cat-herd.webm
```

- To check the file sizes run:

```bash
ls -lh
```

You should have one GIF, and two videos:

```bash
$ ls -lh
total 4.5M
-rw-r--r-- 1 app app 3.7M May 26 00:02 cat-herd.gif
-rw-r--r-- 1 app app 551K May 31 17:45 cat-herd.mp4
-rw-r--r-- 1 app app 341K May 31 17:44 cat-herd.webm
```

Notice that the original GIF is 3.7M, whereas the MP4 version is 551K, and the
WebM version is only 341K. That's a huge savings!

## Update HTML to recreate GIF effect

Animated GIFs have three key traits that videos need to replicate:

- They play automatically.
- They loop continuously (usually, but it is possible to prevent looping).
- They're silent.

Luckily, you can recreate these behaviors using the `<video>` element.

- In the `index.html` file, replace the line with the `<img>` with:

```html/1/0
<img src="/images/cat-herd.gif" alt="Cowboys herding cats.">
<video autoplay loop muted playsinline></video>
```

A `<video>` element using these attributes will play automatically, loop
endlessly, play no audio, and play inline (that is, not fullscreen), all the
behaviors expected of animated GIFs! 🎉

## Specify your sources

All that's left to do is specify your video sources. The `<video>` element requires
one or more `<source>` child elements pointing to different video files the
browser can choose from, depending on format support.
Update the `<video>` with `<source>` elements that link to your cat-herd videos:

```html/1-2
<video autoplay loop muted playsinline>
  <source src="/images/cat-herd.webm" type="video/webm">
  <source src="/images/cat-herd.mp4" type="video/mp4">
</video>
```

{% Aside %}
Browsers don't speculate about which `<source>` is optimal, so the order
of `<source>`s matters. For example, if you specify an MP4 video first and the
browser supports WebM, browsers will skip the WebM `<source>` and use the MPEG-4
instead. If you prefer a WebM `<source>` be used first, specify it first!
{% endAside %}

## Preview

{% Instruction 'preview' %}

The experience should look the same. So far so good.

## Verify with Lighthouse

With the live site open:
{% Instruction 'audit-performance', 'ol' %}

You should see that the "Use video formats for animated content" audit is now
passing! Woohoo! 💪

# Serve images with correct dimensions

This Glitch is small enough that its images could be inspected by hand. However
for most websites, using a tool like Lighthouse to automate this is essential.

{% Instruction 'preview', 'ol' %}
{% Instruction 'audit-performance', 'ol' %}
1. Look for the results of the **Properly Size Images** audit.

{% Img src="image/admin/rGu0xlOX97DGQlujoHad.png", alt="The properly size images audit failing in Lighthouse.", width="800", height="320" %}

The Lighthouse audit shows that both of this page's images need to be resized.

## Fix `flower_logo.png`

Start at the top of the page and fix the logo image.

- Inspect `flower_logo.png` in the DevTools Elements panel.

{% Img src="image/admin/W7Ig8iyp6dKpQyNkJX3S.png", alt="The DevTools elements panel", width="800", height="316" %}

This is the CSS for `flower_logo.png`:

```css
.logo {
  width: 50px;
  height: 50px;
}
```

The CSS width of this image is 50 pixels, so `flower_logo.png` should be resized
to match. You can use [ImageMagick](https://www.imagemagick.org) to resize the
image to fit. ImageMagick is a CLI tool for image editing that comes
pre-installed in the codelab environment.

{% Instruction 'remix', 'ol' %}
{% Instruction 'console', 'ol' %}
1. In the console, type:

```bash
convert flower_logo.png -resize 50x50 flower_logo.png
```

## Fix flower_photo.jpg

Next, fix the photo of the purple flowers.

- Inspect `flower_photo.jpg` in the DevTools elements panel.

{% Img src="image/admin/zGpgvrz00hsMHKYvQoyU.png", alt="The DevTools elements panel", width="800", height="275" %}

This is the CSS for `flower_photo.jpg`:

```css
.photo {
  width: 50vw;
  margin: 30px auto;
  border: 1px solid black;
}
```

`50vw` sets the CSS width of `flower_photo.jpg` to "half the width of
the browser."
([1vw](https://developer.mozilla.org/docs/Learn/CSS/Introduction_to_CSS/Values_and_units)
is equal to 1% the width of the browser).

The ideal size for this image would depend on the device it is being viewed on,
so you should resize it to a size that works well for most of your users. You
can check your analytics data to learn which screen resolutions are common
amongst your users:

{% Img src="image/admin/6j3106xF9uxWCZGh7PrL.png", alt="Google analytics of screen resolutions.", width="684", height="865" %}

This data indicates that 95%+ of the visitors to this site use screen
resolutions 1920 pixels wide or less.

Using this information we can calculate how wide the image should be:
(1920 pixels wide) * (50% of browser width) = 960 pixels

On resolutions greater than 1920 pixels wide, the image will be stretched to
cover the area. The resized image is still fairly large, so the effects of this
should not be very noticeable.

- Use [ImageMagick](https://www.imagemagick.org) to resize the image to 960
pixels wide. In the terminal type:

```bash
# macOS/Linux
convert flower_photo.jpg -resize 960x flower_photo.jpg

# Windows
magick convert flower_photo.jpg -resize 960x flower_photo.jpg
```

{% Aside %}
`960x` is not a typo; it specifies a width, but not a height. The
image height will be scaled in proportion to the width. This is a handy trick
for when you only care about an image's dimensions in one direction.
{% endAside %}

## Re-Run Lighthouse

- Re-run the Lighthouse Performance audit to verify that you have successfully
re-sized the images.

{% Img src="image/admin/ShzwI5v2hLhHR7CGDRqV.png", alt="Lighthouse properly size images audit.", width="800", height="271" %}

… And it fails! Why is that?

Lighthouse runs its tests on a Nexus 5x. The Nexus 5x has a 1080 x 1920 screen.
For the Nexus 5x, the optimal size of `flower_photo.jpg` would be 540 pixels
wide (1080 pixels * . 5). This is much smaller than our resized image.

Should you resize the image to be even smaller? Probably. However, the answer to
this isn't always clear-cut.

The trade-off here is between image quality on high-resolution devices and
performance. It's easy to overestimate how closely users will be inspecting
images—so you should probably make them smaller—but there are
certainly use cases where image quality is more important.

The good news is that you can bypass this tradeoff altogether by using
responsive images to serve multiple images sizes. You can learn more about this
in the [Responsive Images guide](/serve-responsive-images).

# Creating WebP Images with the Command Line

The webp
<a href="https://developers.google.com/speed/webp/docs/precompiled">command line tool</a>
has already been installed for you, so you're all set to get started. This tool
converts JPG, PNG, and TIFF images to WebP.

## Convert images to WebP

{% Instruction 'remix', 'ol' %}
{% Instruction 'console', 'ol' %}
1. Type the following command:

```bash
cwebp -q 50 images/flower1.jpg -o images/flower1.webp
```

This command converts, at a quality of `50` (`0` is the worst; `100` is the
best), the `images/flower1.jpg` file and saves it as `images/flower1.webp`.

{% Aside %}
Are you wondering why you type `cwebp` instead of `webp`?
WebP has two separate commands for encoding and decoding WebP images.
`cwebp` encodes images to WebP, while `dwebp` decodes
images from WebP.
{% endAside %}

After doing this, you should see something like this in the console:

```bash
Saving file 'images/flower1.webp'
File:      images/flower1.jpg
Dimension: 504 x 378
Output:    29538 bytes Y-U-V-All-PSNR 34.57 36.57 36.12   35.09 dB
           (1.24 bpp)
block count:  intra4:        750  (97.66%)
              intra16:        18  (2.34%)
              skipped:         0  (0.00%)
bytes used:  header:            116  (0.4%)
             mode-partition:   4014  (13.6%)
 Residuals bytes  |segment 1|segment 2|segment 3|segment 4|  total
    macroblocks:  |      22%|      26%|      36%|      17%|     768
      quantizer:  |      52 |      42 |      33 |      24 |
   filter level:  |      16 |       9 |       6 |      26 |
```

You've just successfully converted the image to WebP.

However, running the `cwebp` command one image at a time like this would take a
long time to convert many images. If you need to do this, you can use a script
instead.

- Run this script in the console (don't forget the backticks):

```bash
`for file in images/*; do cwebp -q 50 "$file" -o "${file%.*}.webp"; done`
```

This script converts, at a quality of `50`, all the files in the `images/`
directory, and saves them as a new file (same filename, but with a `.webp` file
extension) in the same directory.

### ✔︎ Check-in

You should now have 6 files in your `images/` directory:

```shell
flower1.jpg
flower1.webp
flower2.jpg
flower2.webp
flower3.png
flower3.webp
```

Next, update this Glitch to serve WebP images to browsers that support it.

## Add WebP images using the `<picture>` tag

The `<picture>` tag allows you to serve WebP to newer browsers while maintaining
support for older browsers.

- In `index.html` replace `<img src="images/flower1.jpg"/>` with the following
HTML:

```html
<picture>
  <source type="image/webp" srcset="images/flower1.webp">
  <source type="image/jpeg" srcset="images/flower1.jpg">
  <img src="images/flower1.jpg">
</picture>
```

- Next, replace the `<img>` tags for `flower2.jpg` and `flower3.png` with
`<picture>` tags.

### ✔︎ Check-in

Once completed, the `<picture>` tags in `index.html` should look like this:

```html
<picture>
  <source type="image/webp" srcset="images/flower1.webp">
  <source type="image/jpeg" srcset="images/flower1.jpg">
  <img src="images/flower1.jpg">
</picture>
<picture>
  <source type="image/webp" srcset="images/flower2.webp">
  <source type="image/jpeg" srcset="images/flower2.jpg">
  <img src="images/flower2.jpg">
</picture>
<picture>
  <source type="image/webp" srcset="images/flower3.webp">
  <source type="image/png" srcset="images/flower3.png">
  <img src="images/flower3.png">
</picture>
```

Next, use Lighthouse to verify you've correctly implemented WebP images on the
site.

## Verify WebP usage with Lighthouse

Lighthouse's **Serve images in next-gen formats** performance audit can let you
know if all the images on your site are using next-gen formats like WebP.

{% Instruction 'preview', 'ol' %}
{% Instruction 'audit-performance', 'ol' %}
1. Verify the **Serve images in next-gen formats** audit is passed.

{% Img src="image/admin/Y8x0FLWs1Xsf32DX20DG.png", alt="Passing 'Serve images in next-gen formats' audit in Lighthouse", width="701", height="651" %}

Success! You are now serving WebP images on your site.

# Serve modern code to modern browsers for faster page loads

{% include 'content/devtools-headsup.njk' %}

In this codelab, improve the performance of this simple application that allows
users to rate random cats. Learn how to optimize the JavaScript bundle by
minizming how much code is transpiled.

{% Img src="image/admin/E8nNmPiUnD9unvl0ydwT.png", alt="App screenshot", width="800", height="681" %}

In the sample app,
you can select a word or emoji to convey how much you like each cat.
When you click a button,
the app displays the button's value underneath the current cat image.

## Measure

{% Aside %}
 Since webpack is used in this application, any changes made to the code will trigger a new build, which can take a few seconds. Once it completes, you should see your changes reflected in the application.
{% endAside %}

It's always a good idea to begin by inspecting a website before adding any
optimizations:

{% Instruction 'preview', 'ol' %}
{% Instruction 'devtools-network', 'ol' %}
{% Instruction 'disable-cache', 'ol' %}
{% Instruction 'reload-app', 'ol' %}

{% Img src="image/admin/WOLgtkSsnLlAVqXXkvS2.png", alt="Original bundle size request", width="717", height="127" %}

Over 80 KB is used for this application! Time to find out if parts of the bundle
aren't being used:

{% Instruction 'devtools-command', 'ol' %}
<!--lint disable code-block-style-->
    {% Img src="image/admin/bUJOV2XobvxF0LrIvML4.png", alt="Command Menu", width="800", height="170" %}

1. Enter `Show Coverage` and hit `Enter` to display the **Coverage** tab.
1. In the **Coverage** tab, click **Reload** to reload the
application while capturing coverage.

    {% Img src="image/admin/MVS59W0Ei307x4Ab3FvU.png", alt="Reload app with code coverage", width="800", height="363" %}

1. Take a look at how much code was used versus how much was loaded for
the main bundle:

    {% Img src="image/admin/YaXfH1OcPENdyMeBBrqK.png", alt="Code coverage of bundle", width="800", height="77" %}
<!--lint enable code-block-style-->
Over half the bundle (44 KB) is not even utilized. This is because a lot of the
code within consists of polyfills to ensure that the application works in older
browsers.

## Use @babel/preset-env

The syntax of the JavaScript language conforms to a standard known as
ECMAScript, or [ECMA-262](https://github.com/tc39/ecma262). Newer versions of
the specification are released every year and include new features that have
passed the proposal process. Each major browser is always at a different stage
of supporting these features.

<!-- lint disable no-repeat-punctuation -->
<!-- note "..." is used in the url for "For…of loop" -->
The following ES2015 features are used in the application:

+  [Arrow functions](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
+  [Template literals](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals)
+  [For…of loop](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/for...of)
+  [Destructuring assignment](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring)
<!-- lint enable no-repeat-punctuation -->

The following ES2017 feature is used as well:

+  [Async functions](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function)

Feel free to dive into the source code in `src/index.js` to see how all of this
is used.

All of these features are supported in the latest version of Chrome, but what
about other browsers that don't support them? [Babel](https://babeljs.io/docs/en),
which is included in the application, is the most popular library used to compile
code that contains newer syntax into code that older browsers and environments can
understand. It does this in two ways:

+ **Polyfills** are included to emulate newer ES2015+ functions so that their APIs
can be used even if it is not supported by the browser. Here is an example of a
[polyfill](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill)
of the `Array.includes` method.
+ **Plugins** are used to transform ES2015 code (or later) into older ES5 syntax.
Since these are syntax related changes (such as arrow functions), they cannot be emulated with polyfills.

Look at `package.json` to see which Babel libraries are included:

```json
"dependencies": {
  "@babel/polyfill": "^7.0.0"
},
"devDependencies": {
  //...
  "babel-loader": "^8.0.2",
  "@babel/core": "^7.1.0",
  "@babel/preset-env": "^7.1.0",
  //...
}
```

+ `@babel/core` is the core Babel compiler. With this, all the Babel configurations are defined in a `.babelrc` at the root of the project.
+ `babel-loader` includes Babel in the webpack build process.

Now look at `webpack.config.js` to see how `babel-loader` is included as a
rule:

<pre>
module: {
  rules: [
    //...
    <strong>{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "babel-loader"
    }</strong>
  ]
},
</pre>

+ `@babel/polyfill` provides all the necessary polyfills for any newer ECMAScript features so that they can
work in environments that do not support them. It is already imported at the very top of `src/index.js.`

```js/1
import "./style.css";
import "@babel/polyfill";
```

+ `@babel/preset-env` identifies which transforms and polyfills are necessary
  for any browsers or environments chosen as targets.

Take a look at the Babel configurations file, `.babelrc`, to see how it's
included:

```json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions"
      }
    ]
  ]
}
```

This is a Babel and webpack setup. [Learn how to include Babel in your
application](https://babeljs.io/en/setup) if you happen to use a different
module bundler than webpack.

The `targets` attribute
in `.babelrc` identifies which browsers are being targeted. `@babel/preset-env`
integrates with browserslist, which means you can find a full list of compatible
queries that can be used in this field in the
[browserlist documentation](https://github.com/browserslist/browserslist#full-list).

The `"last 2 versions"` value transpiles the code in the application for the
[last two versions](http://browserl.ist/?q=last+2+versions) of every browser.

### Debugging

To get a complete look at all the browser's Babel targets as well as all the
transforms and polyfills that are included, add a `debug` field to `.babelrc:`

```js/6
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "debug": true
      }
    ]
  ]
}
```

- Click **Tools**.
- Click **Logs**.

Reload the application and take a look at the Glitch status logs at the bottom
of the editor.

### Targeted browsers

Babel logs a number of details to the console about the compilation process,
including all the target environments that the code has been compiled for.

{% Img src="image/admin/X6nauvGqKuyWoBekgCbw.png", alt="Targeted browsers", width="464", height="457" %}

Notice how discontinued browsers, such as Internet Explorer, are included in
this list. This is a problem because unsupported browsers won't have newer
features added, and Babel continues to transpile specific syntax for them. This
unnecessarily increases the size of your bundle if users are not using this
browser to access your site.

Babel also logs a list of transform plugins used:

{% Img src="image/admin/clNXHWSfeD2RKDkIrJo8.png", alt="List of plugins used", width="800", height="604" %}

That's a pretty long list! These are all the plugins that Babel needs to use to
transform any ES2015+ syntax to older syntax for all the targeted browsers.

However, Babel doesn't show any specific polyfills that are used:

{% Img src="image/admin/xPhc2usfwfIJkoK4EubY.png", alt="No polyfills added", width="800", height="38" %}

This is because the entire `@babel/polyfill` is being imported directly.

### Load polyfills individually

By default, Babel includes every polyfill needed for a complete ES2015+ environment when
`@babel/polyfill` is imported into a file. To import specific polyfills needed for
the target browsers, add a `useBuiltIns: 'entry'` to the configuration.

```js/7
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "debug": true
        "useBuiltIns": "entry"
      }
    ]
  ]
}
```

Reload the application. You can now see all the specific polyfills included:

{% Img src="image/admin/VQHDI6qG94zbTiorG3aT.gif", alt="List of polyfills imported", width="800", height="465" %}

Although only needed polyfills for `"last 2 versions"` is now included, it is still a super long list! This is because
polyfills needed for the target browsers for _every_ newer feature is still included. Change the value of the attribute to `usage`
to only include those needed for features that are being used in the code.

```js/8/7
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "debug": true,
        "useBuiltIns": "entry"
        "useBuiltIns": "usage"
      }
    ]
  ]
}
```

With this, polyfills are automatically included where needed.
This means you can remove the `@babel/polyfill` import in `src/index.js.`

```js//1
import "./style.css";
import "@babel/polyfill";
```

Now only the required polyfills needed for the application are included.

{% Img src="image/admin/ir2qkYhxoJDWoQUhD8KD.png", alt="List of polyfills automatically included", width="800", height="114" %}

The application bundle size is reduced significantly.

{% Img src="image/admin/6cIW1H9dYz38h3jp9QpK.png", alt="Bundle size reduced to 30.1 KB", width="796", height="135" %}

## Narrowing the list of supported browsers

The number of browser targets included is still quite large, and not many users
use discontinued browsers such as Internet Explorer. Update the configurations
to the following:

```js/6/5
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "targets": [">0.25%", "not ie 11"],
        "debug": true,
        "useBuiltIns": "usage",
      }
    ]
  ]
}
```

Take a look at the details for the fetched bundle.

{% Img src="image/admin/LQrCZtEqujASX2KBR9ku.png", alt="Bundle size of 30.0 KB", width="793", height="131" %}

Since the application is so small, there really isn't much of a difference with
these changes. However, using a browser market share percentage (such as
`">0.25%"`) along with excluding specific browsers that you are confident your
users are not using is the recommended approach. Take a look at the
["Last 2 versions" considered harmful](https://jamie.build/last-2-versions)
article by James Kyle to learn more about this.

## Use &lt;script type=&quot;module&quot;&gt;

There is still more room for improvement. Although a number of unused polyfills
have been removed, there are many that are being shipped that are not needed for
some browsers. By using modules, newer syntax can be written and shipped to
browsers directly without the use of any unnecessary polyfills.

**JavaScript modules** are a relatively new feature supported in [all major browsers](https://caniuse.com/#feat=es6-module).
Modules can be created using a `type="module"` attribute to define scripts that import and export from other
modules. For example:

```js
// math.mjs
export const add = (x, y) => x + y;

<!-- index.html -->
<script type="module">
  import { add } from './math.mjs';

  add(5, 2); // 7
</script>
```

Many newer ECMAScript features are already supported in environments that
support JavaScript modules (instead of needing Babel.) This means that the Babel
config can be modified to send two different versions of your application to the
browser:

+ A version that would work in newer browsers that support modules and that includes a module that is largely untranspiled but has a smaller file size
+ A version that includes a larger, transpiled script that would work in any legacy browser

## Using ES Modules with Babel

To have separate `@babel/preset-env` settings for the two versions of
the application, remove the `.babelrc` file. Babel settings can be added to the
webpack configuration by specifying two different compilation formats for each
version of the application.

Begin by adding a configuration for the legacy script to `webpack.config.js`:

```js
const legacyConfig = {
  entry,
  output: {
    path: path.resolve(__dirname, "public"),
    filename: "[name].bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: [
            ["@babel/preset-env", {
              useBuiltIns: "usage",
              targets: {
                esmodules: false
              }
            }]
          ]
        }
      },
      cssRule
    ]
  },
  plugins
}
```

Notice that instead of using the `targets` value for `"@babel/preset-env"`,
`esmodules` with a value of `false` is used instead. This means that Babel
includes all the necessary transforms and polyfills to target every browser that does
not yet support ES modules.

Add `entry`, `cssRule`, and `corePlugins` objects to the beginning of the
`webpack.config.js` file. These are all shared between both the module and
legacy scripts served to the browser.

```js
const entry = {
  main: "./src"
};

const cssRule = {
  test: /\.css$/,
  use: ExtractTextPlugin.extract({
    fallback: "style-loader",
    use: "css-loader"
  })
};

const plugins = [
  new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
  new HtmlWebpackPlugin({template: "./src/index.html"})
];
```

Now similarly, create a config object for the module script below where `legacyConfig` is defined:

```js
const moduleConfig = {
  entry,
  output: {
    path: path.resolve(__dirname, "public"),
    filename: "[name].mjs"
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: [
            ["@babel/preset-env", {
              useBuiltIns: "usage",
              targets: {
                esmodules: true
              }
            }]
          ]
        }
      },
      cssRule
    ]
  },
  plugins
}
```

The main difference here is that a `.mjs` file extension is used for the output
filename. The `esmodules` value is set to true here which means that the code
that is outputted into this module is a smaller, less compiled script that does
not go through any transformation in this example since all the features used
are already supported in browsers that support modules.

At the very end of the file, export both configurations in a single array.

```js
module.exports = [
  legacyConfig, moduleConfig
];
```

Now this builds both a smaller module for browsers that support it and a larger
transpiled script for older browsers.

Browsers that support modules ignore scripts with a `nomodule` attribute.
Conversely, browsers that do not support modules ignore script elements with
`type="module"`. This means you can include a module as well as a compiled
fallback. Ideally, the two versions of the application should be in `index.html`
like this:

```html
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>
```

Browsers that support modules fetch and execute `main.mjs` and ignore
`main.bundle.js.` The browsers that do not support modules do the opposite.

It is important to note that unlike regular scripts, module scripts are always deferred by default.
If you would like the equivalent `nomodule` script to also be deferred and only executed after
parsing, then you'll need to add the `defer` attribute:

```html
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>
```

The last thing that needs to be done here is to add the `module` and `nomodule`
attributes to the module and legacy script respectively, Import the
[ScriptExtHtmlWebpackPlugin](https://github.com/numical/script-ext-html-webpack-plugin)
at the very top of `webpack.config.js`:

```js/4
const path = require("path");

const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
```

Now update the `plugins` array in the configurations to include this plugin:

<pre>
const plugins = [
  new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
  new HtmlWebpackPlugin({template: "./src/index.html"}),
  new ScriptExtHtmlWebpackPlugin({
    <strong>module: /\.mjs$/,
    custom: [
      {
        test: /\.js$/,
        attribute: 'nomodule',
        value: ''
    },
    ]</strong>
  })
];
</pre>

These plugin settings add a `type="module"` attribute for all `.mjs` script
elements as well as a `nomodule` attribute for all `.js` script modules.

{% Aside %}
  If you're having trouble understanding how to add all of these configurations to `webpack.config.js`, take a look at the [complete version of the file](https://glitch.com/edit/#!/serve-modern-code-complete?path=webpack.config.js:1:0).
{% endAside %}

## Serving modules in the HTML document

The last thing that needs to be done is to output both the legacy and modern script elements to the HTML file. Unfortunately, the plugin that creates the final HTML file, `HTMLWebpackPlugin`, [does not currently support](https://github.com/jantimon/html-webpack-plugin/issues/782) the output of both the module and nomodule scripts. Although there are workarounds and separate plugins created to solve this problem, such as [BabelMultiTargetPlugin](https://github.com/DanielSchaffer/webpack-babel-multi-target-plugin) and [HTMLWebpackMultiBuildPlugin](https://github.com/firsttris/html-webpack-multi-build-plugin), a simpler approach of adding the module script element manually is used for the purpose of this tutorial.

Add the following to `src/index.js` at the end of the file:

```html/2
    ...
    </form>
    <script type="module" src="main.mjs"></script>
  </body>
</html>
```

Now load the application in a browser that supports modules, such as the latest
version of Chrome.

{% Img src="image/admin/7DHv0rPqfFE14Qp7OtqJ.png", alt="5.2 KB module fetched over network for newer browsers", width="800", height="100" %}

Only the module is fetched, with a much smaller bundle size due to it being
largely untranspiled! The other script element is completely ignored by the
browser.

If you load the application on an older browser, only the larger, transpiled
script with all the needed polyfills and transforms are be fetched. Here is a
screenshot for all the requests made on an older version of Chrome (version
38).

{% Img src="image/admin/xapbFvWftJjVdsekOAFJ.png", alt="30 KB script fetched for older browsers", width="800", height="324" %}

## Conclusion

You now understand how to use `@babel/preset-env` to provide only the necessary
polyfills required for targeted browsers. You also know how JavaScript modules
can improve performance further by shipping two different transpiled versions of an
application. With a decent understanding of how both these techniques can cut
your bundle size down significantly, go forth and optimize!

# Setting performance budgets with webpack

[Webpack](/webpack/)
combines all your imported files and packages them into one or more output
files known as bundles. Bundling is neat, but as your app grows your bundles
will grow too. You need to monitor bundle sizes to ensure that they don't grow
too large and affect how long your application takes to load. Webpack supports
setting [performance budgets](/performance-budgets-101)
based on **asset size** and it can keep an eye on bundle sizes for you.

To see it in action, here's an app that counts the days left until New Year's.
It's built with [React](https://reactjs.org/) and [moment.js](https://momentjs.com/).
(Just like real-world apps that increasingly rely on frameworks and libraries. 😉)

{% Img src="image/admin/xG1cKnEmDUJk6Dd4892x.png", alt="An app that counts the days left until New Year's day", width="800", height="459" %}

## Measure

This codelab already contains the app bundled with webpack.

{% Instruction 'remix', 'ol' %}
{% Instruction 'console', 'ol' %}
1. To get a color-coded list of assets and their sizes, type `webpack` in the console.

```bash
webpack
```

The main bundle is highlighted in yellow because it's larger than 244 KiB (250
KB).

{% Aside %}
Webpack uses the unit "KiB", which you might not have
seen before. 1 "KiB" and 1 "KB" are fairly close in size: 1 KiB is 1024 bytes,
while 1 KB is 1000 bytes.
{% endAside %}

<figure>
  {% Img src="image/admin/MpeaVZE4nJHOAAnlQ8Sz.png", alt="Webpack output showing bundle size of 323 KiB", width="640", height="59" %}
  <figcaption>
    Webpack warning you about bulky JS bundle ⚠️
  </figcaption>
</figure>

These warnings are enabled by default in [production mode](https://webpack.js.org/concepts/mode/)
and the default threshold is **244 KiB uncompressed**, for both assets and
[entry points](https://webpack.js.org/concepts/entry-points/)
(the combination of all assets used during the initial load of a page).

<figure>
  {% Img src="image/admin/tXgrsOqdJAzf6LTelr0v.png", alt="Webpack warning that the asset exceeds the recommended size limit", width="642", height="108" %}
  <figcaption>
    Webpack warning you about bulky JS bundle ⚠️
  </figcaption>
</figure>

Webpack will not only warn you, but it will also give you a recommendation on
how to downsize your bundles. You can learn more about the recommended techniques on
[Web Fundamentals](https://developers.google.com/web/fundamentals/performance/webpack/use-long-term-caching#lazy-loading).

<figure>
  {% Img src="image/admin/pygtt0At7nmByNKm38hr.png", alt="Webpack performance optimization recommendation", width="641", height="96" %}
  <figcaption>Webpack performance optimization recommendation 💁</figcaption>
</figure>

## Set a custom performance budget

An appropriate performance budget will depend on the nature of your project.
It's always best to [do your own research](/your-first-performance-budget).
A [good rule](/your-first-performance-budget#budget-for-quantity-based-metrics)
of thumb is to deliver under 170 KB of compressed/minified
[critical-path](/critical-rendering-path/)
resources.

{% Aside %}
Critical-path resources are the minimal set of resources required
for the browser to display the first screen's worth of content.
{% endAside %}

For this simple demo, try being even more conservative and set the budget to
100 KB (97.7 KiB). In `webpack.config.js`, add the following:

```js
module.exports = {
  //...
  performance: {
    maxAssetSize: 100000,
    maxEntrypointSize: 100000,
    hints: "warning"
  }
};
```

The new performance budget is set in **bytes**:

- 100000 bytes for individual assets (maxAssetSize)
- 100000 bytes for the entry-point (maxEntrypointSize)

In this case, there's only one bundle which also acts as the entry point.

Possible values for **hints** are:

1. **`warning`** (default): Displays a yellow warning message, but the build
   passes. It's best to use this in development environments.
2. **`error`**: Displays a red error message, but the build still passes. This
   setting is recommended for production builds.
3. **`false`**: No warnings or errors are shown.

<figure>
  {% Img src="image/admin/ZFP0SZfO4zzD0lyD29Vk.png", alt="Webpack performance error in red font", width="606", height="107" %}
  <figcaption>Webpack performance hint "error" 🚨</figcaption>
</figure>

## Optimize

The purpose of a performance budget is to warn you about performance issues
before they become too difficult to fix. There is always more than one way to
build an app and some techniques will make for faster load times. (A lot of
them are documented right here in [Optimizing your JavaScript](/fast#optimize-your-javascript). 🤓)

Frameworks and libraries make developers' lives easier, but end users don't
really care how apps are built, only that they're functional and fast. If you
find yourself going over the performance budget, it's time to think about
possible optimizations.

In the real world, large client-side frameworks are usually hard to swap out,
so it's important to use them wisely. With a bit of research, you can often
find smaller alternatives to popular libraries that do the job just as well
([date-fns](https://date-fns.org/) is a good alternative for [moment.js](https://momentjs.com/)).
Sometimes it makes more sense to not use a framework or library at all if it
has a significant performance impact.

Removing unused code a good way to optimize apps that include large third-party
libraries. The [Remove unused code guide](/remove-unused-code) explains this
process in detail and here's a quick way to rewrite the countdown code without using moment.js.

In **app/components/Countdown.jsx** remove:

```js//0-2
const today = moment();
const yearEnd = moment().endOf('year');
const daysLeft = yearEnd.diff(today, 'days');
```

And delete this line:

```js//0
const moment = require('moment');
```

It takes a bit of math, but you can implement the same countdown with vanilla
JavaScript:

```js
const today = new Date();
const year = today.getFullYear();
const yearEnd = new Date(year,11,31); //months are zero indexed in JS
const timeDiff = Math.abs(yearEnd.getTime() - today.getTime());
const daysLeft = Math.ceil(timeDiff / (1000 * 3600 * 24));
```

Now remove `moment.js` from `package.json` and run webpack in the console
again to build the optimized bundle.

Ta-da! You have shaved off 223 KiB (230KB) and the app is under budget.🎉

<figure>
  {% Img src="image/admin/A6TIDToLthjRrHvxluND.png", alt="Webpack bundle size output after optimization is 97.7 KiB", width="473", height="58" %}
</figure>

## Monitor

Setting up a performance budget in webpack takes just a couple of lines of code
and it will warn you if you ever (accidentally) add a big dependency. The
saying goes "out of sight, out of mind" but webpack can make sure that you are
aware of performance implications at all times.

# Specifying multiple slot widths

{% Instruction 'preview' %}

- Reload the app in differently sized browser windows to see the browser load
different images and use different layouts at different browser sizes.

## View the code

- Checkout `index.html` for the code that makes this work:

```html
<img src="flower.jpg"
  srcset="flower-small.jpg 480w, flower-large.jpg 800w"
  sizes="(max-width: 480px) 100vw, (max-width: 1024px) 50vw, 800px">
```

## What's going on here?

The value of the `sizes` attribute dictates that image display width will be:
"100% of the viewport width" on viewports up to 480px wide, "50% of the viewport
width" on screens 481–1024px wide, and 800px on screens wider than 1024px. These
widths match the widths specified in the CSS.

The ability to specify multiple slot widths accommodates page layouts that use
different styling (that is, image widths) for different viewport sizes.

## How to specify multiple slot widths

- Use a comma-separated list to specify multiple slot widths. Each list item,
except for the last item, consists of a media condition (e.g. `max-width` or
`min-width`) and a slot width.
- The last item in this list is the default slot width. It is the default, so
you do not need to specify a media condition.
- You can list as many slot widths as you want - the number of images listed in
`srcset` does not matter.
- Slot width can be specified using a variety of units. The following are all
valid widths:

- `100px`
- `33vw`
- `20em`
- `calc(50vw-10px)`

The following is not a valid width:

- `25%` (percentages cannot be used with the sizes attribute)

# Minify and compress network payloads with gzip

{% include 'content/devtools-headsup.njk' %}

This codelab explores how both minifying and compressing the JavaScript
bundle for the following application improves page performance by reducing
the app's request size.

{% Img src="image/admin/Ga0pUShY7cQ0BDAPgPuh.png", alt="App screenshot", width="800", height="535" %}

## Measure

{% Aside %}
 Since webpack is used in this application, any changes made to the code will
 trigger a new build which can take a few seconds. Once it completes, you should
 see your changes reflected in the application.
{% endAside %}

Before diving in to add optimizations, it's always a good idea to first analyze
the current state of the application.

{% Instruction 'preview' %}

This app, which was also covered in the ["Remove unused
code"](/remove-unused-code) codelab, lets you vote for your favorite
kitten. 🐈

Now take a look at how large this application is:

{% Instruction 'devtools-network', 'ol' %}
{% Instruction 'disable-cache', 'ol' %}
{% Instruction 'reload-app', 'ol' %}

{% Img src="image/admin/Zzm8kiE2W29yGEZC7C2u.png", alt="Original bundle size in Network panel", width="800", height="186" %}

Although a lot of progress was made in the ["Remove unused code"](/remove-unused-code)
codelab to trim this bundle size down, 225 KB is still quite large.

## Minification

Consider the following block of code.

```js
function soNice() {
  let counter = 0;

  while (counter < 100) {
    console.log('nice');
    counter++;
  }
}
```

If this function is saved in a file of its own, the file size is around
**112 B (bytes).**

If all whitespace is removed, the resulting code looks like this:

```js
function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}
```

The file size would now be around 83 B. If it gets further mangled by reducing
the length of variable name and modifying some expressions, the final code may
end up looking like this:

```js
function soNice(){for(let i=0;i<100;)console.log("nice"),i++}
```

The file size now reaches **62 B**.

With each step, the code is becoming harder to read. However, the browser's
JavaScript engine interprets each of these in the exact same way. The
benefit of obfuscating code in this manner can help achieve smaller file
sizes. 112 B really was not much to begin with, but there was still a 50%
reduction in size!

In this application, [webpack](https://webpack.js.org/) version 4 is used as a
module bundler. The specific version can be seen in `package.json`.

```json/2
"devDependencies": {
  //...
  "webpack": "^4.16.4",
  //...
}
```

Version 4 already minifies the bundle by default during production mode. It uses
`TerserWebpackPlugin` a plugin for [Terser](https://github.com/terser-js/terser).
Terser is a popular tool used to compress JavaScript code.

To get an idea of what the minified code looks like, go ahead and click
`main.bundle.js` while still in the DevTools **Network** panel. Now click the
**Response** tab.

{% Img src="image/admin/uti2q15O2MtiEsegYoV9.png", alt="Minified response", width="800", height="249" %}

The code in its final form, minified and mangled, is shown in the response body.
To find out how large the bundle may have been if it was not minified, open
`webpack.config.js` and update the `mode` configuration.

```js/2/1
module.exports = {
  mode: 'production',
  mode: 'none',
  //...
```

Reload the application and take a look at the bundle size again through the
DevTools **Network** panel

{% Img src="image/admin/H0lINRmM2gF6NHnzmkGM.png", alt="Bundle size of 767 KB", width="700", height="129" %}

That's a pretty big difference! 😅

Make sure to revert the changes here before continuing.

```js/1/2
module.exports = {
  mode: 'production',
  mode: 'none',
  //...
```

Including a process to minify code in your application depends on the tools
that you use:

+ If webpack v4 or greater is used, no additional work needs to be done
since code is minified by default in production mode. 👍
+ If an older version of webpack is used, install and include `TerserWebpackPlugin`
into the webpack build process. The [documentation](https://webpack.js.org/plugins/terser-webpack-plugin/)
explains this in detail.
+ Other minification plugins also exist and can be used instead,
such as [BabelMinifyWebpackPlugin](https://github.com/webpack-contrib/babel-minify-webpack-plugin)
and [ClosureCompilerPlugin](https://github.com/roman01la/webpack-closure-compiler).
+ If a module bundler is not being used at all, use [Terser](https://github.com/terser-js/terser)
as a CLI tool or include it directly as a dependency.

## Compression

{% Aside 'warning' %}
Many hosting platforms, CDNs and reverse proxy servers either encode
assets with compression by default or allow you to easily configure them. This
means that you may rarely ever need to set up your server similar to how it is
done in the compression section of this tutorial, but you can continue to read
if you are interested in learning how compression works.
{% endAside %}

Although the term "compression" is sometimes loosely used to explain how code is
reduced during the minification process, it isn't actually compressed in the
literal sense.

**Compression** usually refers to code that has been modified using a data
compression algorithm. Unlike minification which ends up providing perfectly
valid code, compressed code needs to be _decompressed_ before being used.

With every HTTP request and response, browsers and web servers can add
[headers](https://developer.mozilla.org/docs/Web/HTTP/Headers) to include
additional information about the asset being fetched or received. This can be
seen in the `Headers` tab within the DevTools Network panel where three types
are shown:

+ **General** represents general headers relevant to the entire request-response
  interaction.
+ **Response Headers** shows a list of headers specific to the actual response
  from the server.
+ **Request Headers** shows a list of headers attached to the request by the
  client.

Take a look at the `accept-encoding` header in the `Request Headers`.

{% Img src="image/admin/shmaD9cmjjFMITKL0TAW.png", alt="Accept encoding header", width="800", height="361" %}

`accept-encoding` is used by the browser to specify which content
encoding formats, or compression algorithms, it supports. There are many
text-compression algorithms out there, but there are only three that are
supported here for the compression (and decompression) of HTTP network requests:

* [Gzip](https://www.gzip.org/) (`gzip`): The most widely used compression
format for server and client interactions. It builds on top of the Deflate
algorithm and is supported in all current browsers.
* Deflate (`deflate`): Not commonly used.
* [Brotli](https://github.com/google/brotli) (`br`): A newer compression
algorithm that aims to further improve compression ratios, which can result in
even faster page loads. It is supported in the
[latest versions of most browsers](https://caniuse.com/#feat=brotli).

The sample application in this tutorial is identical to the app completed in the
["Remove unused code"](/remove-unused-code) codelab except for the fact that
[Express](https://expressjs.com/) is now used as a server framework. In the next
few sections, both static and dynamic compression is explored.

## Dynamic compression

**Dynamic** compression involves compressing assets on-the-fly as they get
requested by the browser.

### Pros

+ Creating and updating saved compressed versions of assets does not need to be
done.
+ Compressing on-the-fly works especially well for web pages that are
dynamically generated.

### Cons

+ Compressing files at higher levels to achieve better compression ratios
takes longer. This can cause a performance hit as the user waits for assets to
compress before they are sent by the server.

### Dynamic compression with Node/Express

The `server.js` file is responsible for setting up the Node server that hosts
the application.

```js
const express = require('express');

const app = express();

app.use(express.static('public'));

const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});
```

All this currently does is import `express` and use the `express.static`
middleware to load all the static HTML, JS and CSS files in the
`public/` directory (and those files are created by webpack with every build).

To make sure all of the assets are compressed every time they're requested, the
[compression](https://github.com/expressjs/compression) middleware library can
be used. Begin by adding it as a `devDependency` in `package.json`:

```json/2
"devDependencies": {
  //...
  "compression": "^1.7.3"
},
```

And import it into the server file, `server.js`:

```js/1
const express = require('express');
const compression = require('compression');
```

And add it as a middleware **before** `express.static` is mounted:

```js/4
//...

const app = express();

app.use(compression());

app.use(express.static('public'));

//...
```

Now reload the app and take a look at the bundle size in the **Network** panel.

{% Img src="image/admin/MMtTKWPKfht8RYd8BMYF.png", alt="Bundle size with dynamic compression", width="800", height="161" %}

From 225 KB to 61.6 KB! In the `Response Headers` now, a `content-encoding`
header shows that the server is sending down this file encoded with `gzip`.

{% Img src="image/admin/523FjNSQOK95lGzg0B6D.png", alt="Content encoding header", width="800", height="470" %}

## Static compression

The idea behind **static** compression is to have assets compressed and saved
ahead of time.

### Pros

+ Latency due to high compression levels is not a concern anymore.
Nothing needs to happen on-the-fly to compress files as they can now be fetched directly.

### Cons

+ Assets need to compressed with every build. Build times can increase
significantly if high compression levels are used.

### Static compression with Node/Express and webpack

Since static compression involves compressing files ahead of time, webpack
settings can be modified to compress assets as part of the build step.
[`CompressionPlugin`](https://github.com/webpack-contrib/compression-webpack-plugin)
can be used for this.

Begin by adding it as a `devDependency` in `package.json`:

```json/2
"devDependencies": {
  //...
  "compression-webpack-plugin": "^1.1.11"
},
```

Like any other webpack plugin, import it in the configurations file,
`webpack.config.js:`

```js/4
const path = require("path");

//...

const CompressionPlugin = require("compression-webpack-plugin");
```

And include it within the `plugins` array:

```js/4
module.exports = {
  //...
  plugins: [
    //...
    new CompressionPlugin()
  ]
}
```

By default, the plugin compresses the build files using `gzip`. Take a look
at the [documentation](https://github.com/webpack-contrib/compression-webpack-plugin)
to learn how to add options to use a different algorithm or include/exclude
certain files.

When the app reloads and rebuilds, a compressed version of the main bundle is
now created. Open the Glitch Console to take a look at what's inside the
final `public/` directory that's served by the Node server.

{% Aside %}
The `git.ignore` file includes the `public/` directory. Directories that
contain build files are usually included here.
Glitch also hides these files from the editor tree.
{% endAside %}

+ Click the **Tools** button.
+ Click the **Console** button.
+ In the console, run the following commands to change into the `public`
  directory and see all of its files:

```bash
cd public
ls
```

{% Img src="image/admin/YQwCT87xMfMwWiH63lTS.png", alt="Final outputted files in public directory", width="800", height="188" %}

The gzipped version of the bundle, `main.bundle.js.gz`, is now saved here as
well. `CompressionPlugin` also compresses `index.html` by default.

The next thing that needs to be done is tell the server to send these gzipped
files whenever their original JS versions are being requested. This can be done
by defining a new route in `server.js` before the files are served with
`express.static`.

<pre>
const express = require('express');
const app = express();

<strong>app.get('*.js', (req, res, next) => {
  req.url = req.url + '.gz';
  res.set('Content-Encoding', 'gzip');
  next();
});</strong>

app.use(express.static('public'));

//...
</pre>

`app.get` is used to tell the server how to respond to a GET request for a
specific endpoint. A callback function is then used to define how to handle this
request. The route works like this:

+ Specifying `'*.js'` as the first argument means that this works for every
endpoint that is fired to fetch a JS file.
+ Within the callback, `.gz` is attached to the URL of the request and the
`Content-Encoding` response header is set to `gzip`.
+ Finally, `next()` ensures that the sequence continues to any callback
that may be next.

Once the app reloads, take a look at the `Network` panel once more.

{% Img src="image/admin/b7xYRsSdNhWX5Lc8zE51.png", alt="Bundle size reduction with static compression", width="800", height="176" %}

Just like before, a significant reduction in bundle size!

## Conclusion

This codelab covered the process of minifying and compressing source code.
Both these techniques are becoming a default in many of the tools
available today, so it's important to find out whether your toolchain already
supports them or if you should begin applying both processes yourself.

# Minify and compress network payloads with brotli

{% include 'content/devtools-headsup.njk' %}

This codelab is an extension of the [Minify and compress network payloads
codelab](/codelab-text-compression)
and assumes you are familiar with the basics concepts of compression. As
compared to other compression algorithms like `gzip`, this codelab explores how
Brotli compression can further reduce compression ratios and your app's overall
size.

{% Img src="image/admin/k94QI9lhJ39aXpwUrMNk.png", alt="App screenshot", width="800", height="535" %}

## Measure

{% Aside %}
Since [webpack](https://webpack.js.org) is used in this application,
any changes made to the codewill trigger a new build which can take a few
seconds. Once it completes, you should see your changes reflectedin the
application.
{% endAside %}

Before diving in to add optimizations, it's always a good idea to first analyze
the current state of the application.

{% Instruction 'remix', 'ol' %}
{% Instruction 'preview', 'ol' %}

In the previous [Minify and compress network payloads
codelab](/codelab-text-compression),
we reduced the size of `main.js` from 225 KB to 61.6 KB. In this codelab, you
will explore how Brotli compression can reduce this bundle size even further.

## Brotli Compression

{% Aside 'warning' %}
Many hosting platforms, CDNs and reverse proxy servers either encode assets with
compression by default or allow you to easily configure them. If your hosting
platform supports Brotli, you may not need to setup your server to compress your
assets with Brotli as described in this tutorial.
{% endAside %}

[Brotli](https://opensource.googleblog.com/2015/09/introducing-brotli-new-compression.html)
is a newer compression algorithm which can provide even better text compression
results than `gzip`. According to
[CertSimple](https://certsimple.com/blog/nginx-brotli), Brotli performance is:
+  14% smaller than `gzip` for JavaScript
+  21% smaller than `gzip` for HTML
+  17% smaller than `gzip` for CSS

To use Brotli, your server must support HTTPS. Brotli is supported in the
[latest versions of most browsers](https://caniuse.com/#feat=brotli). Browsers
that support Brotli will include `br` in `Accept-Encoding` headers:

<pre>
Accept-Encoding: gzip, deflate, <strong>br</strong>
</pre>

You can determine which compression algorithm is used via the `Content-Encoding`
field in the Chrome Developer Tools Network tab (`Command+Option+I` or
`Ctrl+Alt+I`):

<figure>
  {% Img src="image/admin/ZddI1FFjEckeO8mabgYl.png", alt="Network panel", width="800", height="136" %}
</figure>

## Enabling Brotli

### Dynamic compression

**Dynamic compression** involves compressing assets on-the-fly as they get
requested by the browser.

#### Pros
* Creating and updating saved compressed versions of assets does not need to be
  done.
* Compressing on-the-fly works especially well for web pages that are
  dynamically generated.

#### Cons
+  Compressing files at higher levels to achieve better compression ratios takes
   longer. This can cause a performance hit as the user waits for assets to
   compress before they are sent by the server.

#### Dynamic compression with Node/Express

The `server.js` file is responsible for setting up the Node server that hosts
the application.

```js
var express = require('express');

var app = express();

app.use(express.static('public'));

var listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});
```

All this currently does is import `express` and use the `express.static`
middleware to load all the static HTML, JS and CSS files in the
`public/directory` (and those files are created by webpack with every build).

To make sure all of the assets are compressed using brotli every time they're
requested, the [`shrink-ray`](https://github.com/aickin/shrink-ray#readme)
module can be used. Begin by adding it as a `devDependency` in `package.json`:

```json/2
"devDependencies": {
  //...
  "shrink-ray": "^0.1.3"
},
```

And import it into the server file, `server.js`:

```js/1
var express = require('express');
var shrinkRay = require('shrink-ray');
```

And add it as a middleware before `express.static` is mounted:

```js/4
//...
var app = express();

// compress all requests
app.use(shrinkRay());

app.use(express.static('public'));
```

Now reload the app, and take a look at the bundle size in the Network panel:

<figure>
  {% Img src="image/admin/KXDWBC6aH4o6MVJvBu8X.png", alt="Bundle size with dynamic Brotli compression", width="724", height="97" %}
</figure>

You can now see `brotli` is applied from `bz` in the `Content-Encoding` header.
`main.bundle.js` is reduced from **225 KB to 53.1 KB**! This is ~14% smaller
compared to `gzip` (61.6 KB).

{% Aside %}
Brotli has eleven quality levels  from `0` (no compression) to
`9` (maximum compression). A quality of `1` is very fast
but less effective, whereas a quality setting of `11` is very slow
but provides big savings in file size. Note that unlike the standard brotli
library, which defaults to quality `11`, `shrink-ray`
defaults to quality `4`, which is generally more appropriate for
dynamic content. You can adjust the parameters of the brotli algorithm by
passing in an `options` parameters to
`shrinkRay([options])`.
{% endAside %}

### Static compression

The idea behind static compression is to have assets compressed and saved ahead
of time.

#### Pros
+  Latency due to high compression levels is not a concern anymore. Nothing
   needs to happen on-the-fly to compress files as they can now be fetched
   directly.

#### Cons
+  Assets need to compressed with every build. Build times can increase
   significantly if high compression levels are used.

#### Static compression with Node/Express and webpack

Since **static compression** involves compressing files ahead of time, webpack
settings can be modified to compress assets as part of the build step. The
`brotli-webpack-plugin` can be used for this.

Begin by adding it as a `devDependency` in `package.json`:

```js/2
"devDependencies": {
  //...
 "brotli-webpack-plugin": "^1.1.0"
},
```

Like any other webpack plugin, import it in the configurations file,
`webpack.config.js`:

```js/3
var path = require("path");

//...
var BrotliPlugin = require('brotli-webpack-plugin');
```

And include it within the plugins array:

```js/4-7
module.exports = {
  // ...
  plugins: [
    // ...
    new BrotliPlugin({
      asset: '[file].br',
      test: /\.(js)$/
    })
  ]
},
```

The plugin array uses the following arguments:
+  `asset`: The target asset name.
+  `[file]` is replaced with the original asset file name.
+  `test`: All assets that match this RegExp (that is, JavaScript assets ending in
   `.js`) are processed.

For example, `main.js` would be renamed to `main.js.br`.

When the app reloads and rebuilds, a compressed version of the main bundle is
now created. Open the Glitch Console to take a look at what's inside the final
`public/` directory that's served by the Node server.

1. Click the **Tools** button.
1. Click the **Console** button.
1. In the console, run the following commands to change into the `public`
   directory and see all of its files:

```bash
cd public
ls -lh
```

<figure>
  {% Img src="image/admin/GOlRrAhdDWO7yi7Bm1Pg.png", alt="Bundle size with static Brotli compression", width="489", height="133" %}
</figure>

The brotli compressed version of the bundle, `main.bundle.js.br`, is now saved
here as well and is **~76% smaller in size** (225 KB vs. 53 KB) than
`main.bundle.js`.

Next, tell the server to send these brotli-compressed files whenever their
original JS versions are being requested. This can be done by defining a new
route in `server.js` before the files are served with `express.static`.

```js/4-9
var express = require('express');

var app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.br';
  res.set('Content-Encoding', 'br');
  res.set('Content-Type', 'application/javascript; charset=UTF-8');
  next();
});

app.use(express.static('public'));
```

`app.get` is used to tell the server how to respond to a `GET` request for a
specific endpoint. A callback function is then used to define how to handle this
request. The route works like this:
+ Specifying `'*.js'` as the first argument means that this works for every
  endpoint that is fired to fetch a JS file.
+ Within the callback, `.br` is attached to the URL of the request and the
  `Content-Encoding` response header is set to `br`.
+ The `Content-Type` header is set to `application/javascript; charset=UTF-8` to
  specify the MIME type.
+ Finally, `next()` ensures that the sequence continues to any callback that may
  be next.

Because some browsers may not support brotli compression, confirm brotli is
supported before returning the brotli-compressed file by checking the
`Accept-Encoding` request header includes `br`:

```js/5,10
var express = require('express');

var app = express();

app.get('*.js', (req, res, next) => {
  if (req.header('Accept-Encoding').includes('br')) {
    req.url = req.url + '.br';
    console.log(req.header('Accept-Encoding'));
    res.set('Content-Encoding', 'br');
    res.set('Content-Type', 'application/javascript; charset=UTF-8');
  }
  next();
});

app.use(express.static('public'));
```

Once the app reloads, take a look at the Network panel once more.

<figure>
  {% Img src="image/admin/2rJrkqLzWt3MT4XxNsYn.png", alt="Bundle size of 53.1 KB (from 225KB)", width="724", height="97" %}
</figure>

Success! You have used Brotli compression to further compress your assets!

## Conclusion

This codelab illustrated how `brotli` can further reduce your app's overall
size. Where supported, `brotli` is a more powerful compression algorithm than
`gzip`.

{% Aside 'warning' %}
Remember to check if your CDN supports `brotli` before manually
implementing. If you need to implement `brotli` manually (as
described in this codelab) but have CDN support for other compression algorithms
such as `gzip`, it is a good idea to weigh the benefits of
`brotli` against the effort required to implement.
{% endAside %}

# Two ways to prefetch: `<link>` tags and HTTP headers

{% include 'content/devtools-headsup.njk' %}

In this codelab, you'll implement prefetching in two ways: with `<link rel="prefetch">` and with HTTP `Link` header.

The sample app is a website that has a promotional landing page with a special discount for the shop's best selling t-shirt. Since the landing page links to a single product, it's safe to assume that a high percentage of users will navigate to the product details page. This makes the product page a great candidate to prefetch on the landing page.

## Measure performance

First establish the baseline performance:

{% Instruction 'remix', 'ol' %}
{% Instruction 'preview', 'ol' %}
{% Instruction 'devtools-network', 'ol' %}

1. In the **Throttling** drop-down list, select **Fast 3G** to simulate a slow connection type.
1. To load the product page, click **Buy now** in the sample app.

The `product-details.html` page takes about 600 ms to load:

{% Img src="image/admin/MVpybZcY1aF8slLFgJ0n.png", alt="Network panel showing load times for product-details.html", width="800", height="186" %}

## Prefetch the product page with `<link rel="prefetch">`

To improve navigation, insert a `prefetch` tag in the landing page to prefetch the `product-details.html` page:

- Add the following `<link>` element to the head of the `views/index.html` file:

```html/7-7/
<!doctype html>
  <html>
    <head>
       <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet">

      <link rel="prefetch" href="/product-details.html" as="document">
	  ...
</head>
```

The `as` attribute is optional but recommended; it helps the browser set the right headers and determine whether the resource is already in the cache.  Example values for this attribute include: `document`, `script`, `style`, `font`, `image`, and [others](https://developer.mozilla.org/docs/Web/HTML/Element/link#Attributes).

To verify that prefetching is working:

{% Instruction 'preview', 'ol' %}
{% Instruction 'devtools-network', 'ol' %}

1. In the **Throttling** drop-down list, select **Fast 3G** to simulate a slow connection type.
1. Clear the Disable cache checkbox.
{% Instruction 'reload-app', 'ol' %}

Now when the landing page loads, the `product-details.html` page loads too, but at the lowest priority:

{% Img src="image/admin/LDkU6zNbFU7GhPuUcaCR.png", alt="Network panel showing product-details.html prefetched.", width="800", height="172" %}

The page is kept in the [HTTP cache](https://developer.mozilla.org/docs/Web/HTTP/Caching) for five minutes, after which the normal [`Cache-Control`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control) rules for the document apply. In this case, `product-details.html` has  a `cache-control` header with a value of `public, max-age=0`, which means that the page is kept for a total of five minutes.

### Reevaluate performance

{% Instruction 'reload-app', 'ol' %}
1. To load the product page, click **Buy now** in the sample app.

Take a look at the **Network** panel. There are two differences compared to the initial network trace:

- The **Size** column shows "prefetch cache", which means this resource was retrieved from the browser's cache rather than the network.
- The **Time** column shows that the time it takes for the document to load is now about 10 ms.

This is approximately a 98% reduction compared to the previous version, which took about 600 ms.

{% Img src="image/admin/gJZPsifaqFPozkhnMznX.png", alt="Network panel showing product-details.html retrieved from prefetch cache.", width="800", height="223" %}

## Extra credit: Use `prefetch` as a progressive enhancement

Prefetching is best implemented as a progressive enhancement for the users who are browsing on fast connections. You can use the [Network Information API](https://developer.mozilla.org/docs/Web/API/Network_Information_API) to check the network conditions and based on that dynamically inject prefetch tags. That way, you can minimize data consumption and save costs for users on slow or expensive data plans.

To implement adaptive prefetching, first remove the `<link rel="prefetch">` tag from `views/index.html`:

```html//6
<!doctype html>
  <html>
    <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet">
	   <link rel="prefetch" href="/product-details.html" as="document">
	   ...
	</head>
```

Then add the following code to `public/script.js` to declare a function that dynamically injects the `prefetch` tag when the user is on a fast connection:

```js/0-14/
function injectLinkPrefetchIn4g(url) {
	if (window.navigator.connection.effectiveType === '4g') {
		//generate link prefetch tag
		const linkTag = document.createElement('link');
		linkTag.rel = 'prefetch';
		linkTag.href = url;
		linkTag.as = 'document';

		//inject tag in the head of the document
		document.head.appendChild(linkTag);
	}
}
```

The function works as follows:

- It checks the [effectiveType](https://developer.mozilla.org/docs/Web/API/NetworkInformation/effectiveType) property of the [Network Information API](https://developer.mozilla.org/docs/Web/API/Network_Information_API) to determine if the user is on a 4G (or faster) connection.
- If that condition is fulfilled, it generates a `<link>` tag with `prefetch` as the type of hint, passes the URL that will be prefetched in the `href` attribute, and indicates that the resource is an HTML `document` in the `as` attribute.
- Finally, it injects the script dynamically in the `head` of the page.

Next add `script.js` to `views/index.html`, just before the closing `</body>` tag:

```html/2-2/
<body>
      ...
      <script src="/script.js"></script>
</body>

```

Requesting `script.js` at the end of the page ensures that it will be loaded and executed after the page is parsed and loaded.

To make sure that the prefetching doesn't interfere with critical resources for the current page, add the following code snippet to call `injectLinkPrefetchIn4g()` on the `window.load` event:

```html/4-7/
<body>
      ...
      <script src="/script.js"></script>
      <script>
           window.addEventListener('load', () => {
                injectLinkPrefetchIn4g('/product-details.html');
           });
      </script>
</body>

```

The landing page now prefetches `product-details.html` only on fast connections. To verify that:

{% Instruction 'preview', 'ol' %}
{% Instruction 'devtools-network', 'ol' %}
1. In the **Throttling** drop-down list, select **Online**.
{% Instruction 'reload-app', 'ol' %}

You should see `product-details.html` in the Network panel:

{% Img src="image/admin/NCoFDNGs0iSfBkDjiwzd.png", alt="Network panel showing product-details.html prefetched.", width="800", height="201" %}

To verify that the product page isn't prefetched on slow connections:

1. In the Throttling drop-down list, select **Slow 3G**.
{% Instruction 'reload-app', 'ol' %}

The **Network** panel should include only the resources for the landing page without `product-details.html`:

{% Img src="image/admin/xpHuregNQIEKrVylhG3G.png", alt="Network panel showing product-details.html not being prefetched.", width="800", height="171" %}

## Prefetch the stylesheet for the product page with the HTTP `Link` header

The HTTP `Link` header can be used to prefetch the same type of resources as the `link` tag. Deciding when to use one or the other mostly depends on your preference, as the difference in performance is insignificant. In this case, you'll use it to prefetch the main CSS of the product page, to further improve its rendering.

Add an HTTP `Link` header for `style-product.css` in the server response for the landing page:

1. Open the `server.js` file and look for the `get()` handler for the root url: `/`.
1. Add the following line at the beginning of the handler:

```js/1-1/
app.get('/', function(request, response) {
	response.set('Link', '</style-product.css>; rel=prefetch');
	response.sendFile(__dirname + '/views/index.html');
});
```

{% Instruction 'preview', 'ol' %}
{% Instruction 'devtools-network', 'ol' %}
{% Instruction 'reload-app', 'ol' %}

The `style-product.css` is now prefetched at the lowest priority after the landing page loads:

{% Img src="image/admin/Memd8AIP4Yr5dhGGi240.png", alt="Network panel showing style-product.css prefetched.", width="800", height="205" %}

To navigate to the product page, click **Buy now**. Take a look at the **Network** panel:

{% Img src="image/admin/12tQkKKPqx4JjaWofEYK.png", alt="Network panel showing style-product.css retrieved from prefetch cache.", width="800", height="223" %}

The `style-product.css` file is retrieved from the "prefetch cache" and it took only 12 ms to load.

{% Aside %}
When using HTTP `Link` header, you can decide whether to prefetch depending on the network conditions based on the information available in [client hints](/performance-optimizing-content-efficiency-client-hints//).
{% endAside %}

# Lazy load offscreen images with lazysizes

Lazy loading is the approach of waiting to load resources until they are needed,
rather than loading them in advance. This can improve performance by reducing
the amount of resources that need to be loaded and parsed on initial page load.

Images that are offscreen during the initial pageload are ideal candidates for
this technique. Best of all, [lazysizes](https://github.com/aFarkas/lazysizes)
makes this a very simple strategy to implement.

## Add the lazysizes script to the page

{% Instruction 'remix' %}

`lazysizes.min.js` has already been downloaded and added to this Glitch. To
include it in the page:

- Add the following `<script>` tag to `index.html`:

```html/0
  <script src="lazysizes.min.js" async></script>
  <!-- Images End -->
</body>
```

{% Aside %}
The [lazysizes.min.js](https://raw.githubusercontent.com/aFarkas/lazysizes/gh-pages/lazysizes.min.js)
file has already been added to this project, so there is no need to add it
separately. The script you just added can use this script.
{% endAside %}

lazysizes will intelligently load images as the user scrolls through the page
and prioritize the images that the user is going to encounter soon.

## Indicate the images to lazy load

- Add the class `lazyload` to images that should be lazy loaded. In addition,
  change the `src` attribute to `data-src`.

For example, the changes for `flower3.png` would look like this:

```html/1/0
<img src="images/flower3.png" alt="">
<img data-src="images/flower3.png" class="lazyload" alt="">
```

For this example, try lazy loading `flower3.png`, `flower4.jpg`, and
`flower5.jpg`.

{% Aside %}
You may be wondering why it is necessary to change the `src` attribute to
`data-src`. If this attribute is not changed, all the images will load
immediately instead of being lazy-loaded. `data-src` is not an attribute that
the browser recognizes, so when it encounters an image tag with this attribute,
it doesn't load the image. In this case, that is a good thing, because it then
allows the lazysizes script to decide when the image should be loaded, rather
than the browser.
{% endAside %}

## See it in action

That's it! To see these changes in action, follow these steps:

{% Instruction 'preview' %}

- Open the console and find the images that were just added. Their classes
  should change from `lazyload` to `lazyloaded` as you scroll down the page.

{% Img src="image/admin/yXej5KAOMzoqoQAB2paq.png", alt="Images being lazy loaded", width="428", height="252" %}

- Watch the network panel to see the image files load individually as you scroll
  down the page.

{% Img src="image/admin/tcQpLeAubOW1l42eyXiW.png", alt="Images being lazy loaded", width="418", height="233" %}

## Verify using Lighthouse

Lastly, it's a good idea to use Lighthouse to verify these changes. Lighthouse's
"Defer offscreen images" performance audit will indicate if you've forgotten to
add lazy loading to any offscreen images.

{% Instruction 'preview', 'ol' %}
{% Instruction 'audit-performance', 'ol' %}
1. Verify the **Defer offscreen images** audit was passed.

{% Img src="image/admin/AWMJnCEi3IAgANHhTgiC.png", alt="Passing 'Efficiently encode images' audit in Lighthouse", width="800", height="774" %}

Success! You have used lazysizes to lazy load the images on your page.

# Choose the correct level of compression

Images often account for most of the downloaded bytes on a web page
and also often occupy a significant amount of visual space.
As a result,
optimizing images can often yield some of the largest byte savings and performance improvements for your website:
the fewer bytes the browser has to download,
the less competition there is for the client's bandwidth
and the faster the browser can download and render useful content on the screen.

Image optimization is both an art and science:
an art because there is no one definitive answer for how best to compress an individual image,
and a science because there are many well developed techniques
and algorithms that can significantly reduce the size of an image.
Finding the optimal settings for your image requires careful analysis along many dimensions:
format capabilities, content of encoded data, quality, pixel dimensions, and more.

## Optimizing vector images

All modern browsers support Scalable Vector Graphics (SVG),
which is an XML-based image format for two-dimensional graphics.
You can embed the SVG markup directly on the page
or as an external resource.
Most vector-based drawing software can create SVG files or you can
write them by hand directly in your favorite text editor.

```xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.2" baseProfile="tiny" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
    x="0px" y="0px" viewBox="0 0 612 792" xml:space="preserve">
<g id="XMLID_1_">
  <g>
    <circle fill="red" stroke="black" stroke-width="2" stroke-miterlimit="10" cx="50" cy="50" r="40"/>
  </g>
</g>
</svg>
```

The above example renders the below simple circle shape with a black outline and red background
and was exported from Adobe Illustrator.

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.2" baseProfile="tiny" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
    x="0px" y="0px" viewBox="0 0 612 120" xml:space="preserve">
<g id="XMLID_1_">
  <g>
    <circle fill="red" stroke="black" stroke-width="2" stroke-miterlimit="10" cx="50" cy="50" r="40"/>
  </g>
</g>
</svg>

As you can tell, it contains a lot of metadata,
such as layer information, comments, and XML namespaces that are often unnecessary to render the asset in the browser.
As a result, it is always a good idea to minify your SVG files by running through a tool like [SVGO](https://github.com/svg/svgo).

Case in point, SVGO reduces the size of the above SVG file generated by Illustrator by 58%,
taking it from 470 to 199 bytes.

```xml
<svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 612 792"><circle fill="red" stroke="#000" stroke-width="2" stroke-miterlimit="10" cx="50" cy="50" r="40"/></svg>
```

Because SVG is an XML-based format,
you can also apply GZIP compression to reduce its transfer size&mdash;make sure your server is configured to compress SVG assets!

A raster image is simply a two-dimensional grid of individual "pixels"&mdash;for example,
a 100x100 pixel image is a sequence of 10,000 pixels.
In turn, each pixel stores the "[RGBA](https://en.wikipedia.org/wiki/RGBA_color_space)" values:
(R) red channel, (G) green channel, (B) blue channel, and (A) alpha (transparency) channel.

Internally, the browser allocates 256 values (shades) for each channel,
which translates to 8 bits per channel (2 ^ 8 = 256),
and 4 bytes per pixel (4 channels x 8 bits = 32 bits = 4 bytes).
As a result, if we know the dimensions of the grid we can easily calculate the filesize:

* 100x100 pixel image is composed of 10,000 pixels
* 10,000 pixels x 4 bytes = 40,000 bytes
* 40,000 bytes / 1024 = 39 KB

{% Aside %}
As an aside, regardless of the image format used to transfer the data from the server to the client,
when the image is decoded by the browser,
each pixel always occupies 4 bytes of memory.
This can be an important constraint for large images and devices which do not have a lot of available memory
&mdash;for example, low-end mobile devices.
{% endAside %}

<div class="table-wrapper">
<table>
<thead>
  <tr>
    <th>Dimensions</th>
    <th>Pixels</th>
    <th>File size</th>
  </tr>
</thead>
<tbody>
<tr>
  <td data-th="dimensions">100 x 100</td>
  <td data-th="pixels">10,000</td>
  <td data-th="file size">39 KB</td>
</tr>
<tr>
  <td data-th="dimensions">200 x 200</td>
  <td data-th="pixels">40,000</td>
  <td data-th="file size">156 KB</td>
</tr>
<tr>
  <td data-th="dimensions">300 x 300</td>
  <td data-th="pixels">90,000</td>
  <td data-th="file size">351 KB</td>
</tr>
<tr>
  <td data-th="dimensions">500 x 500</td>
  <td data-th="pixels">250,000</td>
  <td data-th="file size">977 KB</td>
</tr>
<tr>
  <td data-th="dimensions">800 x 800</td>
  <td data-th="pixels">640,000</td>
  <td data-th="file size">2500 KB</td>
</tr>
</tbody>
</table>
</div>

39&nbsp;KB for a 100x100 pixel image may not seem like a big deal,
but the filesize quickly explodes for larger images and makes image assets both slow and expensive to download.
This post has so far only focused on the "uncompressed" image format.
Thankfully, a lot can be done to reduce the image file size.

One simple strategy is to reduce the "bit-depth" of the image from 8 bits per channel to a smaller color palette:
8 bits per channel gives us 256 values per channel and 16,777,216 (256 ^ 3) colors in total.
What if you reduce the palette to 256 colors?
Then you would only need 8 bits in total for the RGB channels and immediately save two bytes per pixel&mdash;that's 50% compression savings over the original 4 bytes per pixel format!


<figure>
  {% Img src="image/admin/ssek7uXzhs67joEbp0P8.png", alt="Compression artifacts", width="612", height="266" %}
  <figcaption>Left to right (PNG): 32-bit (16M colors), 7-bit (128 colors), 5-bit (32 colors).</figcaption>
</figure>

Complex scenes with gradual color transitions (for example, gradients or sky)
require larger color palettes to avoid visual artifacts such as the pixelated sky in the 5-bit asset.
On the other hand, if the image only uses a few colors,
then a large palette is simply wasting precious bits!

Next, once you've optimized the data stored in individual pixels you could get more clever and look at nearby pixels as well:
turns out, many images, and especially photos, have many nearby pixels with similar colors&mdash;
for example, the sky, repeating textures, and so on.
Using this information to your advantage the compressor can apply [delta encoding](https://en.wikipedia.org/wiki/Delta_encoding)
where instead of storing the individual values for each pixel,
you can store the difference between nearby pixels:
if the adjacent pixels are the same, then the delta is "zero" and you only need to store a single bit!
But why stop there…

The human eye has different level of sensitivity to different colors:
you can optimize your color encoding to account for this by reducing or increasing the palette for those colors.
"Nearby" pixels form a two-dimensional grid. This means that each pixel has multiple neighbors:
you can use this fact to further improve delta encoding.
Instead of looking at just the immediate neighbors for each pixel,
you can look at larger blocks of nearby pixels and encode different blocks with different settings.

As you can tell, image optimization gets complicated quickly (or fun, depending on your perspective),
and is an active area of academic and commercial research.
Images occupy a lot of bytes and there is a lot of value in developing better image compression techniques!
If you're curious to learn more, head to the [Wikipedia page](https://en.wikipedia.org/wiki/Image_compression),
or check out the [WebP compression techniques whitepaper](https://developers.google.com/speed/webp/docs/compression) for a hands-on example.

So, once again, this is all great, but also very academic:
how does it help you to optimize images on your site?
Well, it's important to understand the shape of the problem: RGBA pixels, bit-depth, and various optimization techniques.
All of these concepts are critical to understand and keep in mind before you dive into the discussions of various raster image formats.

## Lossless versus lossy image compression

For certain types of data, such as source code for a page, or an executable file,
it is critical that a compressor does not alter or lose any of the original information:
a single missing or wrong bit of data could completely change the meaning of the contents of the file,
or worse, break it entirely.
For some other types of data, such as images, audio, and video,
it may be perfectly acceptable to deliver an "approximate" representation of the original data.

In fact, due to how the eye works,
we can often get away with discarding some information about each pixel in order to reduce the filesize of an image&mdash;
for example, our eyes have different sensitivity to different colors,
which means that we can use fewer bits to encode some colors.
As a result, a typical image optimization pipeline consists of two high level steps:

1. Image is processed with a [lossy](https://en.wikipedia.org/wiki/Lossy_compression) filter that eliminates some pixel data.
1. Image is processed with a [lossless](https://en.wikipedia.org/wiki/Lossless_compression) filter that compresses the pixel data.

The first step is optional,
and the exact algorithm will depend on the particular image format,
but it is important to understand that any image can undergo a lossy compression step to reduce its size.
In fact, the difference between various image formats, such as GIF, PNG, JPEG, and others,
is in the combination of the specific algorithms they use (or omit) when applying the lossy and lossless steps.

So, what is the "optimal" configuration of lossy and lossless optimization?
The answer depends on the image contents and your own criteria such as the tradeoff between filesize and artifacts introduced by lossy compression:
In some cases, you may want to skip lossy optimization to communicate intricate detail in its full fidelity.
In other cases, you may be able to apply aggressive lossy optimization to reduce the filesize of the image asset.
This is where your own judgment and context need to come into play&mdash;there is no one universal setting.

As a hands-on example, when using a lossy format such as JPEG,
the compressor will typically expose a customizable "quality" setting
(for example, the quality slider provided by the "Save for Web" functionality in Adobe Photoshop),
which is typically a number between 1 and 100 that controls the inner workings of the specific collection of lossy and lossless algorithms.
For best results, experiment with various quality settings for your images,
and don't be afraid to dial down the quality&mdash;the visual results are often very good and the filesize savings can be quite large.

{% Aside %}
Note that quality levels for different image formats are not directly comparable due to differences in algorithms used to encode the image:
quality 90 JPEG will produce a very different result than a quality 90 WebP.
In fact, even quality levels for the same image format may produce visibly different output based on implementation of the compressor!
{% endAside %}

## Effects of image compression on Core Web Vitals

Because images are often candidates for [Largest Contentful Paint](/lcp/), reducing the resource load time of an image can translate into better LCP in both [the lab](/lab-and-field-data-differences/#lab-data) and in [the field](/lab-and-field-data-differences/#field-data).

When playing with compression settings on raster image formats, be sure to experiment with WebP and AVIF formats to see if you can deliver the same image in a small footprint as compared to older formats.

You want to be careful not to _overcompress_ raster images, though. A good solution is to use an image optimization CDN to find the best compression settings for you, but an alternative may be to use tools like [Butteraugli](https://github.com/google/butteraugli) to estimate visual differences so that you don't encode images _too_ aggressively and lose too much quality.

## Image optimization checklist

Some tips and techniques to keep in mind as you work on optimizing your images:

* **Prefer vector formats:** vector images are resolution and scale independent,
which makes them a perfect fit for the multi-device and high-resolution world.
* **Minify and compress SVG assets:** XML markup produced by most drawing applications
often contains unnecessary metadata which can be removed;
Ensure that your servers are configured to apply GZIP compression for SVG assets.
* **Prefer WebP or AVIF over older raster formats**: [WebP](/serve-images-webp/) and [AVIF images](/compress-images-avif/) will usually be far smaller than older image formats.
* **Pick best raster image format:** determine your functional requirements and [select the one that suits each particular asset](/choose-the-right-image-format/).
* **Experiment with optimal quality settings for raster formats:** don't be afraid to dial down the "quality" settings,
the results are often very good and byte savings are significant.
* **Remove unnecessary image metadata:** many raster images contain unnecessary metadata about the asset:
geo information, camera information, and so on.
Use appropriate tools to strip this data.
* **Serve scaled images:** [resize images](/serve-images-with-correct-dimensions/) and ensure that the "display" size is as close as possible to the "natural" size of the image.
Pay close attention to large images in particular, as they account for largest overhead when resized!
* **Automate, automate, automate:** invest into automated tools and infrastructure that will ensure that all of your image assets are always optimized.

# Content delivery networks (CDNs)

Content delivery networks (CDNs) improve site performance by using a distributed network of servers to deliver resources to users. Because CDNs reduce server load, they reduce server costs and are well-suited to handling traffic spikes. This article discusses how CDNs work and provides platform-agnostic guidance on choosing, configuring, and optimizing a CDN setup.

## Overview

A content delivery network consists of a network of servers that are optimized for quickly delivering content to users. Although CDNs are arguably best known for serving cached content, CDNs can also improve the delivery of uncacheable content. Generally speaking, the more of your site delivered by your CDN, the better.

At a high-level, the performance benefits of CDNs stem from a handful of principles: CDN servers are located closer to users than [origin servers](https://en.wikipedia.org/wiki/Upstream_server) and therefore have a shorter [round-trip time (RTT)](https://en.wikipedia.org/wiki/Round-trip_delay) latency; networking optimizations allow CDNs to deliver content more quickly than if the content was loaded "directly" from the origin server; lastly, CDN caches eliminate the need for a request to travel to the origin server.

{% Aside 'key-term' %}
**Origin server** refers to the server that a CDN retrieves content from.
{% endAside %}

### Resource delivery

Although it may seem non-intuitive, using a CDN to deliver resources (even uncacheable ones) will typically be faster than having the user load the resource "directly" from your servers.

When a CDN is used to deliver resources from the origin, a new connection is established between the client and a nearby CDN server. The remainder of the journey (in other words, the data transfer between the CDN server and origin) occurs over the CDN's network - which often includes existing, persistent connections with the origin. The benefits of this are twofold: terminating the new connection as close to the user as possible eliminates unnecessary connection setup costs (establishing a new connection is expensive and requires multiple roundtrips); using a pre-warmed connection allows data to be immediately transferred at the maximum possible throughput.


<figure>
  {% Img src="image/admin/M9kzM7J7FenUyO7E9MF0.png", alt="Comparison of connection setup with and without a CDN", width="800", height="512" %}
</figure>

Some CDNs improve upon this even further by routing traffic to the origin through multiple CDN servers spread across the Internet. Connections between CDN servers occur over reliable and highly optimized routes, rather than routes determined by the [Border Gateway Protocol (BGP)](https://en.wikipedia.org/wiki/Border_Gateway_Protocol). Although BGP is the internet's de facto routing protocol, its routing decisions are not always performance-oriented. Therefore, BGP-determined routes are likely to be less performant than the finely-tuned routes between CDN servers.

<figure>
  {% Img src="image/admin/ZLMPFySQgBkpWvgujuJP.png", alt="Comparison of connection setup with and without a CDN", width="800", height="449" %}
</figure>


### Caching

Caching resources on a CDN's servers eliminates the need for a request to travel all the way to the origin in order to be served. As a result, the resource is delivered more quickly; this also reduces the load on the origin server.

#### Adding resources to the cache

The most commonly used method of populating CDN caches is to have the CDN "pull" resources as they are needed - this is known as "origin pull". The first time that a particular resource is requested from the cache the CDN will request it from the origin server and cache the response. In this manner, the contents of the cache are built-up over time as additional uncached resources are requested.

#### Removing resources from the cache

CDNs use cache eviction to periodically remove not-so-useful resources from the cache. In addition, site owners can use purging to explicitly remove resources.


*  **Cache eviction**

    Caches have a finite storage capacity. When a cache nears its capacity, it makes room for new resources by removing resources that haven't been accessed recently, or which take up a lot of space. This process is known as cache eviction. A resource being evicted from one cache does not necessarily mean that it has been evicted from all caches in a CDN network.

*  **Purging**

    Purging (also known as "cache invalidation") is a mechanism for removing a resource from a CDN's caches without having to wait for it to expire or be evicted. It is typically executed via an API. Purging is critical in situations where content needs to be retracted (for example, correcting typos, pricing errors, or incorrect news articles). On top of that, it can also play a crucial role in a site's caching strategy.

    If a CDN supports near instant purging, purging can be used as a mechanism for managing the caching of dynamic content: cache dynamic content using a long TTL, then purge the resource whenever it is updated. In this way, it is possible to maximize the caching duration of a dynamic resource, despite not knowing in advance when the resource will change. This technique is sometimes referred to as "hold-till-told caching".

    When purging is used at scale it is typically used in conjunction with a concept known as "cache tags" or "surrogate cache keys". This mechanism allows site owners to associate one or more additional identifiers (sometimes referred to as "tags") with a cached resource. These tags can then be used to carry out highly granular purging. For example, you might add a "footer" tag to all resources (for example, `/about`, `/blog`) that contain your site footer. When the footer is updated, instruct your CDN to purge all resources associated with the "footer" tag.

#### Cacheable resources

If and how a resource should be cached depends on whether it is public or private; static or dynamic.

##### Private and public resources

*  **Private Resources**

    Private resources contain data intended for a single user and therefore should not be cached by a CDN. Private resources are indicated by the `Cache-Control: private` header.

*  **Public Resources**

    Public resources do not contain user-specific information and therefore are cacheable by a CDN. A resource may be considered cacheable by a CDN if it does not have a `Cache-Control: no-store` or `Cache-Control: private` header. The length of time that a public resource can be cached depends on how frequently the asset changes.

##### Dynamic and static content

*   **Dynamic content**

    Dynamic content is content that changes frequently. An API response and a store homepage are examples of this content type. However, the fact that this content changes frequently doesn't necessarily preclude it from being cached. During periods of heavy traffic, caching these responses for very short periods of time (for example, 5 seconds) can significantly reduce the load on the origin server, while having minimal impact on data freshness.

*   **Static content**

    Static content changes infrequently, if ever. Images, videos, and versioned libraries are typically examples of this content type. Because static content does not change, it should be cached with a long Time to Live (TTL) - for example, 6 months or 1 year.

## Choosing a CDN

Performance is typically a top consideration when choosing a CDN. However, the other features that a CDN offers (for example, security and analytics features), as well as a CDN's pricing, support, and onboarding are all important to consider when choosing a CDN.


### Performance

At a high-level, a CDN's performance strategy can be thought of in terms of the tradeoff between minimizing latency and maximizing cache hit ratio. CDNs with many points of presence (PoPs) can deliver lower latency but may experience lower cache hit ratios as a result of traffic being split across more caches. Conversely, CDNs with fewer PoPs may be located geographically further from users, but can achieve higher cache hit ratios.

As a result of this tradeoff, some CDNs use a tiered approach to caching: PoPs located close to users (also known as "edge caches") are supplemented with central PoPs that have higher cache hit ratios. When an edge cache can't find a resource, it will look to a central PoP for the resource. This approach trades slightly greater latency for a higher likelihood that the resource can be served from a CDN cache - though not necessarily an edge cache.

The tradeoff between minimizing latency and maximizing cache hit ratio is a spectrum. No particular approach is universally better; however, depending on the nature of your site and its user base, you may find that one of these approaches delivers significantly better performance than the other.

It's also worth noting that CDN performance can vary significantly depending on geography, time of day, and even current events. Although it's always a good idea to do your own research on a CDN's performance, it can be difficult to predict the exact performance you'll get from a CDN.


### Additional features

CDNs typically offer a wide variety of features in addition to their core CDN offering. Commonly offered features include: load balancing, image optimization, video streaming, edge computing, and security products.


## How to setup and configure a CDN

Ideally you should use a CDN to serve your entire site. At a high-level, the setup process for this consists of signing up with a CDN provider, then updating your CNAME DNS record to point at the CDN provider. For example, the CNAME record for `www.example.com` might point to `example.my-cdn.com`. As a result of this DNS change, traffic to your site will be routed through the CDN.

If using a CDN to serve all resources is not an option, you can configure a CDN to only serve a subset of resources - for example, only static resources. You can do this by creating a separate CNAME record that will only be used for resources that should be served by the CDN. For example, you might create a `static.example.com` CNAME record that points to `example.my-cdn.com`. You would also need to rewrite the URLs of resources being served by the CDN to point to the `static.example.com` subdomain that you created.

Although your CDN will be set up at this point, there will likely be inefficiencies in your configuration. The next two sections of this article will explain how to get the most out of your CDN by increasing cache hit ratio and enabling performance features.


## Improving cache hit ratio

An effective CDN setup will serve as many resources as possible from the cache. This is commonly measured by cache hit ratio (CHR). Cache hit ratio is defined as the number of cache hits divided by the number of total requests during a given time interval.

A freshly initialized cache will have a CHR of 0 but this increases as the cache is populated with resources. A CHR of 90% is a good goal for most sites. Your CDN provider should supply you with analytics and reporting regarding your CHR.

When optimizing CHR, the first thing to verify is that all cacheable resources are being cached and cached for the correct length of time. This is a simple assessment that should be undertaken by all sites.

The next level of CHR optimization, broadly speaking, is to fine tune your CDN settings to make sure that logically equivalent server responses aren't being cached separately. This is a common inefficiency that occurs due to the impact of factors like query params, cookies, and request headers on caching.


### Initial audit

Most CDNs will provide cache analytics. In addition, tools like [WebPageTest](https://webpagetest.org/) and [Lighthouse](https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/) can also be used to quickly verify that all of a page's static resources are being cached for the correct length of time. This is accomplished by checking the HTTP Cache headers of each resource. Caching a resource using the maximum appropriate Time To Live (TTL) will avoid unnecessary origin fetches in the future and therefore increase CHR.

At a minimum, one of these headers typically needs to be set in order for a resource to be cached by a CDN:

*   `Cache-Control: max-age=`
*   `Cache-Control: s-maxage=`
*   `Expires`

In addition, although it does not impact if or how a resource is cached by a CDN, it is good practice to also set the [`Cache-Control: immutable`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control#Revalidation_and_reloading) directive.`Cache-Control: immutable` indicates that a resource "will not be updated during its freshness lifetime". As a result, the browser will not revalidate the resource when serving it from the browser cache, thereby eliminating an unnecessary server request. Unfortunately, this directive is only [supported](https://caniuse.com/#feat=mdn-http_headers_cache-control_immutable) by Firefox and Safari - it is not supported by Chromium-based browsers. This [issue](https://bugs.chromium.org/p/chromium/issues/detail?id=611416) tracks Chromium support for `Cache-Control: immutable`. Starring this issue can help encourage support for this feature.

For a more detailed explanation of HTTP caching, refer to [Prevent unnecessary network requests with the HTTP Cache](/http-cache/).


### Fine tuning

A slightly simplified explanation of how CDN caches work is that the URL of a resource is used as the key for caching and retrieving the resource from the cache. In practice, this is still overwhelmingly true, but is complicated slightly by the impact of things like request headers and query params. As a result, rewriting request URLs is an important technique for both maximizing CHR and ensuring that the correct content is served to users. A properly configured CDN instance strikes the correct balance between overly granular caching (which hurts CHR) and insufficiently granular caching (which results in incorrect responses being served to users).

#### Query params

By default, CDNs take query params into consideration when caching a resource. However, small adjustments to query param handling can have a significant impact on CHR. For example:



*   **Unnecessary query params**

    By default, a CDN would cache `example.com/blog` and `example.com/blog?referral_id=2zjk` separately even though they are likely the same underlying resource. This is fixed by adjusting a CDN's configuration to ignore the `referral\_id` query param.

*   **Query param order**

    A CDN will cache `example.com/blog?id=123&query=dogs` separately from `example.com/blog?query=dogs&id=123`. For most sites, query param order does not matter, so configuring the CDN to sort the query params (thereby normalizing the URL used to cache the server response) will increase CHR.


#### Vary

The [Vary](https://developer.mozilla.org/docs/Web/HTTP/Headers/Vary) response header informs caches that the server response corresponding to a particular URL can vary depending on the headers set on the request (for example, the [Accept-Language](https://developer.mozilla.org/docs/Web/HTTP/Headers/Accept-Language) or [Accept-Encoding](https://developer.mozilla.org/docs/Web/HTTP/Headers/Accept-Encoding) request headers). As a result, it instructs a CDN to cache these responses separately. The Vary header is not widely supported by CDNs and may result in an otherwise cacheable resource not being served from a cache.

Although the Vary header can be a useful tool, inappropriate usage hurts CHR. In addition, if you do use `Vary`, normalizing request headers will help improve CHR. For example, without normalization the request headers `Accept-Language: en-US` and `Accept-Language: en-US,en;q=0.9` would result in two separate cache entries, even though their contents would likely be identical.


#### Cookies

Cookies are set on requests via the [`Cookie`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cookie) header; they are set on responses via the `Set-Cookie` header. Unnecessary use of [`Set-Cookie`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie) header should be avoided given that caches will typically not cache server responses containing this header.


## Performance features

This section discusses performance features that are commonly offered by CDNs as part of their core product offering. Many sites forget to enable these features, thereby losing out on easy performance wins.


### Compression

All text-based responses should be [compressed](/reduce-network-payloads-using-text-compression/#data-compression) with either gzip or Brotli. If you have the choice, choose Brotli over gzip. Brotli is a newer compression algorithm, and compared to gzip, it can achieve higher compression ratios.

There are two types of CDN support for Brotli compression: "Brotli from origin" and "automatic Brotli compression".


#### Brotli from origin

Brotli from origin is when a CDN serves resources that were Brotli-compressed by the origin. Although this may seem like a feature that all CDNs should be able to support out of the box, it requires that a CDN be able to cache multiple versions (in other words, gzip-compressed and Brotli-compressed versions) of the resource corresponding to a given URL.


#### Automatic Brotli compression

Automatic Brotli compression is when resources are Brotli compressed by the CDN. CDNs can compress both cacheable and non-cacheable resources.

The first time that a resource is requested it is served using "good enough" compression - for example, Brotli-5. This type of compression is applicable to both cacheable and non-cacheable resources.

Meanwhile, if a resource is cacheable, the CDN will use offline processing to compress the resource at a more powerful but far slower compression level - for example, Brotli-11. Once this compression completes, the more compressed version will be cached and used for subsequent requests.


#### Compression best practices

Sites that want to maximize performance should apply Brotli compression at both their origin server and CDN. Brotli compression at the origin minimizes the transfer size of resources that can't be served from the cache. To prevent delays in serving requests, the origin should compress dynamic resources using a fairly conservative compression level - for example, Brotli-4; static resources can be compressed using Brotli-11. If an origin does not support Brotli, gzip-6 can be used to compress dynamic resources; gzip-9 can be used to compress static resources.


### TLS 1.3

TLS 1.3 is the newest version of [Transport Layer Security (TLS)](https://en.wikipedia.org/wiki/Transport_Layer_Security), the cryptographic protocol used by [HTTPS](https://en.wikipedia.org/wiki/HTTPS). TLS 1.3 provides better privacy and performance compared to TLS 1.2.

TLS 1.3 shortens the TLS handshake from two roundtrips to one. For connections using HTTP/1 or HTTP/2, shortening the TLS handshake to one roundtrip effectively reduces connection setup time by 33%.

<figure>
  {% Img src="image/admin/FnCSj1W23jXaiOWCp0Bw.png", alt="Comparison of the TLS 1.2 and TLS 1.3 handshakes", width="800", height="448" %}
</figure>

### HTTP/2 and HTTP/3

HTTP/2 and HTTP/3 both provide performance benefits over HTTP/1. Of the two, HTTP/3 offers greater _potential_ performance benefits. HTTP/3 isn't fully standardized yet, but it will be widely [supported](https://caniuse.com/#feat=http3) once this occurs.


#### HTTP/2

If your CDN hasn't already enabled [HTTP/2](https://almanac.httparchive.org/en/2019/http2) by default, you should consider turning it on. HTTP/2 provides multiple [performance benefits](https://hpbn.co/http2) over HTTP/1 and is [supported](https://caniuse.com/#feat=http2) by all major browsers. Performance features of HTTP/2 include: [multiplexing](https://hpbn.co/http2/#request-and-response-multiplexing), [stream prioritization](https://hpbn.co/http2/#stream-prioritization), [server push](https://almanac.httparchive.org/en/2019/http2#http2-push), and [header compression](https://tools.ietf.org/html/rfc7541/).

*  **Multiplexing**

    Multiplexing is arguably the most important feature of HTTP/2. Multiplexing enables a single TCP connection to serve multiple request-response pairs at the same time. This eliminates the overhead of unnecessary connection setups; given that the number of connections that a browser can have open at a given time is limited, this also has the implication that the browser is now able to request more of a page's resources in parallel. Multiplexing theoretically removes the need for HTTP/1 optimizations like concatenation and sprite sheets - however, in practice, these techniques will remain relevant given that larger files compress better.


*  **Stream prioritization**

    Multiplexing enables multiple concurrent streams; [stream prioritization](https://httpwg.org/specs/rfc7540.html#StreamPriority) provides an interface for communicating relative priority of each of these streams. This helps the server to send the most important resources first - even if they weren't requested first.

Stream prioritization is expressed by the browser via a dependency tree and is merely a statement of _preference_: in other words, the server is not obligated to meet (or even consider) the priorities supplied by the browser. Stream prioritization becomes more effective when more of a site is served through a CDN.

CDN implementations of HTTP/2 resource prioritization vary wildly. To identify whether your CDN fully and properly supports HTTP/2 resource prioritization, check out [Is HTTP/2 Fast Yet?](https://ishttp2fastyet.com/).


Although switching your CDN instance to HTTP/2 is largely a matter of flipping a switch, it's important to thoroughly test this change before enabling it in production. HTTP/1 and HTTP/2 use the same conventions for request and response headers - but HTTP/2 is far less forgiving when these conventions aren't adhered to. As a result, non-spec practices like including non-ASCII or uppercase characters in headers may begin causing errors once HTTP/2 is enabled. If this occurs, a browser's attempts to download the resource will fail. The failed download attempt will be visible in the "Network" tab of DevTools. In addition, the error message "ERR_HTTP2_PROTOCOL_ERROR" will be displayed in the console.


#### HTTP/3

[HTTP/3](https://en.wikipedia.org/wiki/HTTP/3) is the successor to [HTTP/2](https://en.wikipedia.org/wiki/HTTP/2). As of September 2020, all major browsers have experimental [support](https://caniuse.com/#feat=http3) for HTTP/3 and some CDNs support it. Performance is the primary benefit of HTTP/3 over HTTP/2. Specifically, HTTP/3 eliminates head-of-line blocking at the connection level and reduces connection setup time.


*  **Elimination of head-of-line blocking**

    HTTP/2 introduced multiplexing, a feature that allows a single connection to be used to transmit multiple streams of data simultaneously. However, with HTTP/2, a single dropped packet blocks all streams on a connection (a phenomena known as a head-of-line blocking). With HTTP/3, a dropped packet only blocks a single stream. This improvement is largely the result of HTTP/3 using [UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol) (HTTP/3 uses UDP via [QUIC](https://en.wikipedia.org/wiki/QUIC)) rather than [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol). This makes HTTP/3 particularly useful for data transfer over congested or lossy networks.

<figure>
  {% Img src="image/admin/B7YKfqGG4eS2toSoTDdS.png", alt="Diagram showing the differences in data transmission between HTTP/1, HTTP/2, and HTTP/3", width="800", height="449" %}
</figure>

*  **Reduced connection setup time**

    HTTP/3 uses TLS 1.3 and therefore shares its performance benefits: establishing a new connection only requires a single round-trip and resuming an existing connection does not require any roundtrips.

<figure>
  {% Img src="image/admin/7ffDEjblsisTNsfkynt6.png", alt="Comparison of connection resumption between TLS 1.2, TLS 1.3, TLS 1.3 0-RTT, and HTTP/3", width="800", height="400" %}
</figure>

HTTP/3 will have the biggest impact on users on poor network connections: not only because HTTP/3 handles packet loss better than its predecessors, but also because the absolute time savings resulting from a 0-RTT or 1-RTT connection setup will be greater on networks with high latency.


### Image optimization

CDN image optimization services typically focus on image optimizations that can be applied automatically in order to reduce image transfer size. For example: stripping [EXIF](https://en.wikipedia.org/wiki/Exif) data, applying lossless compression, and converting images to newer file formats (for example, WebP). Images make up ~50% of the transfer bytes on the median web page, so optimizing images can significantly reduce page size.


### Minification

[Minification](/reduce-network-payloads-using-text-compression/#minification) removes unnecessary characters from JavaScript, CSS, and HTML. It's preferable to do minification at the origin server, rather than the CDN. Site owners have more context about the code to be minified and therefore can often use more aggressive minification techniques than those employed by CDNs. However, if minifying code at the origin is not an option, minification by the CDN is a good alternative.


## Conclusion

*   **Use a CDN:** CDNs deliver resources quickly, reduce load on the origin server, and are helpful for dealing with traffic spikes.
*   **Cache content as aggressively as possible:** Both static and dynamic content can and should be cached - albeit for varying durations. Periodically audit your site to make sure that you are optimally cacheing content.
*   **Enable CDN performance features:** Features like Brotli, TLS 1.3, HTTP/2, and HTTP/3 further improve performance.

# Best practices for cookie notices

This article discusses how cookie notices can affect performance, performance
measurement, and user experience.


## Performance

Cookie notices can have a significant impact on page performance due to
the fact that they are typically loaded early in the page load process, are
shown to all users, and can potentially influence the loading of ads and other
page content.

Here's how cookie notices can impact Web Vitals metrics:



*  **Largest Contentful Paint (LCP):**
    Most cookie consent notices are fairly small and therefore typically don't
    contain a page's LCP element. However, this can happen—particularly on mobile
    devices. On mobile devices, a cookie notice typically takes up a larger portion
    of the screen. This usually occurs when a cookie notice contains a large
    block of text (text blocks can be LCP elements too).

*  **First Input Delay (FID):**
    Generally speaking, your cookie consent solution in and of itself should have a
    minimal impact on FID—cookie consent requires little JavaScript execution.
    However, the technologies that these cookies enable—namely advertising and
    tracking scripts—may have a significant impact on page interactivity. Delaying
    these scripts until cookie acceptance can serve as a technique to decrease
    First Input Delay (FID).

*  **Cumulative Layout Shift (CLS):**
    Cookie consent notices are a very common source of layout shifts.

Generally speaking, you can expect a cookie notice from third-party providers to
have a greater impact on performance than a cookie notice that you build
yourself. This is not a problem unique to cookie notices—but rather the nature
of third-party scripts in general.


### Best practices

The best practices in this section focus on third-party cookie notices. Some,
but not all, of these best practices will also be applicable to first-party
cookie notices.


#### Load cookie notices scripts asynchronously

Cookie notice scripts should be loaded asynchronously. To do this, add the
[`async`](https://developer.mozilla.org/docs/Web/HTML/Element/script#attr-async)
attribute to the script tag.

```html
<script src="https://cookie-notice.com/script.js" async>
```

Scripts that are not asynchronous block the browser parser. This delays page
load and LCP. For more information, see [Efficiently load third-party
JavaScript](/efficiently-load-third-party-javascript/).

{% Aside %}
If you must use synchronous scripts (for example, some cookie notices rely on
synchronous scripts to implement cookie blocking) you should make sure that this
request loads as quickly as possible. One way to do this is to [use resource
hints](/preconnect-and-dns-prefetch/).
{% endAside %}


#### Load cookie notice scripts directly

Cookie notice scripts should be loaded "directly" by placing the script tag in
the HTML of the main document—rather than loaded by a tag manager or other
script. Using a tag manager or secondary script to inject the cookie notice
script delays the loading of the cookie notice script: it obscures the script
from the browser's lookahead parser and it prevents the script from loading
before JavaScript execution.


#### Establish an early connection with the cookie notice origin

All sites that load their cookie notice scripts from a third-party location
should use either the `dns-prefetch` or `preconnect` resource hints to help
establish an early connection with the origin that hosts cookie notice
resources. For more information, see [Establish network connections early to
improve perceived page speed](/preconnect-and-dns-prefetch/).

```html
<link rel="preconnect" href="https://cdn.cookie-notice.com/">
```

{% Aside %}
It is common for cookie notices to load resources from multiple origins—
for example, loading resources from both
`www.cookie-notice.com` and
`cdn.cookie-notice.com`. Separate origins require separate connections and
therefore separate resource hints.
{% endAside %}


#### Preload cookie notices as appropriate

Some sites would benefit from using the
[`preload`](https://developer.mozilla.org/docs/Web/HTML/Preloading_content)
resource hint to load their cookie notice script. The `preload` resource hint
informs the browser to initiate an early request for the specified resource.

```html
<link rel="preload" href="https://www.cookie-notice.com/cookie-script.js">
```

`preload` is most powerful when its usage is limited to fetching a couple key
resources per page. Thus, the usefulness of preloading the cookie notice script
will vary depending on the situation.


#### Be aware of performance tradeoffs when styling cookie notices

Customizing the look and feel of a third-party cookie notice may incur
additional performance costs. For example, third-party cookie notices aren't
always able to reuse the same resources (for example, web fonts) that are used
elsewhere on the page. In addition, third-party cookie notices tend to load
styling at the end of long request chains. To avoid any surprises, be aware of
how your cookie notice loads and applies styling and related resources.


#### Avoid layout shifts

These are some of the most common layout shift issues associated with cookie
notices:


*   **Top-of-screen cookie notices:** Top-of-screen cookie notices are a very
    common source of layout shift. If a cookie notice is inserted into the DOM
    after the surrounding page has already rendered, it will push the page
    elements below it further down the page. This type of layout shift can be
    eliminated by reserving space in the DOM for the consent notice. If this is
    not a feasible solution—for example, if the dimensions of your cookie
    notice vary by geography, consider using a sticky footer or modal to
    display the cookie notice. Because both of these alternative approaches
    display the cookie notice as an "overlay" on top of the rest of the page,
    the cookie notice should not cause content shifts when it loads.
*   **Animations**: Many cookie notices use animations—for example, "sliding in"
    a cookie notice is a common design pattern. Depending on how these effects
    are implemented, they can cause layout shifts. For more information, see
    [Debugging layout shifts](/debugging-layout-shifts/).
*   **Fonts**: Late-loading fonts can block render and or cause layout shifts.
    This phenomena is more apparent on slow connections.

#### Advanced loading optimizations

These techniques take more work to implement but can further optimize the
loading of cookie notice scripts:

*   Caching and serving third-party cookie notice scripts from your own servers
    can improve the delivery speed of these resources.
*   Using [service
    workers](https://developer.mozilla.org/docs/Web/API/Service_Worker_API/Using_Service_Workers)
    can allow you more control over the [fetching and caching of
    third-party
    scripts](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#cross-origin-considerations)
    such as cookie notice scripts.


## Performance measurement

Cookie notices can impact performance measurements. This section discusses some
of these implications and techniques for mitigating them.


### Real User Monitoring (RUM)

Some analytics and RUM tools use cookies to collect performance data. In the
event that a user declines usage of cookies these tools cannot capture
performance data.

Sites should be aware of this phenomenon; it is also worthwhile to understand
the mechanisms that your RUM tooling uses to collect its data. However, for the
typical site this discrepancy probably isn't a cause for alarm given the
direction and magnitude of the data skew. Cookie usage is not a technical
requirement for performance measurement. The
[web-vitals](https://github.com/GoogleChrome/web-vitals) JavaScript library is
an example of a library that does not use cookies.

Depending on how your site uses cookies to collect
performance data (that is, whether the cookies contain personal information), as
well as the legislation in question, the use of cookies for performance
measurement might not be subject to the same legislative requirements as some of
the cookies used on your site for other purposes—for example, advertising
cookies. Some sites choose to break out performance cookies as a separate
category of cookies when asking for user consent.


### Synthetic monitoring


Without custom configuration, most synthetic tools (such as Lighthouse and WebPageTest) will only measure the
experience of a first-time user who has not responded to a cookie consent
notice. However, not only do variations in cache state (for example, an initial
visit versus a repeat visit) need to be considered when collecting performance
data, but also variations in cookie acceptance state—accepted, rejected, or
unresponded.


The following sections discuss WebPageTest and Lighthouse settings that can be
helpful for incorporating cookie notices into performance measurement workflows.
However, cookies and cookie notices are just one of many factors that can be
difficult to perfectly simulate in lab environments. For this reason, it is
important to make [RUM
data](/user-centric-performance-metrics/#how-metrics-are-measured)
the cornerstone of your performance benchmarking, rather than synthetic tooling.


### Testing cookie notices with WebPageTest

#### Scripting

You can use scripting to have a [WebPageTest](https://webpagetest.org/) "click"
the cookie consent banner while collecting a trace.

Add a script by going to the **Script** tab. The script below navigates to the URL
to be tested and then clicks the DOM element with the id `cookieButton`.

{% Aside 'caution' %}
WebPageTest scripts are
[tab-delimited](https://github.com/WPO-Foundation/webpagetest-docs/blob/main/src/scripting.md#scripting).
{% endAside %}

```shell
combineSteps
navigate    %URL%
clickAndWait    id=cookieButton
```

When using this script be aware that:

*   `combineSteps` tells WebPageTest to "combine" the results of the scripting
    steps that follow into a single set of traces and measurements. Running this
    script without `combineSteps` can also be useful—separate traces make it
    easy to see whether resources were loaded before or after cookie acceptance.
*   `%URL%` is a WebPageTest convention that refers to the URL that is being
    tested.
*   `clickAndWait` tells WebPageTest to click on the element indicated by
    `attribute=value` and wait for the subsequent browser activity to complete.
    It follows the format `clickAndWait attribute=Value`.


If you've configured this script correctly, the screenshot taken by WebPageTest
should not show a cookie notice (the cookie notice has been accepted).

For more information on WebPageTest scripting, check out [WebPageTest
documentation](https://docs.webpagetest.org/scripting/).

#### Set cookies

To run WebPageTest with a cookie set, go to the **Advanced** tab and add the
cookie header to the **Custom headers** field:



{% Img src="image/j2RDdG43oidUy6AL6LovThjeX9c2/qSccrAxF0H4yoSzYRYdh.png", alt="Screeshot showing the 'Custom headers' field in WebPageTest", width="800", height="181" %}


#### Change the test location

To change the test location used by WebPageTest, click the **Test Location**
dropdown located on the **Advanced Testing** tab.



{% Img src="image/j2RDdG43oidUy6AL6LovThjeX9c2/J27NcDQ5LTtXYloaA1DN.png", alt="Screenshot of the 'Test Location' dropdown in WebPageTest", width="800", height="267" %}



### Testing cookie notices with Lighthouse

Setting cookies on a Lighthouse run can serve as a mechanism for getting a
page into a particular state for testing by Lighthouse. Lighthouse's cookie
behavior varies slightly by context (DevTools, CLI, or PageSpeed Insights).


#### DevTools

Cookies are not cleared when Lighthouse is run from DevTools. However, other
types of storage are cleared by default. This behavior can be changed by using
the **Clear Storage** option in the **Lighthouse** settings panel.



{% Img src="image/j2RDdG43oidUy6AL6LovThjeX9c2/nmNDeSoGEQUVKeTP7q7R.png", alt="Screenshot higlighting the Lighthouse 'Clear Storage' option", width="800", height="304" %}



#### CLI

Running Lighthouse from the CLI uses a fresh Chrome instance, so no cookies
are set by default. To run Lighthouse from the CLI with a particular cookie set,
use the following
[command](https://github.com/GoogleChrome/lighthouse#cli-options):

```shell
lighthouse <url> --extra-headers "{\"Cookie\":\"cookie1=abc; cookie2=def; \_id=foo\"}"
```

For more information on setting custom request headers in Lighthouse CLI, see
[Running Lighthouse on Authenticated
Pages](https://github.com/GoogleChrome/lighthouse/blob/master/docs/authenticated-pages.md).


#### PageSpeed Insights

Running Lighthouse from PageSpeed Insights uses a fresh Chrome instance and does
not set any cookies. PageSeed Insights cannot be configured to set particular
cookies.


## User experience

The user experience (UX) of different cookie consent notices is primarily the
result of two decisions: the location of the cookie notice within the page and
the extent to which the user can customize a site's use of cookies. This section
discusses potential approaches to these two decisions.

{% Aside 'caution' %}
Cookie notice UX is often heavily influenced by legislation which can vary
widely by geography. Thus, some of the design patterns discussed in this section
may not be relevant to your particular situation. This article should not be
considered a substitute for legal advice.
{% endAside %}

When considering potential designs for your cookie notice, here are some things
to think about:

*   UX: Is this a good user experience? How will this particular design affect
    existing page elements and user flows?
*   Business: What is your site's cookie strategy? What are your goals for the
    cookie notice?
*   Legal: Does this comply with legal requirements?
*   Engineering: How much work would this be to implement and maintain? How
    difficult would it be to change?



### Placement

Cookie notices can be displayed as a header, inline element, or footer. They can
also be displayed on top of page content using a modal or served as an
[interstitial](https://en.wikipedia.org/wiki/Interstitial_webpage).

{% Img src="image/j2RDdG43oidUy6AL6LovThjeX9c2/LLqHAhp7W6x4E3rZh0Oc.png", alt="Diagram showing examples of different placement options for cookie notices", width="800", height="345" %}



#### Header, footer, and inline cookie notices

Cookie notices are commonly placed in the header or footer. Of these two
options, the footer placement is generally preferable because it is unobtrusive,
does not compete for attention with banner ads or notifications, and typically
does not cause CLS. In addition, it is a common place for placing privacy
policies and terms of use.

Although inline cookie notices are an option, they can be difficult to integrate
into existing user interfaces, and therefore are uncommon.

#### Modals

Modals are cookie consent notices that are displayed on top of page content.
Modals can look and perform quite differently depending on their size.

Smaller, partial-screen modals can be a good alternative for sites that are
struggling to implement cookie notices in a way that doesn't cause [layout
shifts](/cls/).

On the other hand, large modals that obscure the majority of page content should
be used carefully. In particular, smaller sites may find that users bounce
rather than accept the cookie notice of an unfamiliar site with obscured
content. Although they are not necessarily synonymous concepts, if you are
considering using a full-screen cookie consent modal, you should be aware of
legislation regarding [cookie
walls](https://techcrunch.com/2020/05/06/no-cookie-consent-walls-and-no-scrolling-isnt-consent-says-eu-data-protection-body/).


{% Aside %}
Large modals can be considered a type of interstitial. Google Search [does not
penalize](https://developers.google.com/search/blog/2016/08/helping-users-easily-access-content-on)
the usage of interstitials when they are used to comply with legal regulations
such as in the case of cookie banners. However, their usage of interstitials in
other contexts—particularly if they are intrusive or create a poor user
experience—may be penalized.
{% endAside %}


### Configurability

Cookie notice interfaces give users varying levels of control over which cookies
they accept.


#### No configurability

These notice-style cookie banners do not present users with direct UX controls
for opting out of cookies. Instead, they typically include a link to the site's
cookie policy which may provide users with information about managing cookies
using their web browser. These notices typically include either a "dismiss" and/or
"Accept" button.

{% Img src="image/j2RDdG43oidUy6AL6LovThjeX9c2/RlAg8DCjBC0bX7Ki5MuE.png", alt="Diagram showing examples of cookie notices with no cookie configurability", width="800", height="518" %}

#### Some configurability

These cookie notices give the user the option of declining cookies but do not
support more granular controls. This approach to cookie notices is less common.

{% Img src="image/j2RDdG43oidUy6AL6LovThjeX9c2/MOl8u9NcnyjCWogxzjdz.png", alt="Diagram showing examples of cookie notices with some cookie configurability", width="800", height="508" %}

#### Full configurability

These cookie notices provide users with more fine-grained controls for
configuring the cookie usage that they accept.

{% Img src="image/j2RDdG43oidUy6AL6LovThjeX9c2/QfFoqkkmdKHAYlftIH0n.png", alt="Diagram showing examples of chookie notices with full cookie configurability", width="800", height="467" %}

*  **UX:**
    Controls for configuring cookie usage are most commonly displayed using a
    separate modal that is launched when the user responds to the initial cookie
    consent notice. However, if space permits, some sites will display these
    controls inline within the initial cookie consent notice.

*  **Granularity:**
    The most common approach to cookie configurability is to allow users to
    opt-in to cookies by cookie "category". Examples of common cookie categories
    include functional, targeting, and social media cookies.

    However, some sites will go a step further and allow users to opt-in on a
    per-cookie basis. Alternatively, another way of providing users with more
    specific controls is to break down cookie categories like "advertising" into
    specific use cases—for example, allowing users to separately opt-in to
    "basic ads" and "personalized ads".

{% Img src="image/j2RDdG43oidUy6AL6LovThjeX9c2/z7zFPtCkFi8GEpkfubek.png", alt="Diagram showing examples of cookie notices with full cookie configurability", width="800", height="372" %}

# Debug layout shifts

The first part of this article discusses tooling for debugging layout shifts,
while the second part discusses the thought process to use when
identifying the cause of a layout shift.


## Tooling


### Layout Instability API

The [Layout Instability API](https://wicg.github.io/layout-instability/) is the
browser mechanism for measuring and reporting layout shifts. All tools for
debugging layout shifts, including DevTools, are ultimately built upon the
Layout Instability API. However, using the Layout Instability API directly is a
powerful debugging tool due to its flexibility.

{% Aside %}
The Layout Instability API is only
[supported](https://caniuse.com/mdn-api_layoutshift) by Chromium browsers. At
the current time there is no way to measure or debug layout shifts in
non-Chromium browsers.
{% endAside %}


#### Usage

The same code [snippet](/cls/#measure-cls-in-javascript) that
measures [Cumulative Layout Shift (CLS)](/cls/) can also
serve to debug layout shifts. The snippet below logs information about layout
shifts to the console. Inspecting this log will provide you information
about when, where, and how a layout shift occurred.

```javascript
let cls = 0;
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      cls += entry.value;
      console.log('Current CLS value:', cls, entry);
    }
  }
}).observe({type: 'layout-shift', buffered: true});
```

When running this script be aware that:



*   The `buffered: true` option indicates that the
    [`PerformanceObserver`](https://developer.mozilla.org/docs/Web/API/PerformanceObserver)
    should check the browser's [performance entry
    buffer](https://www.w3.org/TR/performance-timeline-2/#dfn-performance-entry-buffer)
    for performance entries that were created before the observer's
    initialization. As a result, the `PerformanceObserver` will report layout
    shifts that happened both before and after it was initialized. Keep this in
    mind when inspecting the console logs. An initial glut of layout shifts can
    reflect a reporting backlog, rather than the sudden occurrence of numerous
    layout shifts.
*   To avoid impacting performance, the `PerformanceObserver` waits until the main
    thread is idle to report on layout shifts. As a result, depending on how
    busy the main thread is, there may be a slight delay between when a layout
    shift occurs and when it is logged in the console.
*   This script ignores layout shifts that occurred within 500&nbsp;ms of user input
    and therefore do not count towards CLS.



Information about layout shifts is reported using a combination of two APIs: the
[`LayoutShift`](https://wicg.github.io/layout-instability/#layoutshift) and
[`LayoutShiftAttribution`](https://wicg.github.io/layout-instability/#sec-layout-shift-attribution)
interfaces. Each of these interfaces are explained in more detail in the
following sections.


#### LayoutShift

Each layout shift is reported using the `LayoutShift` interface. The contents of
an entry look like this:

```javascript
duration: 0
entryType: "layout-shift"
hadRecentInput: false
lastInputTime: 0
name: ""
sources: (3) [LayoutShiftAttribution, LayoutShiftAttribution, LayoutShiftAttribution]
startTime: 11317.934999999125
value: 0.17508567530168798
```

The entry above indicates a layout shift during which three DOM elements changed
position. The layout shift score of this particular layout shift was `0.175`.

These are the properties of a `LayoutShift` instance that are most relevant to
debugging layout shifts:



| Property | Description |
|----------|-------------|
|`sources`| The `sources` property lists the DOM elements that moved during the layout shift. This array can contain up to five sources. In the event that there are more than five elements impacted by the layout shift, the five largest (as measured by impact on layout stability) sources of layout shift are reported. This information is reported using the LayoutShiftAttribution interface (explained in more detail below).|
|`value`| The `value` property reports the [layout shift score](/cls/#layout-shift-score) for a particular layout shift.|
|`hadRecentInput`| The `hadRecentInput` property indicates whether a layout shift occurred within 500 milliseconds of user input.|
|`startTime`| The `startTime` property indicates when a layout shift occurred. `startTime` is indicated in milliseconds and is measured relative to the [time that the page load was initiated](https://www.w3.org/TR/hr-time-2/#sec-time-origin).|
|`duration`| The `duration` property will always be set to `0`. This property is inherited from the [`PerformanceEntry`](https://developer.mozilla.org/docs/Web/API/PerformanceEntry) interface (the `LayoutShift` interface extends the `PerformanceEntry` interface). However, the concept of duration does not apply to layout shift events, so it is set to `0`. For information on the `PerformanceEntry` interface, refer to the [spec](https://w3c.github.io/performance-timeline/#the-performanceentry-interface).|

{% Aside %}
The [Web Vitals
Extension](https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma)
can log layout shift info to the console. To enable this feature, go to
**Options > Console Logging**.
{% endAside %}

#### LayoutShiftAttribution

The `LayoutShiftAttribution` interface describes a single shift of a single DOM
element. If multiple elements shift during a layout shift, the `sources`
property contains multiple entries.

For example, the JSON below corresponds to a layout shift with one source: the
downward shift of the `<div id='banner'>` DOM element from `y: 76` to
`y:246`.

```json
// ...
  "sources": [
    {
      "node": "div#banner",
      "previousRect": {
        "x": 311,
        "y": 76,
        "width": 4,
        "height": 18,
        "top": 76,
        "right": 315,
        "bottom": 94,
        "left": 311
      },
      "currentRect": {
        "x": 311,
        "y": 246,
        "width": 4,
        "height": 18,
        "top": 246,
        "right": 315,
        "bottom": 264,
        "left": 311
      }
    }
  ]
```

The `node` property identifies the HTML element that shifted. Hovering on this
property in DevTools highlights the corresponding page element.

The `previousRect` and `currentRect` properties report the size and position of
the node.



*   The `x` and `y` coordinates report the x-coordinate and y-coordinate
    respectively of the top-left corner of the element
*   The `width` and `height` properties report the width and height respectively
    of the element.
*   The `top`, `right`, `bottom`, and `left` properties report the x or y
    coordinate values corresponding to the given edge of the element. In other
    words, the value of `top` is equal to `y`; the value of `bottom` is equal to
    `y+height`.

If all properties of `previousRect` are set to 0 this means that the element has
shifted into view. If all properties of `currentRect` are set to 0 this means
that the element has shifted out of view.

{% Aside 'caution' %}
Layout Instability API currently does not ignore elements that shifted but were not
visible due to another element being positioned in front of them.
Use `display: none`, `visibility: hidden`, or `opacity: 0` to avoid layout shifts in
cases where you need to run layout on some elements before you make them visible to
the user.
{% endAside %}

One of the most important things to understand when interpreting these outputs
is that elements listed as _sources_ are the elements that shifted during the
layout shift. However, it's possible that these elements are only indirectly
related to the "root cause" of layout instability. Here are a few examples.

**Example #1**

This layout shift would be reported with one source: element B. However, the
root cause of this layout shift is the change in size of element A.


{% Img
  src="image/j2RDdG43oidUy6AL6LovThjeX9c2/oaM41OYL7mFtGcpIN8KF.png",
  alt="Example showing a layout shift caused by a change in element dimensions",
  width="800",
  height="452"
 %}


**Example #2**

The layout shift in this example would be reported with two sources: element A
and element B. The root cause of this layout shift is the change in position of
element A.

{% Img
  src="image/j2RDdG43oidUy6AL6LovThjeX9c2/AhaslIEWb5fFMMgiZcI2.png",
  alt="Example showing a layout shift caused by a change in element position",
  width="800",
  height="451"
 %}


**Example #3**

The layout shift in this example would be reported with one source: element B.
Changing the position of element B resulted in this layout shift.


{% Img
  src="image/j2RDdG43oidUy6AL6LovThjeX9c2/6zKjd4Ua6YJ94LMlqiMR.png",
  alt="Example showing a layout shift caused by a change in element position",
  width="800",
  height="451"
 %}

**Example #4**

Although element B changes size, there is no layout shift in this example.

{% Img
  src="image/j2RDdG43oidUy6AL6LovThjeX9c2/ZujHWxsXI3C7tupe42oD.png",
  alt="Example showing a element changing size but not causing a layout shift",
  width="800",
  height="446"
 %}


Check out a [demo of how DOM changes are reported by the Layout Instability API](https://desert-righteous-router.glitch.me/).


### DevTools


#### Performance panel

The **Experience** pane of the DevTools **Performance** panel displays all
layout shifts that occur during a given performance trace—even if they occur
within 500&nbsp;ms of a user interaction and therefore don't count towards CLS.
Hovering over a particular layout shift in the **Experience** panel highlights
the affected DOM element.

{% Img
  src="image/j2RDdG43oidUy6AL6LovThjeX9c2/Uug2fnJT8mOc2YQmxo2l.png",
  alt="Screenshot of a layout shift displayed in the DevTools Network panel",
  width="724",
  height="629"
 %}

To view more information about the layout shift, click on the layout shift, then
open the **Summary** drawer. Changes to the element's dimensions are listed
using the format `[width, height]`; changes to the element's position are listed
using the format `[x,y]`. The **Had recent input** property indicates whether a
layout shift occurred within 500&nbsp;ms of a user interaction.

{% Img
  src="image/j2RDdG43oidUy6AL6LovThjeX9c2/AfVjsH9Nl9w0lJwQZEjR.png",
  alt="Screenshot of the DevTools 'Summary' tab for a layout shift",
  width="612",
  height="354"
 %}

For information on the duration of a layout shift, open the **Event Log** tab.
The duration of a layout shift can also be approximated by looking in the
**Experience** pane for the length of the red layout shift rectangle.

{% Img
  src="image/j2RDdG43oidUy6AL6LovThjeX9c2/124Dm7vV3EGM7M9fiugs.png",
  alt="Screenshot of the DevTools 'Event Log' tab for a layout shift",
  width="612",
  height="354"
 %}

{% Aside %}
The duration of a layout shift has no impact on its layout shift score.
{% endAside %}

For more information on using the **Performance** panel, refer to [Performance
Analysis
Reference](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/).


#### Highlight layout shift regions

Highlighting layout shift regions can be a helpful technique for getting a
quick, at-a-glance feel for the location and timing of the layout shifts
occurring on a page.

To enable Layout Shift Regions in DevTools, go to **Settings > More Tools >
Rendering > Layout Shift Regions** then refresh the page that you wish to debug.
Areas of layout shift will be briefly highlighted in purple.


## Thought process for identifying the cause of layout shifts

You can use the steps below to identify the cause of layout shifts
regardless of when or how the layout shift occurs. These steps can be
supplemented with running Lighthouse—however, keep in mind that Lighthouse can
only identify layout shifts that occurred during the initial page load. In
addition, Lighthouse also can only provide suggestions for some causes of layout
shifts—for example, image elements that do not have explicit width and height.


### Identifying the cause of a layout shift

Layout shifts can be caused by the following events:



*   Changes to the position of a DOM element
*   Changes to the dimensions of a DOM element
*   Insertion or removal of a DOM element
*   Animations that trigger layout

In particular, the DOM element immediately preceding the shifted element is the
element most likely to be involved in "causing" layout shift. Thus, when
investigating why a layout shift occurred consider:



*   Did the position or dimensions of the preceding element change?
*   Was a DOM element inserted or removed before the shifted element?
*   Was the position of the shifted element explicitly changed?

If the preceding element did not cause the layout shift, continue your search by
considering other preceding and nearby elements.

In addition, the direction and distance of a layout shift can provide hints
about root cause. For example, a large downward shift often indicates the
insertion of a DOM element, whereas a 1&nbsp;px or 2&nbsp;px layout shift often indicates
the application of conflicting CSS styles or the loading and application of a
web font.

<figure>
  {% Img
    src="image/j2RDdG43oidUy6AL6LovThjeX9c2/g0892nhvz3SnSaasaO1b.png",
    alt="Diagram showing a layout shift caused by a font swap",
    width="800",
    height="452"
  %}
  <figcaption>
    In this example, font swapping caused page elements to shift upwards by five pixels.
  </figcaption>
</figure>



These are some of the specific behaviors that most frequently cause layout shift
events:

#### Changes to the position of an element (that aren't due to the movement of another element)

This type of change is often a result of:
*   Stylesheets that are loaded late or overwrite previously declared styles.
*   Animation and transition effects.

#### Changes to the dimensions of an element

This type of change is often a result of:
*   Stylesheets that are loaded late or overwrite previously declared styles.
*   Images and iframes without `width` and `height` attributes that load after
    their "slot" has been rendered.
*   Text blocks without `width` or `height` attributes that swap fonts after the
    text has been rendered.

#### The insertion or removal of DOM elements

This is often the result of:
*   Insertion of ads and other third-party embeds.
*   Insertion of banners, alerts, and modals.
*   Infinite scroll and other UX patterns that load additional content above
    existing content.

#### Animations that trigger layout

Some animation effects can [trigger
layout](https://gist.github.com/paulirish/5d52fb081b3570c81e3a). A common
example of this is when DOM elements are 'animated' by incrementing properties
like `top` or `left` rather than using CSS's
[`transform`](https://developer.mozilla.org/docs/Web/CSS/transform)
property. Read [How to create high-performance CSS animations](/animations-guide/)
for more information.


### Reproducing layout shifts

You can't fix layout shifts that you can't reproduce. One of the simplest, yet
most effective things you can do to get a better sense of your site's layout
stability is take 5-10 minutes to interact with your site with the goal
triggering layout shifts. Keep the console open while doing this and use the
Layout Instability API to report on layout shifts.

For hard to locate layout shifts, consider repeating this exercise with
different devices and connection speeds. In particular, using a slower
connection speed can make it easier to identify layout shifts. In addition,
you can use a `debugger` statement to make it easier to step through layout
shifts.

```javascript/4
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      cls += entry.value;
      debugger;
      console.log('Current CLS value:', cls, entry);
    }
  }
}).observe({type: 'layout-shift', buffered: true});
```

Lastly, for layout issues that aren't reproducible in development, consider
using the Layout Instability API in conjunction with your front-end logging tool
of choice to collect more information on these issues. Check out
the example [code for how to track the largest shifted element on a page](https://github.com/GoogleChromeLabs/web-vitals-report/blob/71b0879334798c732f460945ded5267cab5a36bf/src/js/analytics.js#L104-L118).

# Defer non-critical CSS

CSS files are [render-blocking resources](https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources/):
they must be loaded and processed before the browser renders the page. Web pages that contain unnecessarily large styles
take longer to render.

In this guide, you'll learn how to defer non-critical CSS with the goal of optimizing the [Critical Rendering Path](/critical-rendering-path/), and improving [First Contentful Paint (FCP)](/fcp/).

## Loading CSS in a suboptimal way

The following example contains an accordion with three hidden paragraphs of text, each of which is styled with a different class:

{% Glitch {
  id: 'defer-css-unoptimized',
  path: 'index.html'
} %}

This page requests a CSS file with eight classes, but not all of them are
necessary to render the "visible" content.

The goal of this guide is to optimize this page, so only the **critical** styles
are loaded synchronously, while the rest (like the ones applied to paragraphs),
are loaded in a non-blocking way.

## Measure

Run [Lighthouse](/discover-performance-opportunities-with-lighthouse/#run-lighthouse-from-chrome-devtools) on [the page](https://defer-css-unoptimized.glitch.me/) and go to the **Performance** section.

The report shows the **First Contentful Paint** metric with a value of "1s", and
the opportunity **Eliminate render-blocking resources**, pointing to the
**style.css** file:

<figure>
  {% Img src="image/admin/eZtuQ2IwL3Mtnmz09bmp.png", alt="Lighthouse report for unoptimized page, showing FCP of '1s' and 'Eliminate blocking resources' under 'Opportunities'", width="800", height="640" %}
</figure>

{% Aside %}
The CSS we are using for this demo site is quite small. If you were requesting
larger CSS files (which are not uncommon in production scenarios), and if
Lighthouse detects that a page has at least 2048&nbsp;bytes of CSS rules that
weren't used while rendering the **above the fold** content, you'll
also receive a suggestion called **Remove Unused CSS**.
{% endAside %}

To visualize how this CSS blocks rendering:

1. Open [the page](https://defer-css-unoptimized.glitch.me/) in Chrome.
{% Instruction 'devtools-performance', 'ol' %}
1. In the Performance panel, click **Reload**.

In the resulting trace, you'll see that the **FCP** marker is placed immediately
after the CSS finishes loading:

<figure>
  {% Img src="image/admin/WhpaDYb98Rf03JmuPenp.png", alt="DevTools performance trace for unoptimized page, showing FCP starting after CSS loads.", width="800", height="352" %}
</figure>

This means that the browser needs to wait for all CSS to load and get processed
before painting a single pixel on the screen.

## Optimize

To optimize this page, you need to know which classes are considered "critical".
You'll use the [Coverage Tool](https://developer.chrome.com/docs/devtools/css/reference/#coverage) for that:

1. In DevTools, open the [Command Menu](https://developer.chrome.com/docs/devtools/command-menu/), by pressing `Control+Shift+P` or `Command+Shift+P` (Mac).
1. Type "Coverage" and select **Show Coverage**.
1. Click the **Reload** button, to reload the page and start capturing the
   coverage.

<figure>
  {% Img src="image/admin/JTFK7wjhlTzd2cCfkpps.png", alt="Coverage for CSS file, showing 55.9% unused bytes.", width="800", height="82" %}
</figure>


Double-click on the report, to see classes marked in two colors:

* Green (**critical**): These are the classes the browser needs to render the
  visible content (like the title, subtitle, and accordion buttons).
* Red (**non-critical**): These styles apply to content that's not immediately
  visible (like the paragraphs inside the accordions).

With this information, optimize your CSS so that the browser starts processing
critical styles immediately after page loads, while deferring non-critical CSS
for later:

* Extract the class definitions marked with green in the report obtained from
  the coverage tool, and put those classes inside a `<style>` block at the head
  of the page:

```html
<style type="text/css">
.accordion-btn {background-color: #ADD8E6;color: #444;cursor: pointer;padding: 18px;width: 100%;border: none;text-align: left;outline: none;font-size: 15px;transition: 0.4s;}.container {padding: 0 18px;display: none;background-color: white;overflow: hidden;}h1 {word-spacing: 5px;color: blue;font-weight: bold;text-align: center;}
</style>
```

* Then, load the rest of the classes asynchronously, by applying the following pattern:

```html
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>
```

This is not the standard way of loading CSS. Here's how it works:

* `link rel="preload" as="style"` requests the stylesheet asynchronously. You can learn more about `preload` in the [Preload critical assets guide](/preload-critical-assets).
* The `onload` attribute in the `link` allows the CSS to be processed when it finishes loading.
* "nulling" the `onload` handler once it is used helps some browsers avoid re-calling the handler upon switching the rel attribute.
* The reference to the stylesheet inside of a `noscript` element works as a fallback for browsers that don't execute JavaScript.

{% Aside %}
<p>
In this guide, you used vanilla code to implement this optimization. In a real
production scenario, it's a good practice to use functions like
[loadCSS](https://github.com/filamentgroup/loadCSS/blob/master/README.md), that
can encapsulate this behavior and work well across browsers, and can support a [Content Security Policy](/csp/) that may not allow inline `onload` JavaScript.
</p>
<p>
Alternatively, placing the CSS link at the bottom of the page, allows content above this to be rendered in Firefox and Chrome—though not in Safari—without waiting for the stylesheet. However, it still prioritizes that stylesheet, so it can still block other, more critical, content in all browsers.
</p>
{% endAside %}

The [resulting page](https://defer-css-optimized.glitch.me/) looks exactly like the previous version, even when most styles load asynchronously. Here's how the inlined styles and asynchronous request to the CSS file look like in the HTML file:

<!-- Copy and Paste Me -->
{% Glitch {
  id: 'defer-css-optimized',
  path: 'index.html',
  previewSize: 0
} %}

## Monitor

Use DevTools to run another **Performance** trace on the [optimized
page](https://defer-css-optimized.glitch.me/).

The **FCP** marker appears before the page requests the CSS, which means the
browser doesn't need to wait for the CSS to load before rendering the page:

<figure>
  {% Img src="image/admin/0mVq3q760y37JSn2MmCP.png", alt="DevTools performance trace for unoptimized page, showing FCP starting before CSS loads.", width="800", height="389" %}
</figure>

As a final step, run Lighthouse on the optimized page.

In the report you'll see that the FCP page has been reduced by **0.2s** (a 20%
improvement!):

<figure>
  {% Img src="image/admin/oTDQFSlfQwS9SbqE0D0K.png", alt="Lighthouse report, showing an FCP value of '0.8s'.", width="800", height="324" %}
</figure>

The **Eliminate render-blocking resources** suggestion is no longer under
**Opportunities**, and now belongs to the **Passed Audits** section:

<figure>
  {% Img src="image/admin/yDjEvZAcjPouC6I3I7qB.png", alt="Lighthouse report, showing 'Eliminate blocing resources' on the 'Passed Audits' section.", width="800", height="237" %}
</figure>

## Next steps & references

In this  guide, you learned how to defer non-critical CSS by manually extracting the unused code in the page.
As a complement to this, the [extract critical CSS guide](/extract-critical-css/)
covers some of the most popular tools to extract critical CSS and includes
[a codelab](/codelab-extract-and-inline-critical-css/) to see how
they work in practice.

# Efficiently load third-party JavaScript

If a third-party script is [slowing down](/third-party-javascript/) your page load, you have two options to improve performance:

* Remove it if it doesn't add clear value to your site.

* Optimize the loading process.

This post explains how to optimize the loading process of third-party scripts with the following techniques:

1. Using the `async` or `defer` attribute on `<script>` tags

2. Establishing early connections to required origins

3. Lazy loading

4. Optimizing how you serve third-party scripts

## Use `async` or `defer`

Because [synchronous scripts](/third-party-javascript/) delay DOM construction and rendering, you should always load third-party scripts asynchronously unless the script has to run before the page can be rendered.

The `async` and `defer` attributes tell the browser that it may go on parsing the HTML while loading the script in the background, and then execute the script after it loads. This way, script downloads don't block DOM construction and page rendering. The result is that the user can see the page before all scripts have finished loading.

```html
<script async src="script.js">

<script defer src="script.js">
```

The difference between `async` and `defer` is when they start executing the scripts.

### `async`

Scripts with the `async` attribute execute at the first opportunity after they finish downloading and before the window's [load](https://developer.mozilla.org/docs/Web/Events/load) event. This means it's possible (and likely) that `async` scripts will not be executed in the order in which they appear in the HTML. It also means they can interrupt DOM building if they finish downloading while the parser is still at work.

{% Img src="image/admin/tCqsJ3E7m4lpKOprXu5B.png", alt="Diagram of parser blocking script with async attribute", width="800", height="252" %}

### `defer`

Scripts with the `defer` attribute execute after HTML parsing is completely finished, but before the [`DOMContentLoaded`](https://developer.mozilla.org/docs/Web/Events/DOMContentLoaded) event. `defer` guarantees scripts will be executed in the order they appear in the HTML and will not block the parser.

{% Img src="image/admin/Eq0mcvDALKibHe15HspN.png", alt="Diagram of parser flow with a script with defer attribute", width="800", height="253" %}

* Use `async` if it's important to have the script run earlier in the loading process.

* Use `defer` for less critical resources. A video player that's below-the-fold, for example.

Using these attributes can significantly speed up page load. For example, [Telegraph recently deferred all of their scripts](https://medium.com/p/a0a1000be5#4123), including ads and analytics, and improved the ad loading time by an average of four seconds.


{% Aside %}
Analytics scripts are usually loaded early so you don't miss any valuable analytics data. Fortunately, there are [patterns to initialize analytics lazily](https://philipwalton.com/articles/the-google-analytics-setup-i-use-on-every-site-i-build/) while retaining early page-load data.
{% endAside %}

## Establish early connections to required origins

You can save 100–500 ms by [establishing early connections](/preconnect-and-dns-prefetch/) to important third-party origins.

Two [`<link>`](https://developer.mozilla.org/docs/Web/HTML/Element/link) types can help here:

* `preconnect`

* `dns-prefetch`

### `preconnect`

`<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. When the request for a resource from the pre-connected origin is made, the download starts immediately.

```html
<link rel="preconnect" href="https://cdn.example.com">
```

{% Aside 'caution' %}
Only preconnect to critical domains you will use soon because the browser closes any connection that isn't used within 10 seconds. Unnecessary preconnecting can delay other important resources, so limit the number of preconnected domains and [test the impact preconnecting makes](https://andydavies.me/blog/2019/08/07/experimenting-with-link-rel-equals-preconnect-using-custom-script-injection-in-webpagetest/).
{% endAside %}

### `dns-prefetch`

`<link rel="dns-prefetch>` handles a small subset of what is handled by `<link rel="preconnect">`.  Establishing a connection involves the DNS lookup and TCP handshake, and for secure origins, TLS negotiations. `dns-prefetch` instructs the browser to only resolve the DNS of a specific domain before it has been explicitly called.

The `preconnect` hint is best used for only the most critical connections; for less critical third-party domains use `<link rel=dns-prefetch>`.

```html
<link rel="dns-prefetch" href="http://example.com">
```

[Browser support for `dns-prefetch`](https://caniuse.com/#search=dns-prefetch) is slightly different from [`preconnect` support](https://caniuse.com/#search=preconnect), so `dns-prefetch` can serve as a fallback for browsers that don't support `preconnect`. Use separate link tags to implement this safely:

```html
<link rel="preconnect" href="http://example.com">
<link rel="dns-prefetch" href="http://example.com">
```

## Lazy-load third-party resources

Embedded third-party resources can be a big contributor to slow page speed when constructed poorly. If they aren't critical or are below the fold (that is, if users have to scroll to view them), lazy loading is a good way to improve page speed and paint metrics. This way, users will get the main page content faster and have a better experience.

<figure data-float="left">
{% Img src="image/admin/uzPZzkgzfrv2Oy3UQPrN.png", alt="A diagram of a webpage shown on a mobile device with scrollable content extending beyond the screen. The content that's below-the-fold is desaturated because it's not loaded yet.", width="366", height="438" %}
</figure>

One effective approach is to lazy-load third-party content after the main page content loads. Ads are a good candidate for this approach.

Ads are an important source of income for many sites, but users come for the content. By lazy loading ads and delivering the main content faster, you can increase the overall viewability percentage of an ad. For example, MediaVine switched to [lazy-loading ads](https://www.mediavine.com/lazy-loading-ads-mediavine-ads-load-200-faster/) and saw a 200% improvement in page load speed. DoubleClick have guidance on how to lazy-load ads in their [official documentation](https://support.google.com/dfp_premium/answer/4578089#lazyloading).

An alternative approach is to load third-party content only when users scroll down to that section of the page.

[Intersection Observer](https://developer.chrome.com/blog/intersectionobserver/) is a browser API that efficiently detects when an element enters or exits the browser's viewport and it can be used to implement this technique. [lazysizes](/use-lazysizes-to-lazyload-images/) is a popular JavaScript library for lazy loading images and [`iframes`](http://afarkas.github.io/lazysizes/#examples). It supports YouTube embeds and [widgets](https://github.com/aFarkas/lazysizes/tree/gh-pages/plugins/unveilhooks). It also has [optional support](https://github.com/aFarkas/lazysizes/blob/097a9878817dd17be3366633e555f3929a7eaaf1/src/lazysizes-intersection.js) for IntersectionObserver.

{% Aside 'caution' %}
Be careful when lazy loading resources with JavaScript. If JavaScript fails to load, perhaps due to flaky network conditions, your resources won't load at all.
{% endAside %}

Using the [`loading` attribute for lazy loading images and iframes](/browser-level-image-lazy-loading/) is a great alternative to JavaScript techniques, and it has recently become available in Chrome 76!

## Optimize how you serve third-party scripts


### Third-party CDN hosting

It's common for third-party vendors to provide URLs of JavaScript files that they host, usually on a [content delivery network (CDN)](https://en.wikipedia.org/wiki/Content_delivery_network). The benefits of this approach are that you can get started quickly—just copy and paste the URL—and there's no maintenance overhead. The third-party vendor handles server configuration and script updates.

But because they are not on the same origin as the rest of your resources, loading files from a public CDN comes with a network cost. The browser needs to perform a DNS lookup, establish a new HTTP connection, and, on secure origins, perform an SSL handshake with the vendor's server.

When you use files from third-party servers, you rarely have control over caching. Relying on someone else's caching strategy might cause scripts to be unnecessarily re-fetched from the network too often.

### Self-host third-party scripts

Self-hosting third-party scripts is an option that gives you more control over a script's loading process. By self-hosting you can:

* Reduce DNS lookup and round-trip times.
* Improve [HTTP caching](/http-cache/) headers.
* Take advantage of [HTTP/2](/performance-http2/), or the newer HTTP/3.

For example, Casper managed to [shave 1.7 seconds](https://medium.com/caspertechteam/we-shaved-1-7-seconds-off-casper-com-by-self-hosting-optimizely-2704bcbff8ec) off load time by self-hosting an A/B testing script.

Self-hosting comes with one big downside though: scripts can go out of date and won't get automatic updates when there's an API change or a security fix.

{% Aside 'caution' %}

Manually updating scripts can add a lot of overhead to your development process and you might miss out on important updates. If you are not using CDN hosting for serving all the resources, you are also missing out on [edge-caching](https://www.cloudflare.com/learning/cdn/glossary/edge-server/) and you have to optimize your server's compression.
{% endAside%}

### Use service workers to cache scripts from third-party servers

An alternative to self-hosting that allows you greater control over caching while still getting the third-party CDN benefits is [using service workers to cache scripts from third-party servers](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#cross-origin-considerations). This gives you control over how often scripts are re-fetched from the network and makes it possible to create a loading strategy that throttles requests for non-essential third-party resources until the page reaches a key user moment. Using `preconnect` to establish early connections in this case can also mitigate the network costs to an extent.

# Extract critical CSS

The browser must download and parse CSS files before it can show the page, which makes CSS a render-blocking resource. If CSS files are big, or network conditions are poor, requests for CSS files can significantly increase the time it takes for a web page to render.

{% Aside 'key-term' %}
Critical CSS is a technique that extracts the CSS for above-the-fold content in order to render content to the user as fast as possible.
{% endAside %}

<figure>
  {% Img src="image/admin/t3Kkvh265zi6naTBga41.png", alt="An illustration of a laptop and a mobile device with web pages overflowing the edges of screens", width="800", height="469", class="" %}
</figure>

{% Aside 'note' %}
Above-the-fold is all the content a viewer sees on page load, before scrolling. There is no universally defined pixel height of what is considered above the fold content since there is a myriad of devices and screen sizes.
{% endAside %}

Inlining extracted styles in the `<head>` of the HTML document eliminates the need to make an additional request to fetch these styles. The remainder of the CSS can be loaded asynchronously.

<figure>
    {% Img src="image/admin/RVU3OphqtjlkrlAtKLEn.png", alt="HTML file with critical CSS inlined in the head", width="800", height="325" %}
    <figcaption>
    Inlined critical CSS
    </figcaption>
</figure>

Improving render times can make a huge difference in [perceived performance](/user-centric-performance-metrics/), especially under poor network conditions. On mobile networks, high latency is an issue regardless of bandwidth.

<figure>
  {% Img src="image/admin/NdQz49RVgdHoh3Fff0yr.png", alt="Filmstrip view comparison of loading a page with render-blocking CSS (top) and the same page with inlined critical CSS (bottom) on a 3G connection. Top filmstrip shows six blank frames before finally displaying content. Bottom filmstrip displays meaningful content in the first frame.", width="800", height="363" %}
  <figcaption>
    Comparison of loading a page with render-blocking CSS (top) and the same page with inlined critical CSS (bottom) on a 3G connection
  </figcaption>
</figure>

If you have poor [First Contentful Paint (FCP)](/fcp/) and see "Eliminate render-blocking resource" opportunity in Lighthouse audits it's a good idea to give critical CSS a go.

{% Img src="image/admin/0xea7menL90lWHwbjZoP.png", alt="Lighthouse audit with 'Eliminate render-blocking resource' or 'Defer unused CSS' opportunities", width="743", height="449" %}

{% Aside 'gotchas' %}
Keep in mind that if you inline a large amount of CSS, it delays the transmission of the rest of the HTML document. If everything is prioritized then nothing is. Inlining also has some downsides in that it prevents the browser from caching the CSS for reuse on subsequent page loads, so it's best to use it sparingly.
{% endAside %}

<p id="14KB">To minimize the number of roundtrips to first render, aim to keep above-the-fold content under <strong>14 KB</strong> (compressed).</p>

{% Aside 'note' %}
New [TCP](https://hpbn.co/building-blocks-of-tcp/) connections cannot immediately use the full available bandwidth between the client and the server, they all go through [slow-start](https://hpbn.co/building-blocks-of-tcp/#slow-start) to avoid overloading the connection with more data than it can carry. In this process, the server starts the transfer with a small amount of data and if it reaches the client in perfect condition, doubles the amount in the next roundtrip. For most servers, 10 packets or approximately 14 KB is the maximum that can be transferred in the first roundtrip.
{% endAside %}

The performance impact you can achieve with this technique depends on the type of your website. Generally speaking, the more CSS a site has, the greater the possible impact of inlined CSS.

## Overview of tools

There are a number of great tools that can automatically determine the critical CSS for a page. This is good news because doing this manually would be a tedious process. It requires analysis of the entire DOM to determine the styles that are applied to each element in the viewport.

### Critical

[Critical](https://github.com/addyosmani/critical) extracts, minifies and inlines above-the-fold CSS and is available as [npm module](https://www.npmjs.com/package/critical). It can be used with Gulp (directly) or with Grunt (as a [plugin](https://github.com/bezoerb/grunt-critical)) and there's a [webpack plugin](https://github.com/anthonygore/html-critical-webpack-plugin) too.

It's a simple tool that takes a lot of thinking out of the process. You don't even have to specify the stylesheets, Critical automatically detects them. It also supports extracting critical CSS for multiple screen resolutions.

### criticalCSS

[CriticalCSS](https://github.com/filamentgroup/criticalCSS) is another [npm module](https://www.npmjs.com/package/criticalcss) that extracts above-the-fold CSS. It is also available as a CLI.

It doesn't have options to inline and minify critical CSS, but it does let you force-include rules that don't actually belong in critical CSS and gives you more granular control over including `@font-face` declarations.

### Penthouse

[Penthouse](https://github.com/pocketjoso/penthouse) is a good choice if your site or app has a large number of styles or styles which are being dynamically injected into the DOM (common in Angular apps). It uses [Puppeteer](https://github.com/GoogleChrome/puppeteer) under the hood and even features an [online hosted version](https://jonassebastianohlsson.com/criticalpathcssgenerator/) too.

Penthouse doesn't detect stylesheets automatically, you have to specify the HTML and CSS files that you want to generate critical CSS for. The upside is that it's good at running many jobs in parallel.

# Optimizing resource loading with the Fetch Priority API

{% BrowserCompat 'api.HTMLImageElement.fetchPriority' %}

{% Aside %}
This feature was originally called Priority Hints but was renamed to Fetch Priority after standardization. See [History](#history) below for more details.
{% endAside %}

When a browser parses a web page and begins to discover and download resources such as images, scripts, or CSS, it assigns them a fetch `priority` in an attempt to download resources in an optimal order. These priorities can depend on the kind of resource and where it is in the document. For example, in-viewport images may have a `High` priority while the priority for early loaded, render-blocking CSS via `<link>`s in the `<head>` could be `Very High`. Browsers are pretty good at assigning priorities that work well but may not be optimal in all cases.

In this article, we'll discuss the Fetch Priority API and the `fetchpriority` HTML attribute, which allow you to hint at the relative priority of a resource (`high` or `low`). Fetch Priority can help optimize the Core Web Vitals.

<figure>
  {% Img src="image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/UQ60oFwWrVCPbFYx3pJY.png", alt="A filmstrip view comparing two tests of the Google Flights homepage. At bottom, Fetch Priority are used to boost the priority of the hero image, resulting in a 0.7 second decrease in LCP.
", width="800", height="400" %}
  <figcaption>Fetch Priority improving Largest Contentful Paint from 2.6&nbsp;s to 1.9&nbsp;s in a test of Google Flights</figcaption>
</figure>

## Summary

**A few key areas where Fetch Priority can help:**

- Boost the priority of the LCP image by specifying `fetchpriority="high"` on the image element, causing LCP to happen sooner.
- Increase the priority of `async` scripts using better semantics than the current hack that is commonly used (inserting a <code>&lt;link rel="preload"&gt;</code> for the `async` script).
- Decrease the priority of late-body scripts to allow for better sequencing with images.

Historically, developers have had some, but limited, influence over resource priority using [preload](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preload/) and [preconnect](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/). Fetch Priority complements these [Resource Hints](https://www.w3.org/TR/resource-hints/), but it's essential to understand where they all fit in. Preload lets you tell the browser about critical resources you want to load early before they are discovered naturally. This is especially useful for resources that are not easily discovered, such as fonts included in stylesheets, background images, or resources loaded from a script. Preconnect helps warm up connections to cross-origin servers and can help improve metrics like [Time-to-first-byte](/ttfb/) and is useful when you know an origin but not necessarily the exact URL of a resource that will be needed.

Fetch Priority is a markup-based signal (available through the `fetchpriority` attribute) that developers can use to indicate the relative priority of a particular resource. You can also use these hints via JavaScript and the [Fetch API](/introduction-to-fetch/) with the `priority` property to influence the priority of resource fetches made for data. Fetch Priority can also complement preload. Take a Largest Contentful Paint image, which, when preloaded, will still get a low priority. If it is pushed back by other early low-priority resources, using Fetch Priority can help how soon the image gets loaded.

Fetch Priority is [available](https://www.chromestatus.com/feature/5273474901737472) in Chrome 101 or later.

## Resource priority

The resource download sequence depends on the browser's assigned priority for every resource on the page. Different factors can affect priority computation logic. For example,

- CSS, fonts, scripts, images, and third-party resources are assigned different priorities.
- The location or order in which you reference resources in the document also affects the priority of resources.
- The [`preload`](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preload/) resource hint helps the browser to discover a resource faster and thus load it before the document loads it and affects priority.
- Priority computation changes for [`async` or `defer`](/efficiently-load-third-party-javascript/#use-async-or-defer) scripts.

The following table considers such factors to show how most resources are currently prioritized and sequenced in Chrome.

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>&nbsp;</th>
        <th>Layout-blocking</th>
        <th>Load in layout-blocking phase</th>
        <th colspan="3">Load one-at-a-time in layout-blocking phase</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><strong>Blink<br>Priority</strong></td>
        <td><strong>VeryHigh</strong></td>
        <td><strong>High</strong></td>
        <td><strong>Medium</strong></td>
        <td><strong>Low</strong></td>
        <td><strong>VeryLow</strong></td>
      </tr>
      <tr>
        <td><strong>DevTools<br>Priority</strong></td>
        <td><strong>Highest</strong></td>
        <td><strong>High</strong></td>
        <td><strong>Medium</strong></td>
        <td><strong>Low</strong></td>
        <td><strong>Lowest</strong></td>
      </tr>
      <tr>
        <td></td>
        <td>Main resource</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td>CSS*** (early**)</td>
        <td></td>
        <td>CSS*** (late**)</td>
        <td></td>
        <td>CSS (mismatch)</td>
      </tr>
      <tr>
        <td></td>
        <td></td>
        <td>Script (early** or not from preload scanner)</td>
        <td>Script (**late)</td>
        <td>Script (async)</td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td>Font</td>
        <td>Font (preload)</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td></td>
        <td>Import</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td></td>
        <td>Image (in viewport)</td>
        <td></td>
        <td>Image</td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td>Media</td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td>SVG document</td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td>Prefetch</td>
      </tr>
      <tr>
        <td></td>
        <td></td>
        <td>Preload*</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td></td>
        <td>XSL</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td>XHR (sync)</td>
        <td>XHR/fetch* (async)</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
    </tbody>
  </table>
</div>

{% Aside %}
\* Preload using `"as"` or fetch using `"type"` use the priority of the type they are requesting (e.g., preload `as="stylesheet"` will use Highest priority). With no `"as"`, they will behave like an XHR.<br>
\*\* "Early" is defined as being requested before any non-preloaded images have been requested ("late" is after).<br>
\*\*\* CSS where the media type doesn't match is not fetched by the preload scanner and is only processed when the main parser reaches it, which usually means it will be fetched very late and with a "late" priority.
{% endAside %}

The browser downloads resources with the same computed priority in the order they are discovered. You can check the priority assigned to different resources when loading a page under the Chrome Dev Tools **Network** tab. (Ensure that you include the priority column by right-clicking on the table headings).

<figure>
  {% Img src="image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/Lwm9jwJF5sQ3gQ7PHSs1.png", alt="A screenshot of assets listed in the network tab of Chrome's DevTools. The columns read, from left to right: name, status, type, initiator, size, time, and priority.", width="800", height="263" %}
  <figcaption>Priority for resource <code>type = &quot;font&quot;</code> on BBC news detail page</figcaption>
</figure>


<figure>
  {% Img src="image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/K5U3maOt2TFBbtScW7TY.png", alt="A screenshot of assets listed in the network tab of Chrome's DevTools. The columns read, from left to right: name, status, type, initiator, size, time, and priority.", width="800", height="175" %}
  <figcaption>Priority for resource type = &quot;script&quot; on BBC news detail page</figcaption>
</figure>

## When would you need Fetch Priority?

Knowledge of the browser's prioritization logic provides you with a few existing knobs to tweak the order of downloads. You can

1.  Place resource tags such as `<script>` and `<link>` depending on the order you want to download them. Resources with the same priority are generally loaded in the order they are discovered.
2.  [Use the `preload` resource hint](/preload-critical-assets/) to download necessary resources earlier, particularly for resources that are not easily discovered early by the browser.
3.  Use [`async` or `defer`](/efficiently-load-third-party-javascript/#use-async-or-defer) to download scripts without blocking other resources.
4.  Lazy-load below-the-fold content so that the browser can use the available bandwidth for more critical above-the-fold resources.

These techniques help to control the browser's priority computation, thereby improving performance and [Core Web Vitals](/vitals/). For example, when a critical background image is preloaded, it can be discovered much earlier, improving the Largest Contentful Paint ([LCP](/lcp/)).

Sometimes these handles may not be enough to prioritize resources optimally for your application. Here are some of the scenarios where Fetch Priority could be helpful:

1. You have several above-the-fold images, but all of them need not have the same priority. For example, in an image carousel, only the first visible image needs a higher priority compared to the others.
2. Hero images inside the viewport start at a "Low" priority. After the layout is complete, Chrome discovers they are in the viewport and boosts their priority (unfortunately, dev tools only shows the final priority—WebPageTest will show both). This usually adds a significant delay to loading the image. Providing the Fetch Priority in markup lets the image start at a "High" priority and start loading much earlier.<br><br>Note that preload is still required for the early discovery of LCP images included as CSS backgrounds and can be combined with Fetch Priority by including the `fetchpriority='high'` on the preload, otherwise it will still start with the default "Low" priority for images.
3. Declaring scripts as `async` or `defer` tells the browser to load them asynchronously. However, as seen in the figure above, these scripts are also assigned a "low" priority. You may want to bump up their priority while ensuring asynchronous download, particularly for any scripts that are critical for the user experience.
4. You may use the JavaScript [`fetch()`](/introduction-to-fetch/) API to fetch resources or data asynchronously. Fetch is assigned a "High" priority by the browser. There may be situations where you do not want all your fetches to be executed with "High" priority and prefer using different Fetch Priority instead. This can be helpful when running background API calls and mixing them with API calls that are responding to user input, like with autocomplete. The background API calls can be tagged as "Low" priority and the interactive API calls marked as "High" priority.
5. The browser assigns CSS and fonts a "High" priority, but all such resources may not be equally important or required for LCP. You can use Fetch Priority to lower the priority of some of these resources.

## The `fetchpriority` attribute

You can provide a Fetch Priority using the `fetchpriority` HTML attribute. You can use the attribute with `link`, `img`, and `script` tags. The attribute allows you to specify the priority for resource types such as CSS, fonts, scripts, and images when downloaded using the supported tags.
The `fetchpriority` attribute accepts one of three values:

- `high`: You consider the resource a high priority and want the browser to prioritize it as long as the browser's heuristics don't prevent that from happening.
- `low`: You consider the resource a low priority and want the browser to deprioritize it if it's heuristics permit.
- `auto`: This is the default value where you don't have a preference and let the browser decide the appropriate priority.

Here are a few examples of using the `fetchpriority` attribute in markup and the script-equivalent `priority` property.

```html
<!-- We don't want a high priority for this above-the-fold image -->
<img src="/images/in_viewport_but_not_important.svg" fetchpriority="low" alt="I'm an unimportant image!">

<!-- We want to initiate an early fetch for a resource, but also deprioritize it -->
<link rel="preload" href="/js/script.js" as="script" fetchpriority="low">

<script>
  fetch('https://example.com/', {priority: 'low'})
  .then(data => {
    // Trigger a low priority fetch
  });
</script>
```

### Browser priority and `fetchpriority`

You can apply the `fetchpriority` attribute to different resources as shown in the following figure to potentially increase or reduce their computed priority. `fetchpriority="auto"` (◉) in each row denotes the default priority for that type of resource.

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>&nbsp;</th>
        <th>Layout-blocking</th>
        <th>Load in layout-blocking phase</th>
        <th colspan="3">Load one-at-a-time in layout-blocking phase</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><strong>Blink<br>Priority</strong></td>
        <td><strong>VeryHigh</strong></td>
        <td><strong>High</strong></td>
        <td><strong>Medium</strong></td>
        <td><strong>Low</strong></td>
        <td><strong>VeryLow</strong></td>
      </tr>
      <tr>
        <td><strong>DevTools<br>Priority</strong></td>
        <td><strong>Highest</strong></td>
        <td><strong>High</strong></td>
        <td><strong>Medium</strong></td>
        <td><strong>Low</strong></td>
        <td><strong>Lowest</strong></td>
      </tr>
      <tr>
        <td>Main Resource</td>
        <td>◉</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>CSS*** (early**)</td>
        <td>⬆◉</td>
        <td>⬇</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>CSS*** (late**)</td>
        <td></td>
        <td>⬆</td>
        <td>◉</td>
        <td>⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>Script (early** or not from preload scanner)</td>
        <td></td>
        <td>⬆◉</td>
        <td></td>
        <td>⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>Script*** (late**)</td>
        <td></td>
        <td>⬆</td>
        <td>◉</td>
        <td>⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>Script (async/defer)</td>
        <td></td>
        <td>⬆</td>
        <td></td>
        <td>◉⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>Font</td>
        <td>◉</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>Font (preload)</td>
        <td></td>
        <td>⬆◉</td>
        <td></td>
        <td>⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>Import</td>
        <td></td>
        <td>◉</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>Image (in viewport)</td>
        <td></td>
        <td>⬆◉</td>
        <td></td>
        <td>⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>Image</td>
        <td></td>
        <td>⬆</td>
        <td></td>
        <td>◉⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>Media (video/audio)</td>
        <td></td>
        <td>⬆</td>
        <td></td>
        <td>◉⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>SVG document</td>
        <td></td>
        <td>⬆</td>
        <td></td>
        <td>◉⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>XHR (sync) - deprecated</td>
        <td>◉</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>XHR/fetch* (async)</td>
        <td></td>
        <td>⬆◉</td>
        <td></td>
        <td>⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>Preload*</td>
        <td></td>
        <td>⬆◉</td>
        <td></td>
        <td>⬇</td>
        <td></td>
      </tr>
      <tr>
        <td>Prefetch</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td>◉</td>
      </tr>
      <tr>
        <td>Favicon</td>
        <td></td>
        <td></td>
        <td>◉</td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>XSL</td>
        <td></td>
        <td>◉</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
    </tbody>
  </table>
</div>

{% Aside %}
\* Preload using `"as"` or fetch using `"type"` use the priority of the type they are requesting (e.g., preload `as="stylesheet"` will use Highest priority). With no `"as"`, they will behave like an XHR.<br>
\*\* "Early" is defined as being requested before any non-preloaded images have been requested ("late" is after).<br>
\*\*\* CSS where the media type doesn't match is not fetched by the preload scanner and is only processed when the main parser reaches it, which usually means it will be fetched very late and with a "late" priority.<br>
◉: `fetchpriority="auto"`<br>
⬆: `fetchpriority="high"`<br>
⬇: `fetchpriority="low"`<br>
Images within the viewport start at "Low" priority and then at layout time are boosted to "High". By tagging it in markup using `fetchpriority="high"`, it can start at "High" immediately and load much faster.
{% endAside %}

Note that `fetchpriority` sets the _relative priority_—that is it raises or lowers the default priority by an appropriate amount, rather than explicitly setting the priority to "High" or "Low" and the browser decides the relative priority. Often this is "High" or "Low", but not always. For example, critical CSS with `fetchpriority="high"` will still retain the "VeryHigh"/"Highest" priority, and using `fetchpriority="low"` on these will still retain the "High" priority—in neither case is the Priority explicitly set to "High" or "Low".

### Use cases

You can use the `fetchpriority` attribute to address scenarios where you may wish to provide the browser with an extra hint as to what priority to fetch a resource with.

#### Increase the priority of the LCP image

You can specify `fetchpriority="high"` to boost the priority of the LCP or other critical images.

```html
<img src="lcp-image.jpg" fetchpriority="high">
```

The following comparison shows the Google Flights page with an LCP background image loaded with and without Fetch Priority. With the priority set to high, the [LCP improved from 2.6s to 1.9s](https://www.webpagetest.org/video/compare.php?tests=211006_AiDcG3_40871b05d6040112a05be4524565cf5d%2C211006_BiDcHR_bebed947f1b6607f2d97e8a899fdc36b&thumbSize=200&ival=100&end=visual).

<figure>
  {% Video src="video/1L2RBhCLSnXjCnSlevaDjy3vba73/BCngJfoVOy0YbUz8wFrM.mp4", autoplay=true, muted=true, playsinline=true, loop=true, controls=true %}
  <figcaption>An experiment conducted using Cloudflare workers to rewrite the Google Flights page to use Fetch Priority.</figcaption>
</figure>

#### Lower the priority of above-the-fold images

You can use `fetchpriority="low"` to lower the priority of above-the-fold images that may not be important for example in an image carousel.

```html
<ul class="carousel">
  <img src="img/carousel-1.jpg" fetchpriority="high">
  <img src="img/carousel-2.jpg" fetchpriority="low">
  <img src="img/carousel-3.jpg" fetchpriority="low">
  <img src="img/carousel-4.jpg" fetchpriority="low">
</ul>
```

In an earlier experiment with the [Oodle](https://github.com/google/oodle-demo) app, we used this to lower the priority of images that do not appear on load. It helped to cut down the load time by 2 seconds.

<figure>
  {% Img src="image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/Tn4OkGpqPbrSQtd1j3GV.png", alt="A side-by-side comparison of Fetch Priority when used on the Oodle app's image carousel. On the left, the browser sets default priorities for carousel images, but downloads and paints those images around two seconds slower than the example on the right, which sets a higher priority on only the first carousel image.", width="800", height="460" %}
</figure>

#### Lower the priority of preloaded resources

To stop preloaded resources from competing with other critical resources, you could provide a hint to reduce their priority. You can use this technique with images, scripts, and CSS.

```html
<!-- Lower priority only for non-critical preloaded scripts -->
<link rel="preload" as="script" href="critical-script.js">
<link rel="preload" href="/js/script.js" as="script" fetchpriority="low">

<!-- Preload CSS without blocking other resources -->
<link rel="preload" as="style" href="theme.css" fetchpriority="low" onload="this.rel='stylesheet'">
```

#### Reprioritize scripts

Scripts required to make some parts of the page interactive are essential but should not block other resources. You can mark these as async with high priority.

```html
<script src="async_but_important.js" async fetchpriority="high"></script>
```

Scripts cannot be marked as async if they rely on specific DOM states. However if they are lower down on the page, they may be downloaded with a lower priority as shown.

```html
<script src="blocking_but_unimportant.js" fetchpriority="low"></script>
```

#### Lower the priority for non-critical data fetches

The browser executes `fetch` with a high priority. If you have multiple fetches that may be fired simultaneously, you can use the high default priority for the more critical data fetches and lower it for less critical data.

```javascript
// Important validation data (high by default)
let authenticate = await fetch('/user');

// Less important content data (suggested low)
let suggestedContent = await fetch('/content/suggested', {priority: 'low'});
```

## Fetch Priority implementation notes

Fetch Priority can improve performance in specific use cases, as discussed above. There are some things to be aware of:

- The `fetchpriority` attribute is a hint and not a directive. The browser will try to respect the developer's preference. It is also possible that the browser will apply its preferences for resource priority as deemed necessary in case of conflicts.
- Fetch Priority should not be confused with a preload. They are both distinct because:
    - Preload is a mandatory fetch and not a hint.
    - Preload allows the browser to discover the resource early, but it will still fetch it with the default priority. Conversely, Fetch Priority does not aid discoverability, but does allow you to increase or decrease the fetch priority.
    - It is easier to observe and measure the effects of a preload.

  Fetch Priority can complement preloads by increasing the granularity of prioritization. If you had already specified a preload as one of the first items in the `<head>` for an LCP image, then a `high` Fetch Priority may not result in significant gains. However, if the preload was after other resources, then a `high` Fetch Priority can improve the LCP. If a critical image is a CSS background image, you should preload it with `fetchpriority = "high"`.
- The noticeable gains due to prioritization will be more relevant in environments where more resources contend for the available network bandwidth. This is common for HTTP/1.x connections where parallel downloads are not possible or in low bandwidth HTTP/2 connections. Prioritization can resolve bottlenecks in these conditions.
- CDNs do [not uniformly implement HTTP/2 prioritization](https://github.com/andydavies/http2-prioritization-issues#cdns--cloud-hosting-services). Even if the browser communicates the priority suggested using Fetch Priority; the CDN may not reprioritize resources in the required order. This makes testing of Fetch Priority difficult. The priorities are applied both internally within the browser and with protocols that support prioritization (HTTP/2 and HTTP/3). It is still worth using even for just the internal browser prioritization independent of CDN or origin support, as that will often change when resources are requested by the browser—for example low priority resources like images are often held back from being requested while the browser processes the critical `<head>` items.
- It may not be possible to introduce Fetch Priority as a best practice in your initial design. It is an optimization that you can apply later in the development cycle. You can check the priorities being assigned to different resources on the page, and if they do not match your expectations, you could introduce Fetch Priority for further optimization.

### Using Preload after Chrome 95

The Fetch Priority feature was available for trial in Chrome 73 to 76 but was not released due to prioritization issues with preloads fixed in Chrome 95. Prior to Chrome 95, requests issued via `<link rel=preload>` always start before other requests seen by the preload scanner, even if the other requests have a higher priority.

With the fix in Chrome 95 and the enhancement for Fetch Priority, we hope that developers will start using preload for its [intended purpose](https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/#loading-of-late-discovered-resources)—to preload resources not detected by the parser (fonts, imports, background LCP images). The placement of the `preload` hint will affect when the resource is preloaded. Some key points on using preload are:

- Including the preload in HTTP headers will cause it to jump ahead of everything else.
- Generally, preloads will load in the order the parser gets to them for anything above &quot;Medium&quot; priority—so be careful if you are including preloads at the beginning of the HTML.
- Font preloads will probably work best towards the end of the head or beginning of the body.
- Import preloads (dynamic `import()` or `modulepreload`) should be done after the script tag that needs the import (so the actual script gets loaded/parsed first). Basically, if the script tag loads a script that will trigger the load of dependencies, make sure the `<link rel=preload>` for the dependencies is after the parent script tag, otherwise the dependencies may end up loading before the main script. In the proper order, the main script can be parsed/eval'd while the dependencies are loading.
- Image preloads will have a "Low" priority (without Fetch Priority) and should be ordered relative to async scripts and other low or lowest priority tags.

## History

Fetch Priority was first experimented with in Chrome as an origin trial in 2018 and then again in 2021 using the `importance` attribute. At that time it was known as [Priority Hints](https://github.com/WICG/priority-hints). The interface has since changed to `fetchpriority` for HTML and `priority` for JavaScript's Fetch API as part of the web standards process. To reduce confusion we now refer to this API as Fetch Priority.

## Browser compatibility

{% BrowserCompat 'api.HTMLImageElement.fetchPriority' %}

As of this writing, Fetch Priority is only available in Chromium-based browsers. Other browser engines or earlier versions of Chromium browsers will ignore the attribute and use their default prioritization heuristics. Until another browser implements Fetch Priority, you may notice some references—[such as MDN](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/fetchPriority)—mark this as [_Experimental_](https://developer.mozilla.org/docs/MDN/Writing_guidelines/Experimental_deprecated_obsolete#experimental), however Fetch Priority is now standardized and included in the [HTML living standard](https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes) and [Fetch living standard](https://fetch.spec.whatwg.org/#request-priority).

## Conclusion

Developers are likely to be interested in Fetch Priority with the fixes in preload behavior and the recent focus on Core Web Vitals and LCP. They now have additional knobs available to achieve their desired loading sequence.

# Fixing website speed cross-functionally

One common myth connected to site speed is that it's only the development team's responsibility. The
reality is that a fast site requires help from several departments. Developers can rarely solve the
issues alone, no matter how talented they are. So how do you fix speed together with your
colleagues? This article explains how you (as a web developer) can convince your company
to prioritize site speed, get help from different departments while optimizing, and increase awareness
of your optimization project's success.

{% Aside %}
This guide has a slight focus on e-commerce (particularly the business stakeholders section), but a
lot of the guidance is general enough to be useful to any website that wants to improve mobile conversions.
{% endAside %}

## Fix speed with the help of your business stakeholders {: #stakeholders }

Many companies are in the middle of a disruptive shift in consumer behavior, where the growth of
people buying on digital platforms instead of physical stores tells the tale that companies need to
adjust quickly or risk facing decreasing revenue. This will require both new organizational
processes and mindsets, and a strong buy-in from the stakeholders in order to succeed.

### The shared challenge

The way companies organize themselves will most likely need to change within several levels. First
of all, a speed project, with the potential uplift in revenue that it can create, will be difficult
to perform without developers getting time to work on it. The resource allocation needs to be in
place, and the collaboration with other departments such as marketing and web designers will often
go smoother if stakeholders and executives show that it's a priority project.

Apart from this operational level, it's also important for stakeholders to understand that there are
two shifts happening right now:

-  Even though a larger proportion of your sales
   might come from the physical stores, [most customers will use a digital platform such as your
   site before the purchase in the stores](https://www.thinkwithgoogle.com/marketing-resources/micro-moments/meet-needs-i-want-to-know-moments/).
   If the site doesn't give visitors a good user experience, then the revenue in physical stores
   risk suffering as well. Plus, studies show that it also affects
   [customers' willingness to recommend your business and their loyalty to your
   business](https://www.thinkwithgoogle.com/intl/en-154/marketing-collections/mobile/why-mobile-speed-means-loyal-customers-and-how-make-it-happen/).

-  The growth of mobile visitors is stronger than desktop visitors, making mobile-first either a
   current or future state to prepare for. Also, there's a strong correlation
   between site speed and conversions on mobile, with
   [one study from SOASTA showing that for every second delay in mobile page load,
   conversions can fall by up to 20%](https://www.thinkwithgoogle.com/data/mobile-page-speed-conversion-data/).

This means that companies that make executive decisions informed by the people who are experts on
digital platforms will have an edge. One suggestion could thereby be to invite one developer
to the meetings where business decisions are being made, to ensure that the site and its
constraints are being considered from the start, and that way, secure increased sales.

### Send a proposal to your stakeholders

> In general, around 10% of sales happen on digital platforms, but studies show that as much as
> [85% of the sales in physical stores are influenced digitally first](https://www.thinkwithgoogle.com/marketing-resources/micro-moments/meet-needs-i-want-to-know-moments/).
> Having a site that performs well, especially on mobile (which is becoming the primary device), is
> thereby crucial for our business's revenue. We propose a speed project that will help the site
> increase conversions, and to set up streams of communication so that the insights in technical
> teams can be better put to use before decisions that will affect the site are made.

### Prepare for meeting with your stakeholders

Before meeting with stakeholders, make sure you're prepared with data for your website and your
competitors' sites, and data that demonstrates high ROI for the changes you're suggesting.
Gather data around the importance of speed. The best scenario is if it's connected to the company's
revenue. Here are some ways to achieve this:

-  **Calculate your relative mobile conversion rate**.
   Work with your analytics team and perform the analysis described in [The value of speed][value]. If
   you historically have one period (at least 2-3 months) when the load times have been much
   higher than another period of equal length, then the slow site speed will usually have affected
   mobile conversions heavily. Through the analysis you can calculate how much revenue the company
   lost during the period of bad speed, proving the value of speed. If load times have stayed at
   about the same level, the analysis will be difficult to perform and the below alternatives can
   give more answers.
-  **Estimate the value of speed from statistical models**.
   With the help of Google's [TestMySite](https://www.thinkwithgoogle.com/feature/testmysite) you
   can run a test and then calculate the impact that a faster site can have on your business. All
   you need is data around average monthly visitors, conversion rate, and average order value, which
   your analytics team can help you with.
-  **Provide best practice case studies**.
   You can also use cases from other companies, who've made their site faster and seen the effect.
   [How Page Load Time Affects Conversion Rates: 12 Case Studies](https://blog.hubspot.com/marketing/page-load-time-conversion-rates)
   provides some examples.
-  **Show the importance of mobile**.
   Ask the analytics team to provide data around what percentage of your traffic comes from mobile versus desktop.
-  **Research how much it will cost**.
   The above calculations are ways to show stakeholders the possible revenue uplift from speed, but
   in order for stakeholders to make decisions they need to know the ROI, so if you come prepared
   with an estimated cost then the executive decision can be reached faster. To do this, evaluate
   how long the initial focused speed project will take, and how much time you will need for
   maintenance afterwards. For instance,
   in [Fast-forward thinking: TUI speed up their site by breaking down silos](https://www.thinkwithgoogle.com/intl/en-154/insights-inspiration/case-studies/fast-forward-thinking-tui-speed-their-site/),
   the developers were given 20% of their time free from backlogs and campaigns, which they could
   devote to speed projects and other ways of making the site better.

### Meet with your stakeholders

-  **Present how much traffic you are getting from mobile versus desktop, and the growth you've had
   from mobile the last few years**. This showcases the importance of creating a great mobile experience.
-  **Present your findings on how speed influences the company's revenue**.
-  **Let the stakeholders know that the developers can help increase revenue from the mobile
   visitors** if they are given time to work on speed continuously.
-  **Suggest that one person from the development team should be invited to executive meetings
   where decisions around the site will be taken**. Nowadays we sell on digital platforms. The
   winners will be the companies who start communicating with their digital experts. Modern
   companies will need to have communication streams set up between departments and between
   stakeholders and developers.
   [A study by the Harvard Business Review](https://hbr.org/2011/09/why-your-it-project-may-be-riskier-than-you-think)
   showed that one in six IT projects has a cost overrun of 200%. One example was Kmart, which was
   losing its competitive position to Walmart and Target when it began its $1.4 billion IT
   modernization project in 2000. By setting up communication and maintenance early, silos can be
   broken down and knowledge sharing can become a competitive advantage.

## Fix speed with the help of your marketing team {: #marketing }

### The shared challenge

Marketing teams need tools to handle ads and data in order to make informed decisions, but most
tools slow down websites and are often implemented with render-blocking scripts which makes visitors
who enter the site have to wait until they see anything on the screen. There is therefore a need for
a balance; the marketing department needs data and tools, but for the site to get low bounce rates
and high conversion rates it has to be fast, so that marketing gets a good return on their
investments.

The balance is achieved by being careful with scripts:

-  Avoid duplicates that measure approximately the same thing.
-  Remove tools that are no longer used.
-  Don't measure "nice-to-have" things. Only measure business-critical things.

Since it's difficult for the developers to know what the marketing department is dependent on, and
since the marketing department might not know which tools are especially heavy for the site, you
need to collaborate.

### Send a proposal to your marketing team

> Tools used for tracking and ads often block how quickly visitors see content when they enter the
> site. This reduces the value of marketing investments, since we pay for ads, people get
> interested, they click but then have to wait. This risks them getting annoyed and leaving. [One
> study](https://www.slideshare.net/nicjansma/reliably-measuring-responsiveness) found that
> conversion rate decreased from around 50% to around 35% when the speed metric [Time to
> Interactive](/tti) increased from 0.15 s to 0.3 s. Can we work together on solving this to create a
> balance between the tools you need and site performance, making increased return on marketing
> investments a result?

### Prepare for meeting with your marketing team

-  **Create a list of the scripts that are implemented on the
   site**.
-  **Send the list to marketing and ask them to take a look at it in advance and sort them into
   three buckets**:
   -  **Business-critical**. These scripts must stay.
   -  **Nice-to-have**. Scripts that might be able to be used less or removed. For instance,
      sometimes companies collect heat maps or screen
      recordings, but the data might only be reviewed infrequently and not lead to action or
      increased conversions. In cases such as these it can be better to agree to only run the tools
      for (say) two weeks, collect the insights for analysis, then clean out the scripts until it's
      time for the next period of focused data collection.
   -  **Not used or not owned**. These should be removed.
-  **Check how much each script slows down your site by comparing site speed with and without it**.
   In [WebPageTest](https://www.webpagetest.org/) this can be set up
   via the [Block tab in Advanced Settings](https://andydavies.me/blog/2018/02/19/using-webpagetest-to-measure-the-impact-of-3rd-party-tags/).
   Or you can [block network requests in Chrome DevTools](https://developer.chrome.com/docs/devtools/network/#block).

### Meet with your marketing team

-  **Go through the list of scripts and give your view on each script**.
   For instance, are any of the nice-to-have scripts very damaging to site
   performance? By receiving recommendations it's easier for the marketing team to prioritize, and
   by hearing about what is business-critical for them it's easier for you to understand their point of
   view.
-  **Discuss the following topics**:
   -  Are the benefits of the data collected leading to an uplift in conversions that exceeds
      the decrease caused by the site getting slower? Data can guide the decision.
   -  Has your company chosen a provider that has the fastest version of the tool on the
      market? The marketing department might need help from developers to work out whether
      business critical tools can be replaced by faster alternatives.
   -  Are there alternatives to any of the scripts on the site? For instance, instead of
      heatmaps it might be possible to start doing [usability
      tests](https://winonmobile.withgoogle.com/5-qualitative-research/#content) where you also
      hear about the "why" behind user behavior, often leading to deeper insights.

{% Aside %}
One common challenge is that be that people become defensive when approached with suggestions.
The key might be to always emphasize respect for the knowledge areas of other departments.
By formulating that you want to understand and listen, an openness can be created for listening
to your ideas as well. And finally, try inviting other teams to social gatherings or coffee and
bakery with different discussion topics. Sometimes the most powerful meetings take place outside the meeting room.
{% endAside %}



### Agree on next steps

-  **Decide on a process moving forward**. Should marketing always ask the developers to review new
   tools and their impact on speed before implementation? Or should marketing get a performance
   budget where they learn how to check speed and they themselves can prioritize which tools they
   want as long as they meet the speed target?

## Fix speed with the help of your web design team {: #design }

One of the biggest challenges for web performance is that while connectivity might be getting faster
in many regions, [websites are becoming
heavier](https://speedcurve.com/blog/web-performance-page-bloat/) and as a result actually getting
slower. Developers need to collaborate with web designers in order to solve this.

### The shared challenge

In the end, all departments within a company are usually dependent on the site leading to
conversions, whether that's for revenue generation or something else. And the harsh truth is that
it doesn't matter how beautiful web pages are if they take such a long time to load that people
don't stick around to see them.

But slim web pages are a shared responsibility. Developers can use various image optimization techniques
to make images load faster. Web designers can create pages that help keep page weight below an
agreed level.

### Send a proposal to your web design team

> Studies show that pages with fewer images and less elements create more conversions. For example,
> [a study by Google and SOASTA](https://www.thinkwithgoogle.com/marketing-resources/experience-design/mobile-page-speed-load-time/)
> found that sessions that converted users had 38% fewer images than sessions
> that didn't convert. Can we work together to reach speed targets and set up a joint
> collaboration that can increase conversions on the site? Examples of what we can achieve
> together include finding the right level of image compression with a balance between quality and
> speed, or reducing complex layouts or functionalities that often result
> in heavy, slow sites.

### Prepare for a meeting with your web design team

-  **Track which pages on the site have the highest load time and highest page weight**. For
   instance, if you're working with an e-commerce site, make sure you check not just the home page,
   but also any campaign pages, some of the biggest category pages, some of the product pages, and
   the checkout flow.
-  **Prepare a [performance budget](/performance-budgets-101) proposal for the
   designers**. One alternative is to start as simple as possible with page weight as the metric and
   set 1 MB as the target for all pages (or 1.5 MB if branding is very important for conversions).
-  **Prepare a list of which pages end up above the target page weight level**.

### Meet with your web design team

-  **Commit to investigating how the development team can deliver images faster through
   [various image optimization techniques](/fast/#optimize-your-images)**, such as image compression,
   responsive images, image resizing, lazy loading, caching, and server optimization.
-  **Create a test page where images are published in
   different image qualities and dimensions, and agree on a level that balances performance and
   quality on different screens**. Remember that visitors on mobile often have short session length,
   with one study showing that [more than half of mobile sessions are 30 seconds or shorter](http://ubicomp.oulu.fi/files/mobilehci14.pdf).
   These users might not study images in detail and might prefer the site to be fast.
-  **Propose a performance budget** where the web designers commit to making sure that all web
   pages have a page weight below 1 MB (or at maximum 1.5 MB).

## Fix speed with the help of your analytics team {: #analytics }

Businesses need data to know what to focus on and to get a basis for decisions. But even though many
talk about the importance of site speed and its connection to revenue and sales, there are still
many companies that don't include speed in their weekly reports outside tech teams. This can be
changed if developers start collaborating with analytics teams.

### The shared challenge

The reality is this: what's not reported will not be focused on. If site speed is not part of
weekly or monthly reports to business stakeholders, it will easily be forgotten and you will have a hard time
getting resources to focus on it. But since analytics teams often aren't experienced with tracking speed, they will need
help from developers. And the developers will need help from the analytics team to calculate the
effect on revenue when speed improvements are made. These departments are more dependent on each
other than one might at first think.

### Send a proposal to your analytics team

> Many [case studies](https://blog.hubspot.com/marketing/page-load-time-conversion-rates) show the
> effect site speed has on revenue and bounce rate. Could we possibly collaborate to set up site
> speed tracking for our company, and perhaps get speed as a part of the reports going to
> stakeholders so that we get more buy-in?

### Prepare for a meeting with your analytics team

-  **Track speed regularly**. One recommendation is to use an API with a tool like [Lighthouse CI](https://github.com/GoogleChrome/lighthouse-ci),
   [PageSpeed Insights](https://developers.google.com/speed/docs/insights/v5/get-started), or
   [WebPageTest](https://github.com/andydavies/WPT-Bulk-Tester), enabling you to collect stats
   regularly in a controlled lab setting for easy benchmarking across different time periods.
-  **Forward [The value of speed][value] to the analytics team
   and ask if they can perform the analysis** so that you have the chart ready to be analyzed during
   the meeting.

### Meet with your analytics team

-  **Check the chart created from [The value of speed][value]**.
   Relative mobile conversion rate is one way of
   tracking the impact of speed and how your site is improving. Ask the analytics team if they're open to start
   tracking this.
-  **Decide if the load time metric in Google Analytics is good enough for simplified business reports**.
   [The value of speed][value] uses this metric. As many developers
   know, this is only one of many important [speed metrics](/metrics/), and since it's in Google Analytics it's also [field
   data](/user-centric-performance-metrics/#in-the-field) instead of [lab data](/user-centric-performance-metrics/#in-the-lab).
   But, is it good enough to be used for simplified business reports?
   Check the result you get from the analysis above, and see if it is possible to observe the
   relative mobile conversion rate going down when load times have been high for at least 2–3
   months, and if the relative mobile conversion rate goes up when load times have been low for 2–3
   months. If your load times have remained pretty stable, it will be difficult to know if the
   analysis is working for you until you've improved speed heavily. If so, go with the next
   alternative.
-  **Show your charts for the speed metrics the developers use**. Explain them to the analytics team
   and see if you discover correlations with the conversion rate or the relative mobile conversion
   rate.
-  **Give the analytics team time to think about ways to use speed in
   calculations connected to revenue and how to report this to stakeholders**. This is often a new topic for them.
   Then meet again and try out an approach that you agree upon. As always, you need to revise data collection until you
   find the right fit, but this is a way to start the collaboration.

### Next steps

Ask your analytics team to perform the analysis outlined in [The value of speed][value] after the speed
project has led to big improvements. It will show the company how much more conversions and revenue
it has gained, rewarding your team as well as all of the departments that collaborated with you.
This can be included in a weekly report to stakeholders, so the project doesn't
become a one-off effort but is continuously maintained.

[value]: /value-of-speed/

# Best practices for fonts

This article discusses performance best practices for fonts. There are a variety of ways in which web fonts impact performance:

- **Delayed text rendering:** If a web font has not loaded, browsers typically delay text rendering. In many situations, this delays [First Contentful Paint (FCP)](/fcp). In some situations, this delays [Largest Contentful Paint (LCP)](/lcp/).
- **Layout shifts:** The practice of font swapping has the potential to [cause layout shifts](/debug-layout-shifts/#identifying-the-cause-of-a-layout-shift) and so impact [Cumulative Layout Shift (CLS)](/cls/). These layout shifts occur when a web font and its fallback font take up different amounts of space on the page.

This article is broken down into three sections: **font loading**, **font delivery**, and **font rendering**. Each section explains how that particular aspect of the font lifecycle works and provides corresponding best practices.

## Font loading

Fonts are typically important resources, as without them the user might be unable to view page content. Thus, best practices for font loading generally focus on making sure that fonts get loaded as early as possible. Particular care should be given to fonts loaded from third-party sites as downloading these font files requires separate connection setups.

If you're unsure if your page's fonts are being requested in time, check the **Timing** tab within the **Network** panel in Chrome DevTools for more information.

{% Img
    src="image/j2RDdG43oidUy6AL6LovThjeX9c2/hFVGVzDQHymbC5aIAtD9.png",
    alt="Screenshot of the Timing tab in DevTools",
    width="800",
    height="472"
%}

### Understanding `@font-face`

Before diving into best practices for font loading it's important to understand how [`@font-face`](https://developer.mozilla.org/docs/Web/CSS/@font-face) works and how this impacts font loading.

The [`@font-face`](https://developer.mozilla.org/docs/Web/CSS/@font-face) declaration is an essential part of working with any web font. At a minimum, it declares the name that will be used to refer to the font and indicates the location of the corresponding font file.

```css
@font-face {
  font-family: "Open Sans";
  src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2");
}
```

A common misconception is that a font is requested when a `@font-face` declaration is encountered—this is not true. By itself, `@font-face` declaration does not trigger font download. Rather, a font is downloaded only if it is referenced by styling that is used on the page. For example, like this:

```css
@font-face {
  font-family: "Open Sans";
  src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2");
}

h1 {
  font-family: "Open Sans"
}
```

In other words, in the example above, `Open Sans` would only be downloaded if the page contained a `<h1>` element.

{% Aside %}
    Other ways of loading a font are the [`preload`](https://developer.mozilla.org/docs/Web/HTML/Preloading_content) resource hint and the [Font Loading API](/optimize-webfont-loading/#the-font-loading-api).
{% endAside %}

Thus, when thinking about font optimization, it's important to give stylesheets just as much consideration as the font files themselves. Changing the contents or delivery of stylesheets can have a significant impact on when fonts arrive. Similarly, removing unused CSS and splitting stylesheets can reduce the number of fonts loaded by a page.

### Inline font declarations

Most sites would strongly benefit from inlining font declarations and other critical styling in the `<head>` of the main document rather than including them in an external stylesheet. This allows the browser to discover the font declarations sooner as the browser doesn't need to wait for the external stylesheet to download.

```html
<head>
  <style>
    @font-face {
        font-family: "Open Sans";
        src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2");
    }

    body {
        font-family: "Open Sans";
    }

    ...etc.

  </style>
</head>
```

{% Aside %}
    Note that if only some of the CSS is inlined, then the browser will still need to wait for all the CSS to be loaded, before it can discover if fonts are needed.<br>
    <br>
    Also note that inlining the font files themselves is not recommended. Inlining large resources like fonts is likely to delay the delivery of the main document, and with it, the discovery of other resources.
{% endAside %}


Inlining critical CSS can be a more advanced technique that not all sites will be able to achieve. The performance benefits are clear, but it requires additional processes and build tools to ensure the necessarily CSS—and ideally only the critical CSS—is inlined correctly and that any additional CSS is delivered in a non-render blocking fashion.

### Preconnect to critical third-party origins

If your site loads fonts from a third-party site, it is highly recommended that you use the [`preconnect`](https://developer.mozilla.org/docs/Web/HTML/Link_types/preconnect) resource hint to establish early connection(s) with the third-party origin. Resource hints should be placed in the `<head>` of the document. The resource hint below sets up a connection for loading the font stylesheet.

```html
<head>
  <link rel="preconnect" href="https://fonts.com">
</head>
```

To preconnect the connection that is used to download the font file, add a separate `preconnect` resource hint that uses the `crossorigin` attribute. Unlike stylesheets, font files must be sent over a [CORS connection](https://developer.mozilla.org/docs/Web/HTTP/CORS#what_requests_use_cors).

```html
<head>
  <link rel="preconnect" href="https://fonts.com">
  <link rel="preconnect" href="https://fonts.com" crossorigin>
</head>
```

When using the `preconnect` resource hint, keep in mind that a font provider may serve stylesheets and fonts from separate origins. For example, this is how the `preconnect` resource hint would be used for Google Fonts.

```html
<head>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
</head>
```

{% Aside %}
  [Google Fonts](https://fonts.google.com/) provides the option to load fonts via `<link>` tags or an `@import` statement. The `<link>` code snippet includes a `preconnect` resource hint and therefore will likely result in faster stylesheet
  delivery than using `@import` version. These `<link>` tags should be placed as early in the document as possible.
{% endAside %}

### Be cautious when using `preload` to load fonts

Although `preload` is highly effective at making fonts discoverable early in the page load process, this comes at the cost of taking away browser resources from the loading of other resources.

Inlining font declarations and adjusting stylesheets may be a more effective approach. These adjustments come closer to addressing the root cause of late-discovered fonts—rather than just providing a workaround.

In addition, using `preload` as a font-loading strategy should also be used carefully as it bypasses some of the browser's built-in content negotiation strategies. For example, `preload` ignores `unicode-range` declarations, and if used prudently, should only be used to load a single font format.

However, when using external stylesheets, preloading the most important fonts can be very effective since the browser will not otherwise discover whether the font is needed until much later.

## Font delivery

Faster font delivery yields faster text rendering. In addition, if a font is delivered early enough, this can help eliminate layout shifts resulting from font swapping.

### Using self-hosted fonts

On paper, using a self-hosted font should deliver better performance as it eliminates a third-party connection setup. However, in practice, the performance differences between these two options is less clear cut: for example, the [Web Almanac](https://almanac.httparchive.org/en/2020/fonts#fig-7) found that sites using third-party fonts had a faster render than fonts that used first-party fonts.

If you are considering using self-hosted fonts, confirm that your site is using a [Content Delivery Network (CDN)](/content-delivery-networks/) and [HTTP/2](/content-delivery-networks/#http2-and-http3). Without use of these technologies, it is much less likely that self-hosted fonts will deliver better performance. For more information, see [Content Delivery Networks](/content-delivery-networks/).

{% Aside %}
    If you're unsure if using self-hosted fonts will deliver better performance, try serving a font file from your own servers and compare the transfer time (including connection setup) with that of a third-party font. If you have slow servers, don't use a CDN, or don't use HTTP/2 it becomes less likely that the self-hosted font will be more performant.
{% endAside %}

If you use a self-hosted font, it is recommended that you also apply some of the font file optimizations that third-party font providers typically provide automatically—for example, font subsetting and WOFF2 compression. The amount of effort required to apply these optimizations will depend somewhat on the languages that your site supports. In particular, be aware that optimizing fonts for [CJK languages](https://en.wikipedia.org/wiki/CJK_characters) can be particularly challenging.

### Use WOFF2

Of the modern font fonts, [WOFF2](https://www.w3.org/TR/WOFF2/) is the newest, has the widest browser support, and offers the best compression. Because it uses Brotli, WOFF2 compresses 30% better than WOFF, leading to less data to download and therefore faster performance.

Given the browser support, experts now recommend only using WOFF2:

<blockquote>
  <p>
    In fact, we think it is also time to proclaim: Use only WOFF2 and forget about everything else.<br>
    <br>
    This will simplify your CSS and workflow massively and also prevents any accidental double or incorrect font downloads. WOFF2 is now supported everywhere. So, unless you need to support really ancient browsers, just use WOFF2. If you can't, consider not serving any web fonts to those older browsers at all. This will not be a problem if you have a robust fallback strategy in place. Visitors on older browsers will simply see your fallback fonts.
  </p>
  <cite>
    <a href="https://almanac.httparchive.org/en/2022/fonts#performance">Bram Stein, from the 2022 Web Almanac</a>
  </cite>
</blockquote>

### Subset fonts

Font files typically include a large number of [glyphs](https://en.wikipedia.org/wiki/Glyph) for all the various characters they support. But you may not need all the characters on your page and can reduce the size of font files by subsetting fonts.

The [`unicode-range`](https://developer.mozilla.org/docs/Web/CSS/@font-face/unicode-range) descriptor in the `@font-face` declartion informs the browser which characters a font can be used for.

```css
@font-face {
    font-family: "Open Sans";
    src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2");
    unicode-range: U+0025-00FF;
}
```

A font file will be downloaded if the page contains one or more characters matching the unicode range. `unicode-range` is commonly used to serve different font files depending on the language used by page content.

`unicode-range` is often used in conjunction with the technique of subsetting. A subset font includes a smaller portion of the that were contained in the original font file. For example, rather than serve all characters to all users, a site might generate separate subset fonts for Latin and Cyrillic characters. The number of glyphs per font varies wildly: Latin fonts are usually on the magnitude of 100 to 1000 glyphs per font; [CJK](https://en.wikipedia.org/wiki/CJK_characters) fonts may have over 10,000 characters. Removing unused glyphs can significantly reduce the filesize of a font.

Some font providers may provide different versions of fonts files with different subsets automatically. For example, [Google Fonts](https://fonts.google.com/) does this by default:

```css
/* devanagari */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJbecnFHGPezSQ.woff2) format('woff2');
  unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}
/* latin-ext */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJnecnFHGPezSQ.woff2) format('woff2');
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
```

When moving to self-hosting, this is an optimization that can easily be missed and lead to larger font files locally.

It is also possible to manually subset fonts if your font provider allows this, either through an API ([Google Fonts supports this by providing a `text` parameter](https://developers.google.com/fonts/docs/getting_started#specifying_script_subsets)), or by manually editing the font files and then self-hosting. Tools for generating font subsets include [subfont](https://github.com/Munter/subfont) and [glyphanger](https://github.com/zachleat/glyphhanger). However, do [check the licence for the fonts you use allow subsetting](https://subsetting.xyz/) and self-hosting.

### Use fewer web fonts

The fastest font to deliver is a font that isn't requested in the first place. System fonts and variable fonts are two ways to potentially reduce the number of web fonts used on your site.

A **system font** is the default font used by the user interface of a user's device. System fonts typically vary by operating system and version. Because the font is already installed, the font does not need to be downloaded. System fonts can work particularly well for body text.

To use the system font in your CSS, list `system-ui` as the font-family:

```css
font-family: system-ui
```
The idea behind **[variable fonts](/variable-fonts/)** is that a single variable font can be used as a replacement for multiple font files. Variable fonts work by defining a "default" font style and providing ["axes"](/variable-fonts/#axes-definitions) for manipulating the font. For example, a variable font with a `Weight` axis could be used to implement lettering that would previously require separate fonts for light, regular, bold, and extra bold.

{% Aside %}
    We often refer to "Times New Roman" and "Helvetica" as fonts. However, technically speaking, these are font _families_. A family is composed of styles, which are particular variations of the typeface (for example, light, medium, or bold italic). A font file contains a single style unless it is a variable font. A typeface is the underlying design, which can be expressed as digital fonts—and in physical type, like carved woodblocks or metal.
{% endAside %}

Not everyone will benefit from switching to variable fonts. [Variable fonts](/variable-fonts/) contain many styles, so typically have larger file sizes than individual non-variable fonts that only contain one style. Sites that will see the largest improvement from using variable fonts are those that use (and need to use) a variety of font styles and weights.

## Font rendering

When faced with a web font that has not yet loaded, the browser is faced with a dilemma: should it hold off on rendering text until the web font has arrived? Or should it render the text in a fallback font until the web font arrives?

Different browsers handle this scenario differently. By default, Chromium-based and Firefox browsers will block text rendering for up to 3 seconds if the associated web font has not loaded; Safari will block text rendering indefinitely.

This behavior can be configured by using the `font-display` attribute. This choice can have significant implications: `font-display` has the potential to impact LCP, FCP, and layout stability.

### Choose an appropriate `font-display` strategy

[`font-display`](https://developer.mozilla.org/docs/Web/CSS/@font-face/font-display) informs the browser how it should proceed with text rendering when the associated web font has not loaded. It is defined per font-face.


```css
@font-face {
  font-family: Roboto, Sans-Serif
  src: url(/fonts/roboto.woff) format('woff'),
  font-display: swap;
}
```

There are five possible values for `font-display`:

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th><strong>Value</strong></th>
        <th><strong>Block period</strong></th>
        <th><strong>Swap period</strong></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Auto</td>
        <td>Varies by browser</td>
        <td>Varies by browser</td>
      </tr>
      <tr>
        <td>Block</td>
        <td>2-3 seconds</td>
        <td>Infinite</td>
      </tr>
      <tr>
        <td>Swap</td>
        <td>0ms</td>
        <td>Infinite</td>
      </tr>
      <tr>
        <td>Fallback</td>
        <td>100ms</td>
        <td>3 seconds</td>
      </tr>
      <tr>
        <td>Optional</td>
        <td>100ms</td>
        <td>None</td>
      </tr>
    </tbody>
  </table>
</div>

- **Block period**: The block period begins when the browser requests a web font. During the block period, if the web font is not available, the font is rendered in an _invisible_ fallback font and thus the text will not be visible to the user. If the font is not available at the end of the block period, it will be rendered in the fallback font.
- **Swap period**: The swap period comes after the block period. If the web font becomes available during the swap period, it will be "swapped" in.

`font-display` strategies reflect different viewpoints about the tradeoff between performance and aesthetics. As such it's difficult to give a recommended approach as it does depend on individual preferences, how important the web font is to the page and brand, and how jarring a late-arriving font can be when swapped in.

For most sites, these are the three strategies that will be most applicable:

- **If performance is a top priority:** Use `font-display: optional`. This is the most "performant" approach: text render is delayed for no longer than 100ms and there is assurance that there will be no font-swap related layout shifts. However, the downside here is the web font will not be used if it arrives late.

- **If displaying text quickly is a top priority, but you want to still ensure the web-font is used:** Use `font-display: swap` but make sure to deliver the font early enough that it does not cause a layout shift. The downside of this option is the jarring shift when the font arrives late.

- **If ensuring text is displayed in a web font is a top priority:** Use `font-display: block` but make sure to deliver the font early enough that it minimises the delay of the text. The downside of this is the initial text display will be delayed. Note despite this deplay, it can still cause a layout shift as the text is actually drawn invisible, and the fallback font space is therefore user to reserver the space. Once the web font loads, this may require difference space and hence a shift. This may, however, be a less jarring shift than `font-display: swap` as the text itself will not be seen to shift.

{% Aside %}
    `font-display: auto`, `font-display: block`, `font-display: swap`, and `font-display: fallback` all have the potential to cause layout shifts when the font is swapped. However, of these approaches, `font-display: swap` will delay text render the least. Thus, it can be the preferred approach for situations where it is important that text is displayed as quickly as possible, but ultimately gets rendered as a web font.
{% endAside %}

Also keep in mind that these two approaches can be combined: for example, use `font-display: swap` for branding and other visually distinctive page elements; use `font-display: optional` for fonts used in body text.

{% Aside %}
    The `font-display` strategies that work well for traditional web fonts don't work nearly as well for icon fonts. The fallback font for an icon font typically looks significantly different than the icon font and its characters may convey a completely different meaning. As a result, icon fonts are more likely to cause significant layout shifts. In addition, using a fallback font may not be practical. If possible, it's best to replace icon fonts with SVG (this is also better for accessibility). Newer versions of popular icon fonts typically support SVG. For more information on switching to SVG, see [Font Awesome](https://fontawesome.com/v5.15/how-to-use/on-the-web/advanced/svg-sprites) and [Material Icons](https://google.github.io/material-design-icons/#svg).
{% endAside %}

### Reduce the shift between your fallback font and your webfont

To reduce the CLS impact, you can use the new `size-adjust` attributes. For more information see [this article](/css-size-adjust/). This is a very new addition to our toolset, so is more advanced and a bit manual at present. But definitely one to experiment with and watch for tooling improvements in the future!

## Conclusion

Web fonts still be a performance bottleneck but we have an ever-growing range of options to allow us to optimize them to reduce this bottleneck as much as possible.

# How can performance improve conversion?

In our other e-commerce guides you have learned about [what you should measure
to improve performance](/what-should-you-measure-to-improve-performance/), and
[how to measure and report metrics to build a performance culture](/how-to-report-metrics/).

<figure>
  {% Img src="image/admin/tRFlwIEQJcf52vGBEoDp.png", alt="A conversion funnel going from discover to engage to convert to re-engage.", width="800", height="399" %}
  <figcaption>
    A conversion funnel.
  </figcaption>
</figure>

In this guide we'll address the different ways in which a website should be optimized for performance to yield maximum conversions at the end of the funnel.

## Discovery

New users discover a website in most cases through organic search, social sharing, website links or paid campaigns. Some important discovery mechanisms are directly affected by website performance. Website crawlers may have difficulty indexing sites that are slow to load or have extensive [client side rendering and JavaScript](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering).

Speed can also be a direct ranking factor, for example on [web search](https://webmasters.googleblog.com/2018/01/using-page-speed-in-mobile-search.html), [ad campaigns](https://developer.chrome.com/blog/search-ads-speed/#the-mobile-speed-score-for-ads-landing-pages) or [social networks](https://newsroom.fb.com/news/2017/08/news-feed-fyi-showing-you-stories-that-link-to-faster-loading-webpages/).
Keep in mind that new users who discover your website will get an uncached first load, so basically the worst possible experience. This can be especially frustrating if good money was spent to get the user to the website, just to see them dropping off due to a long first load.

Make sure to use appropriate tools as described in [Fast Load Times](/fast) to optimize towards a first load, because first impressions matter—if the first load is too slow, the user may never see the optimized second load or stay around to look at your products. In general [loading times of a website map very well to bounce rates](https://developer.akamai.com/blog/2015/09/01/mobile-web-performance-monitoring-conversion-rate), which in turn often correlate well with conversions.

## Engagement

After getting users to your site, you need to keep them engaged with your content, which you can verify in your analytics of choice by looking at session length, time-on-page, pages-per-session and general [user flows](https://support.google.com/analytics/answer/1709395?hl=en).

<figure>
  {% Img src="image/admin/Apz0cUkUGVR7m9gKepkt.png", alt="A Google Analytics dashboard shows the number of users that drop off from starting page to first and second interactions.", width="800", height="416" %}
  <figcaption>
    A user flow through the funnel as seen by Google Analytics.
  </figcaption>
</figure>

Besides various UX best practices, a smooth, fast and responsive experience is key here. While optimizing a website for discovery means optimizing for first load, optimizing for engagement means fast navigations and fast repeat loads. Analyze at which steps of the flow users drop out, and then relate back to speed metrics for these navigations. This can be analyzed for example via [WebPageTest](https://github.com/WPO-Foundation/webpagetest-docs/blob/main/src/scripting.md#sample-scripts), [Puppeteer](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md) or via the Chrome DevTools [Record](https://developer.chrome.com/docs/devtools/evaluate-performance/#record) feature. We will show you more examples of those in the following guides.

## Conversion

While website conversions are often bound to good discovery paired with great engagement, there are a couple of additional points to remember. Users expect hero images to [load fast](/user-centric-performance-metrics/), call-to-action buttons should be rendered and [labeled](https://developer.chrome.com/blog/lighthouse-load-performance/#all-text-remains-visible-during-web-font-loads) quickly, page should be [responsive](/fid/) and [layout jumps](https://css-tricks.com/content-jumping-avoid/) should be avoided. A user won't buy anything if they can't click the _Buy Now_ button due to busy CPU or a jumping or unlabeled button.
In general it's best to measure and track the time-to-action towards a conversion or a subgoal for it, for example the median time it takes shoppers to get from landing on your site, to viewing a product, to completing payment .

## Re-Engagement

It turns out that only 2% of users [convert on first visit](https://retargeter.com/what-is-retargeting-and-how-does-it-work/), so it is important to get the other 98% to come back, and re-engage them with your content. Modern websites have different ways to do this, for example via mail, tailored display ads in [remarketing](https://support.google.com/google-ads/answer/2453998?hl=en) or notifications. This works best if the flow from reengagement to website is as smooth as possible. Unfortunately this is not always the case, as for example mail apps often open links in their in-app webview, slowing down page load and complicating logins through different cache and cookie storage.
Make sure to optimize for fast repeat loads and smooth UX flows to increase chances of reengagement.

## Recap

E-commerce sites always strive for conversions, which are at the end of a purchase funnel. Every step along the funnel needs to be optimized for website speed to minimize bounce rates and drop-offs, and for each step there are different things to optimize, different pitfalls and culprits:

<figure>
  {% Img src="image/admin/tRFlwIEQJcf52vGBEoDp.png", alt="A conversion funnel going from discover to engage to convert to re-engage.", width="800", height="399" %}
  <figcaption>
    An e-commerce funnel showing which metric to optimize in which step.
  </figcaption>
</figure>

To learn more, be sure to check out the other posts in this series on [measuring
to improve performance](/what-should-you-measure-to-improve-performance/), and
[how report metrics to build a performance culture](/how-to-report-metrics/).

_Hero image by Campaign Creators on Unsplash_.

# How to measure speed?

Real-world performance is highly variable due to differences in users' devices, network connections, and other factors. For example, if you load your website using a cable network connection in your office and compare it against the load using WiFi in a coffee shop, the experiences are likely to be very different. There are many tools on the market that can help you collect lab or field data to assess page performance.

## Lab data vs Field data

{% Img src="image/admin/6OMEfvIKRuDWWSiVDto4.png", alt="Speed tools graphics", width="800", height="232" %}

**Lab data** is performance data collected within a controlled environment with predefined device and network settings, while **Field data** is performance data collected from real page loads experienced by your users in the wild. Each type has its own strengths and limitations.

**Lab data** offers reproducible results and a debugging environment, but might not capture real-world bottlenecks and cannot correlate against real-world page [KPIs](https://www.thinkwithgoogle.com/intl/en-145/future-of-marketing/creativity/kpis-essential-framework/). With lab data, you need to understand your users' typical devices and networks and appropriately mirror those conditions when you test performance. Have in mind that even in areas with 4G, users may still experience slower or intermittent connections when in elevators, while commuting, or in comparable environments.

**Field data** (also called Real User Monitoring or RUM) captures true real-world user experience and enables correlation to business KPIs, but has a restricted set of metrics and limited debugging capabilities.


## Tools

### Lab data

[Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) takes a URL and runs a series of audits against the page, generating a report on how well the page did. There are multiple ways to run Lighthouse, including an option to easily audit a page from within Chrome DevTools.

### Field data

[Chrome User Experience Report (CrUX)](https://developer.chrome.com/docs/crux/) provides metrics showing how real-world Chrome users experience popular destinations on the web.

### Other tools

[PageSpeed Insights](https://pagespeed.web.dev/) provides both lab and field data about a page. It uses Lighthouse to collect and analyze lab data about the page, while real-world field data is based on the Chrome User Experience Report dataset.

[Chrome Developer Tools](https://developer.chrome.com/docs/devtools/) is a set of web developer tools built directly into the Google Chrome browser. It allows you to profile the runtime of a page, as well as identify and debug performance bottlenecks.

# How to report metrics and build a performance culture

Website performance is a key goal towards e-commerce conversions, so it must be
a core priority for business stakeholders and leadership, not just development
teams.

For that reason, it's crucial to make performance metrics visible and tangible
for all stakeholders, and to build reporting and monitoring into your
workflow.

This guide describes what to be aware of and what to avoid to achieve
this successfully.

## A word about metrics

There are countless metrics to evaluate website performance, and while it may
seem useful to collect all of them, too many metrics can be confusing and
misleading. There are several ways to deal with this:

-   Gather multiple metrics and try to narrow and filter afterwards on
    what might be relevant for the task at hand.
-   Abstract metrics into an overall score, as for example
    [Lighthouse does](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring).
    This can be especially useful for non-technical staff and other
    stakeholders, but is probably insufficient for deeper technical analysis.
-   Try to find the one metric which is most relevant as a predictor for
    your conversions and then optimize towards this.

In reality, a pragmatic combination makes the most sense here. Gather more rather
than less to begin with, report an abstract score to management, and optimize
towards the one metric which best predicts your conversions.

Finding this metric can be done by using your analytics tool of choice
to map performance metrics to user engagement, conversions and transaction values.
For example, a custom report to do so looks like this in Google Analytics:

<figure>
  {% Img src="image/admin/YI31t9fE82uEhrrYKTVg.png", alt="A Google Analytics dashboard shows a number of fields being added to a custom report.", width="800", height="430" %}
  <figcaption>
    Fig 1: Custom report in Google Analytics to analyze the impact of speed on conversions and engagement.
  </figcaption>
</figure>

{% Aside 'caution' %}
  **Metrics can be deceiving.**
  Unfortunately metrics can sometimes be misleading with respect to performance.
  Keep reading for specifics.
{% endAside %}

## Increasing bounce rate

### Problem

Often it is assumed that bounce rate will drop when page load speed
increases. While this normally does hold true, measurements sometimes show the
opposite. This is because analytics can only measure a bounce after the
analytics library is loaded. A faster page load means analytics code also loads
faster, so analytics may see more bounces even if there aren't more happening.

### Solution

This can be eased by measuring
[real page abandonment](/user-centric-performance-metrics/) instead.

## Decreasing relative conversions

### Problem

Relative conversions may sometimes seem to drop for faster sites.
This is because faster pages reach a bigger audience who might be less engaged
or committed. While incremental traffic and conversions increase with faster
pages, relative conversions (the ratio of conversions to page views or visitors)
might still drop.

### Solution

This effect can be eased by looking out for absolute conversions instead, and
even calculate Cost Per Sales (conversions divided with investment level) or ROI.

## Dropping engagement

### Problem

Page engagement may seem to drop for a faster page.

### Solution

This can actually be a positive signal! If the page is faster
users can reach their goal more quickly and might just have shorter sessions and
less time on page.

## Using averages or medians

### Problem

In general, it can be deceiving to look at averages and medians of
performance metrics (see this nicely described in
_[Everything You Know About Latency Is Wrong](https://bravenewgeek.com/everything-you-know-about-latency-is-wrong/))_.
Similar to a chain being just as strong as the weakest link, the performance of
a funnel is only as good as its slowest load. A single slow load may be enough
to lose the user. Therefore averages and medians are more likely to hide the
real performance issues, than to reveal them.

### Solution

It's best to analyze complete distributions, but as this is not
always easily possible we recommend using 90th percentiles—this is the value
for which 90% of loads were faster. Even then a user hitting ten pages will
still have one load slower than this, and might drop out there.

While performance measurement is highly important, make sure to keep an open
mind and question unexpected results—and make sure to not report misleading
numbers to stakeholders and management. If unsure on what to pick and report,
we'd advise as a minimum for 90th percentile
[First Contentful Paint](/fcp/), which is also what we use
across our public tooling.

## Third party content

Website performance is especially prone to being dragged down
by third party content
(see [Eliminate render-blocking resources](https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources/)).
This is a particular problem for e-commerce, often due to trackers and widgets.

Some ways to handle third party content with respect to performance:

-   Always keep third party content out of the
    [critical rendering path](/critical-rendering-path/).
    If the third party has server problems and times out, it will impact your
    website heavily. You can test and simulate this via
    [WebPageTest Single-Point-of-Failure](https://css-tricks.com/use-webpagetest-api/#single-point-of-failure)
    tests.
-   Continually measure and report the ratio of third party content, for
    example via WebPageTest [domain
    breakdown](https://www.webpagetest.org/domains.php). Make sure to use
    performance budgets for third party content.
-   If you suspect that a particular third party component is having a
    detrimental effect on performance, do a performance comparison with it
    included and excluded.
    [Find out how](https://andydavies.me/blog/2018/02/19/using-webpagetest-to-measure-the-impact-of-3rd-party-tags/).
-   Try to stay on one stack from one vendor if possible. For example, if
    you have a tag manager and analytics on one stack, you may only need a
    single script, and may be able to
    [take advantage of HTTP2 synergies](/performance-http2/)
     as there is only one host involved.
-   Make sure not to use the same functionality from two different vendors.
    You shouldn't need two tag managers or two analytics platforms.
-   Routinely audit and clean out redundant third party scripts, trackers
    and widgets. This can easily be done via [Ghostery Extension](https://chrome.google.com/webstore/detail/ghostery-%E2%80%93-privacy-ad-blo/mlomiejdfkolichcflejclcbmpeaniij?hl=en) or tools like [WhatRuns](https://chrome.google.com/webstore/detail/whatruns/cmkdbmfndkfgebldhnkbfhlneefdaaip?hl=en):

<figure>
  {% Img src="image/admin/w2xb4mUJ7kDpt254fq0k.png", alt="A Ghostery report showing all loaded trackers.", width="800", height="710" %}
  <figcaption>
    A Ghostery report showing all loaded trackers
  </figcaption>
</figure>

Learn more in
[Loading Third-Party JavaScript](/optimizing-content-efficiency-loading-third-party-javascript/).

## Performance Culture

Unfortunately performance is often seen as a one-off optimization task, and then
regresses over time as stakeholders raise new feature requests or insist on
adding new trackers and widgets.

Performance must be a continuous goal to improve acquisition, discovery, and
conversion rates as well as safeguarding the reputation of your brand. This can
be achieved with [performance
budgets](/performance-budgets-101)
like [Tinder did](https://medium.com/@addyosmani/a-tinder-progressive-web-app-performance-case-study-78919d98ece0),
and by establishing and fostering a performance culture, where all employees and
especially decision makers recognize speed as a core feature of the website.

## Make metrics tangible

Reports on metrics are often abstract and easy to challenge or dismiss. It's
best to make your performance tangible and visible. There are several good ways
to do this:

-   [Facebook](https://www.theverge.com/2015/10/28/9625062/facebook-2g-tuesdays-slow-internet-developing-world)
    and Google do this by providing slow networks across the company for testing.
-   Make average, low-spec devices with low
    [bandwidth or high latencies](/performance-poor-connectivity/) available
    to management and other stakeholders.
-   Consider adding overlays showing performance metrics on your development
    or staging servers. Connections to these servers from mobile can
    potentially be throttled by default on corporate networks.
-   Monitors can be placed strategically across the company showing videos
    or timestrips of your website's loading behavior, preferably in comparison
    to competitors. WebPageTest [can create
    these](https://www.webpagetest.org/video/) very easily and automatically.
-   Performance can also be fun—maybe a [game based on an individual Lighthouse report](https://g.co/perfgame) can reach audiences which can't be reached by pure reports and metrics?

## Recap

This guide explains why the selection of metrics, and how they are reported and handled, is as important as the measurement and optimization itself—or even more. Make sure to prefer percentiles or distributions over averages, be cautious of using just bounce rate or relative conversion rate as impact measures, and make sure that metrics are easy to understand and tangible for stakeholders across the company. Establishing a performance culture is also an important step towards a well performing e-commerce site.

# How to stay fast?

Brands that optimize speed will often find they regress quickly. This is because website performance is a lot like getting fit: it's not enough to make a one time effort; you have to change your lifestyle.

{% Aside 'note' %}
  [Internal Google study](https://www.youtube.com/watch?v=YJGCZCaIZkQ) has found that 40% of brands regress on web performance after 6 months.
{% endAside %}

Performance budgets are one way to address this. A performance budget is a set of limits on metrics that affect site performance. The concept is similar to a financial budget: you set a limit and make sure you stay within it. In general, a good performance budget combines different types of metrics; so, for example, the performance budget for a product page might look as follows:

<div class="table-wrapper">
  <table>
    <tbody>
      <tr>
        <td>Time To Interactive on slow 3G</td>
        <td>Less than</td>
        <td>5 seconds</td>
      </tr>
      <tr>
        <td>First Contentful Paint on slow 3G</td>
        <td>Less than</td>
        <td>2 seconds</td>
      </tr>
      <tr>
        <td>Lighthouse performance score</td>
        <td>Greater than</td>
        <td>80</td>
      </tr>
      <tr>
        <td>Total JavaScript size</td>
        <td>Less than</td>
        <td>170 kb</td>
      </tr>
    </tbody>
    <caption>Example performance budget.</caption>
  </table>
</div>

Once set, a performance budget has to be enforced, which means for example incorporating the budget into your build process and reporting. Tools like Lighthouse can be included in your continuous integration, and you can write tests that fail a build if key metrics drop below a set threshold. Additionally, regular reporting through dashboards or summary reports can help with visibility and accountability. [Pinterest](https://www.youtube.com/watch?v=Xryhxi45Q5M) are one example of a business that have implemented performance budgets to make sure their fast experience stays fast, while brands like [Experian](https://www.thinkwithgoogle.com/intl/en-gb/success-stories/uk-success-stories/how-mobile-first-mindset-helped-experian-slash-page-load-times/) are now using site speed as a key metric in their monthly executive KPI reports.

You can find more details on performance budgets [here](/performance-budgets-101). A guide that describes how to further instill performance culture and make speed metrics visible and tangible for all stakeholders can be found [here](/how-to-report-metrics/).

# Identify slow third-party JavaScript

As a developer, you often don't have control over [which third-party scripts](/third-party-javascript/#network) your site loads. Before you can optimize third-party content you have to do some detective work to find out what's making your site slow. 🕵️

In this post, you'll learn how to use [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) and [Chrome DevTools](https://developer.chrome.com/docs/devtools/) to identify slow third-party resources. The post walks through increasingly robust techniques which are best used in combination.

## If you only have 5 minutes

The Lighthouse [Performance audit](https://developer.chrome.com/docs/lighthouse/performance/) helps you discover opportunities to speed up page loads. Slow third-party scripts are likely to appear in the **Diagnostics** section under the **Reduce JavaScript execution time** and **Avoid enormous network payloads** audits.

To run an audit:

{% Instruction 'devtools-lighthouse', 'ol' %}
1. Click **Mobile**.
1. Select the **Performance** checkbox. (You can clear the rest of the checkboxes in the Audits section.)
1. Click **Simulated Fast 3G, 4x CPU Slowdown**.
1. Select the **Clear Storage** checkbox.
1. Click **Run audits**.

{% Img src="image/admin/XLNFxdEOc7739bcIwERq.png", alt="Screenshot of the Chrome DevTools Audits panel.", width="800", height="1068" %}

### Third-party usage

The Lighthouse **Third-party usage** audit shows a list of the third-party providers a page uses. This overview can help you better understand the big picture and identify redundant third-party code. The audit is available in the [Lighthouse extension](https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk?hl=en) and will soon be added to DevTools in Chrome 77.

<figure>
  {% Img src="image/admin/4JXHK0FkgJIfKED16BnF.png", alt="Screenshot showing that 51 third-parties were found and a list of imaginary startups.", width="728", height="646" %}
  <figcaption>
  Third-party provider names generated with <a href="http://tiffzhang.com/startup/?s=641553836036">Startup generator</a>. Any similarity to actual startups, living or dead, is purely coincidental.
  </figcaption>
</figure>

### Reduce JavaScript execution time

The Lighthouse [Reduce JavaScript execution time](https://developer.chrome.com/docs/lighthouse/performance/bootup-time/) audit highlights scripts that take a long time to parse, compile, or evaluate. Select the **Show 3rd-party resources** checkbox to discover CPU-intensive third-party scripts.

{% Img src="image/admin/O7vN1En6dtbL3Q8TbufC.png", alt="Screenshot showing that the 'Show third-party resources' checkbox is checked.", width="800", height="981" %}

### Avoid enormous network payloads

The Lighthouse [Avoid enormous network payloads](https://developer.chrome.com/docs/lighthouse/performance/total-byte-weight/) audit identifies network requests—including those from third-parties—that may slow down page load time. The audit fails when your network payload exceeds 4,000 KB.

{% Img src="image/admin/9Pnoz73MLeNzooUQLuam.png", alt="Screenshot of the Chrome DevTools 'Avoid enormous network payloads' audit.", width="799", height="631" %}

## Block network requests in Chrome DevTools

Chrome DevTools [network request blocking](https://developer.chrome.com/docs/devtools/network/#block) allows you to see how your page behaves when a particular script, stylesheet, or other resource isn't available. After you identify third-party scripts that you suspect affect performance, measure how your load time changes by blocking the requests to those scripts.

To enable request blocking:
{% Instruction 'devtools-network', 'ol' %}
1. Right-click any request in the **Network** panel.
1. Select **Block request URL**.

{% Img src="image/admin/UbedvjrtP9si1l0X2QVA.png", alt="A screenshot of the context menu in the Chrome DevTools Performance panel. The 'Block request URL' option is highlighted.", width="800", height="529" %}

A **Request blocking** tab will appear in the DevTools drawer. You can manage which requests have been blocked there.

To measure the impact of third-party scripts:
1. Measure how long your page takes to load using the **Network** panel. To emulate real-world conditions, turn on [network throttling](https://developer.chrome.com/docs/devtools/network/#throttle) and [CPU throttling](https://developer.chrome.com/blog/new-in-devtools-61/#throttling). (On faster connections and desktop hardware, the impact of expensive scripts may not be as representative as it would be on a mobile phone.)
1. Block the URLs or domains responsible for third-party scripts you believe are an issue.
1. Reload the page and re-measure how long it takes to load without the blocked third-party scripts.

You should hopefully see a speed improvement, but occasionally blocking third-party scripts might not have the effect you expect. If that's the case, reduce the list of blocked URLs until you isolate the one that's causing slowness.

Note that doing three or more runs of measurement and looking at the median values will likely produce more stable results. As third-party content can occasionally pull in different resources per page load, this approach can give you a more realistic estimate. [DevTools now supports multiple recordings](https://twitter.com/ChromeDevTools/status/963820146388221952) in the **Performance** panel, making this a little easier.

# Use image CDNs to optimize images

Image content delivery networks (CDNs) are excellent at optimizing images. Switching to an image CDN can yield a [40–80% savings](https://www.youtube.com/watch?v=YJGCZCaIZkQ&t=1010s) in image file size. In theory, it's possible to achieve the same results using only build scripts, but it's rare in practice.

## What's an image CDN?

Image CDNs specialize in the transformation, optimization, and delivery of images. You can also think of them as APIs for accessing and manipulating the images used on your site. For images loaded from an image CDN, an image URL indicates not only which image to load, but also parameters like size, format, and quality. This makes it easy to create variations of an image for different use cases.

<figure>
  {% Img src="image/admin/OIF2VcXp8P6O7tQvw53B.jpg", alt="Shows the request/response flow between the image CDN and the client. Parameters like size and format are used to request variations of the same image.", width="800", height="408" %}
  <figcaption>
    Examples of transformations image CDNs can perform based on parameters in image URLs.
  </figcaption>
</figure>

Image CDNs are different from build-time image optimization scripts in that they create new versions of images as they're needed. As a result, CDNs are generally better suited to creating images that are heavily customized for each individual client than build scripts are.

## How image CDNs use URLs to indicate optimization options

Image URLs used by image CDNs convey important information about an image and the transformations and optimizations that should be applied to it. URL formats will vary depending on image CDN, but at a high-level, they all have similar features. Let's walk through some of the most common features.

<figure>
  {% Img src="image/admin/GA4udXeYUEjHSY4N0Qew.jpg", alt="Image URLs typically consist of the following components: origin, image, security key, and transformations.", width="800", height="127" %}
</figure>

### Origin

An image CDN can live on your own domain or the domain of your image CDN. Third-party image CDNs typically offer the option of using a custom domain for a fee. Using your own domain makes it easier to switch image CDNs at a later date because no URL changes will be needed.

The example above uses the image CDN's domain ("example-cdn.com") with a personalized subdomain, rather than a custom domain.

### Image

Image CDNs can usually be configured to automatically retrieve images from their existing locations when they're needed. This capability is often achieved by including the complete URL of the _existing image_ within the URL for the image generated by the image CDN. For example, you might see a URL that looks like this: `https://my-site.example-cdn.com/https://flowers.com/daisy.jpg/quality=auto`. This URL would retrieve and optimize the image that exists at `https://flowers.com/daisy.jpg`.

{% Aside %}
As the above example shows, the file exension requested (`.jpg` is this case) may not be the same as the image file format returned (WebP in this example). The `content-type` HTTP Header will inform the browser which format the URL is in so it can process it appropriately. However, this may cause some confusion if the file is saved to disk and used by another program that expects the file format to match the file extension.
{% endAside %}

Another widely supported way of uploading images to an image CDN is to send them via an HTTP POST request to the image CDN's API.

### Security key

A security key prevents other people from creating new versions of your images. If this feature is enabled, each new version of an image requires a unique security key. If someone tries to change the parameters of the image URL but doesn't provide a valid security key, they won't be able to create a new version. Your image CDN will take care of the details of generating and tracking security keys for you.

### Transformations

Image CDNs offer tens, and in some cases hundreds, of different image transformations. These transformations are specified via the URL string, and there are no restrictions on using multiple transformations at the same time. In the context of web performance, the most important image transformations are size, pixel density, format, and compression. These transformations are the reason why switching to an image CDN typically results in a significant reduction in image size.

There tends to be an objectively best setting for performance transformations, so some image CDNs support an "auto" mode for these transformations. For example, instead of specifying that images be transformed to the WebP format, you could allow the CDN to automatically select and serve the optimal format. Signals that an image CDN can use to determine the best way to transform an image include:

* [Client hints](https://developer.chrome.com/blog/automating-resource-selection-with-client-hints/) (for example, viewport width, DPR, and image width)
* The [`Save-Data`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Save-Data) header
* The [User-Agent](https://developer.mozilla.org/docs/Web/HTTP/Headers/User-Agent) request header
* The [Network Information API](https://developer.mozilla.org/docs/Web/API/Network_Information_API)

For example, the image CDN might serve AVIF to a Chrome browser, WebP to an Edge browser, and JPEG to a very old browser. Auto settings are popular because they allow you to take advantage of image CDNs' significant expertise in optimizing images without the need for code changes to adopt new technologies once they're supported by the image CDN.

## Types of Image CDNs

Image CDNs can be broken down into two categories: self-managed and third-party-managed.

### Self-managed image CDNs

Self-managed CDNs can be a good choice for sites with engineering staff who are comfortable maintaining their own infrastructure.

- [Thumbor](https://github.com/thumbor/thumbor) is the most popular self-managed image CDN. While it is open-source and free to use, it generally has fewer features than most commercial CDNs, and its documentation is somewhat limited. [Wikipedia](https://wikitech.wikimedia.org/wiki/Thumbor), [Square](https://medium.com/square-corner-blog/dynamic-images-with-thumbor-a430a1cfcd87), and [99designs](https://99designs.com/tech-blog/blog/2013/07/01/thumbnailing-with-thumbor/) are three sites that use Thumbor. See the [How to install the Thumbor image CDN](/install-thumbor) post for instructions on setting it up.
- [Imaginary](https://github.com/h2non/imaginary)
- [Imagor](https://github.com/cshum/imagor)

### Third-party image CDNs

Third-party image CDNs provide image CDNs as a service. Just as cloud providers provide servers and other infrastructure for a fee; image CDNs provide image optimization and delivery for a fee. Because third-party image CDNs maintain the underlying technology, getting started is fairly simple and can usually be accomplished in 10-15 minutes, although a complete migration for a large site might take far longer. Third-party image CDNs are typically priced based on usage tiers, with most image CDNs providing either a free tier or a free trial to give you an opportunity to try out their product.

## Choosing an image CDN

There are many good options for image CDNs. Some will have more features than others, but all of them will probably help you save bytes on your images and therefore load your pages faster. Besides feature sets, other factors to consider when choosing an image CDN are cost, support, documentation, and ease of setup or migration.

Trying them out yourself before making a decision can also be helpful. Below you can find codelabs with instructions on how to quickly get started with several image CDNs.

## Effects on Largest Contentful Paint (LCP)

Images are a vital part of the user experience on many websites, and thus factor into how well sites do when it comes to [Largest Contentful Paint](/lcp/). Here are a few things to keep in mind if you decide to use an image CDN:

1. Images served from CDNs may come from a cross-origin server, which involves extra connection setup time. When possible, try to use an image CDN that proxies through the primary origin so that you're not adding extra origins for the browser to connect to. This has the same effect as self-hosting images on the primary origin.
2. Consider using a [`fetchpriority` attribute value of `"high"`](/fetch-priority/#summary) on the LCP image element so that the browser can begin loading that image as soon as possible.
3. If an image is not immediately discoverable in the initial HTML, consider using a [`rel=preload`](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preload/) hint for your LCP candidate image so that the browser can load that image ahead of time.
4. If proxying through your origin is not possible, and the exact image to be loaded will not be known until later during page load, [you should set up a connection to the cross-origin image CDN as early as possible](/preconnect-and-dns-prefetch/) to shorten the resource loading phase of what could be your page's LCP candidate image.

Image CDNs are indispensable tools that eliminate the toil of manually optimizing images, and should be considered. As always, though, there are trade-offs to consider, and keeping an eye on your website's LCP candidate images and adding hints as appropriate can mitigate any added latency to those key requests.

# Incorporate performance budgets into your build process

Once you've defined a performance budget, it's time to set up the build process
to keep track of it. There are a number of tools that let you define thresholds
for chosen performance metrics and warn you if you go over budget. Find out
how to choose one that best fits your needs and current setup. 🕵️‍♀️


## Lighthouse performance budgets

[Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) is an auditing tool that tests sites in a few key areas—performance, accessibility, best practices and how well your site performs as a progressive web application.

The [command line version](https://developer.chrome.com/docs/lighthouse/overview/#cli) of Lighthouse (v5+) supports setting [performance budgets](/use-lighthouse-for-performance-budgets/) based on:

- resource size
- resource count

You can set budgets for any of the following resource types:
- `document`
- `font`
- `image`
- `media`
- `other`
- `script`
- `stylesheet`
- `third-party`
- `total`

Budgets are set in a JSON file and after defining them the new "Over Budget" column tells you whether you're exceeding any limits.

<figure>
  {% Img src="image/admin/fsRNXJXQ5JESUuwsIlLn.png", alt="Budgets section in Lighthouse report", width="800", height="199" %}
  <figcaption>
    "Budgets" section in Lighthouse report
  </figcaption>
</figure>

## Webpack performance hints

[Webpack](/webpack/) is a powerful build tool for optimizing how your code is delivered to the users. It also supports setting performance budgets based on **asset size**.

Turn on [performance hints](https://webpack.js.org/configuration/performance/) in `webpack.config.js` to get command line warnings or errors when your bundle size grows over the limit. It's a great way to stay mindful about asset sizes throughout the development.

After the build step, webpack outputs a color-coded list of assets and their sizes. Anything over budget is highlighted in yellow.

<figure>
  {% Img src="image/admin/G96Z2pZVL6aowVRa49rA.png", alt="Webpack output highlighting bundle.js", width="617", height="86" %}
  <figcaption>
    The highlighted bundle.js is bigger than your budget
  </figcaption>
</figure>

The default limit for both assets and entry-points is **250 KB**. You can set your own targets in the configuration file.

<figure>
  {% Img src="image/admin/StfxwSScNVzmeM70gwp4.jpg", alt="Webpack bundle size warning", width="566", height="104" %}
  <figcaption>
    Webpack bundle size warning ⚠️
  </figcaption>
</figure>

The budgets are compared against **uncompressed asset sizes**. Uncompressed [JavaScript size is related to the execution time](https://v8.dev/blog/cost-of-javascript-2019) and big files can take a long time to execute, especially on mobile devices.

{% Aside %}
Compressed asset sizes affect the transfer time, which is very important on slow networks.
{% endAside %}

<figure>
  {% Img src="image/admin/ogH1cMiF96iyhj8CiENK.jpg", alt="Webpack performance optimization recommendation", width="556", height="84" %}
  <figcaption>
    Bonus feature: webpack won’t only warn you, it will give you a recommendation on how to downsize your bundles. 💁
  </figcaption>
</figure>

## Bundlesize

[Bundlesize](https://github.com/siddharthkp/bundlesize) is a simple npm package that tests asset size against a threshold you've set. It can run locally and integrate with your CI.

### Bundlesize CLI

Run [bundlesize CLI](https://github.com/siddharthkp/bundlesize#cli) by specifying a threshold and the file that you want to test.

```bash
bundlesize -f "dist/bundle.js" -s 170kB
```

Bundlesize outputs color-coded test results in one line.

<figure>
  {% Img src="image/admin/6YsTnhpUlzbTB1ja23yP.png", alt="Failing bundlesize CLI test", width="473", height="36" %}
  <figcaption>
    Failing bundlesize CLI test ❌
  </figcaption>
</figure>

<figure>
  {% Img src="image/admin/TjynyaIzZlRa6xE2uVcD.png", alt="Passing bundlesize CLI test", width="460", height="36" %}
  <figcaption>
    Passing bundlesize CLI test ✔️
  </figcaption>
</figure>

### Bundlesize for CI

You'll get the most value out of bundlesize if you integrate it with a CI to automatically enforce size limits on pull requests. **If bundlesize test fails, that pull request is not merged.** It works for pull requests on GitHub with [Travis CI](https://travis-ci.org/), [CircleCI](https://circleci.com/), [Wercker](http://www.wercker.com/), and [Drone](http://readme.drone.io/).

<figure>
  {% Img src="image/admin/RKpNwfcXRoKZ34qDoxcO.jpg", alt="Bundlesize check status on GitHub", width="800", height="326", class="screenshot" %}
  <figcaption>
    Bundlesize check status on GitHub
  </figcaption>
</figure>

You may have a fast app today, but adding new code can often change this. Checking pull requests with bundlesize will help you avoid performance regressions. Bootstrap, Tinder, Trivago and many others use it to keep their budgets in check.

With bundlesize, it's possible to set thresholds for each file separately. This is especially useful if you are splitting a bundle in your application.

By default, **it tests gzipped asset sizes**. You can use the compression option to switch to [brotli compression](https://css-tricks.com/brotli-static-compression/) or turn it off completely.

## Lighthouse Bot

<figure>
  {% Img src="image/admin/5zLlIEUm06wEbu3J1WjG.png", alt="Lighthouse Bot", width="800", height="267" %}
  <figcaption>
  </figcaption>
</figure>

[Lighthouse Bot](https://github.com/ebidel/lighthouse-ci) integrates with Travis CI and enforces budgets based on any of the five Lighthouse audit categories. For example, a budget of 100 for your Lighthouse performance score. It's sometimes simpler to keep an eye on a single number than individual asset budgets and Lighthouse scores take many things into account.

<figure>
  {% Img src="image/admin/6yVLSYcr0eCytJixZ1bK.png", alt="Lighthouse scores 💯", width="800", height="211", class="screenshot" %}
  <figcaption>
    Lighthouse scores 💯
  </figcaption>
</figure>

Lighthouse Bot runs an audit after you deploy a site to staging server. In `.travis.yml` set budgets for particular Lighthouse categories with `--perf`, `--a11y`, `--bp`, `--seo` or `--pwa` options. Aim to stay in the green zone with scores of at least 90.

<pre class="prettyprint">
after_success:
  - ./deploy.sh # Deploy the PR changes to staging server
  - npm run lh -- --perf=96 https://staging.example.com # Run Lighthouse test
</pre>

If the scores for a pull request on GitHub fall below the threshold you've set, **Lighthouse Bot can prevent pull request from being merged**. ⛔

<figure>
  {% Img src="image/admin/nVDqJAA1DZDFqpOaQA9J.png", alt="Lighthouse Bot check status on GitHub", width="800", height="175", class="screenshot" %}
  <figcaption>
    Lighthouse Bot check status on GitHub
  </figcaption>
</figure>

**Lighthouse Bot** then comments on your pull request with updated scores. This is a neat feature which encourages conversation about performance as code changes are happening.

<figure>
  {% Img src="image/admin/q1BUp838y2oSs70Suxyb.png", alt="Lighthouse reporting scores on pull request", width="800", height="383" %}
  <figcaption>
    Lighthouse reporting scores on pull request 💬
  </figcaption>
</figure>

If you find your pull request blocked by a poor Lighthouse score, run an audit with [Lighthouse CLI](https://developer.chrome.com/docs/lighthouse/overview/#cli) or in [Dev Tools](https://developer.chrome.com/docs/lighthouse/overview/#devtools). It generates a report with details about bottlenecks and hints for simple optimizations.

## Summary

<div class="table-wrapper">
  <table>
    <tr>
      <th>Tool</th>
      <th>CLI</th>
      <th>CI</th>
      <th>Summary</th>
    </tr>
    <tr>
      <td>Lighthouse</td>
      <td>✔️</td>
      <td>❌</td>
      <td>
        <ul>
          <li>Budgets for different types of resources based on their size or count.</li>
        </ul>
      </td>
    </tr>
    <tr>
      <td>webpack</td>
      <td>✔️</td>
      <td>❌</td>
      <td>
        <ul>
          <li>Budgets based on sizes of assets generated by webpack.</li>
          <li>Checks uncompressed sizes.</li>
        </ul>
      </td>
    </tr>
    <tr>
      <td>bundlesize</td>
      <td>✔️</td>
      <td>✔️</td>
      <td>
        <ul>
          <li>Budgets based on sizes of specific resources.</li>
          <li>Checks compressed or uncompressed sizes.</li>
        </ul>
      </td>
    </tr>
    <tr>
      <td>Lighthouse Bot</td>
      <td>❌</td>
      <td>✔️</td>
      <td>
        <ul>
          <li>Budgets based on Lighthouse scores.</li>
        </ul>
      </td>
    </tr>
  </table>
</div>

# Use lazy loading to improve loading speed

The portion of
[images](http://beta.httparchive.org/reports/state-of-images?start=earliest&end=latest)
and [video](http://beta.httparchive.org/reports/page-weight#bytesVideo) in the
typical payload of a website can be significant.
Unfortunately, project stakeholders may be unwilling to cut any media resources from their existing
applications.
Such impasses are frustrating,
especially when all parties involved want to improve site performance,
but can't agree on how to get there.
Fortunately, lazy loading is a solution that lowers initial page payload _and_
load time, but doesn't skimp on content.

## What is lazy loading? {: #what }

Lazy loading is a technique that defers loading of non-critical resources at page
load time. Instead, these non-critical resources are loaded at the moment of
need. Where images are concerned, "non-critical" is often synonymous with
"off-screen". If you've used Lighthouse and examined some opportunities for
improvement, you may have seen some guidance in this realm in the form of the
[Defer offscreen images audit](https://developer.chrome.com/docs/lighthouse/performance/offscreen-images/):

<figure>
  {% Img src="image/admin/63NnMISWUUWD3mvAliwe.png", alt="A screenshot of the Defer offscreen images audit in Lighthouse.", width="800", height="102" %}
  <figcaption>One of Lighthouse's performance audits is to
identify off screen images, which are candidates for lazy loading.</figcaption>
</figure>

You've probably already seen lazy loading in action, and it goes something like
this:

- You arrive at a page, and begin to scroll as you read content.
- At some point, you scroll a placeholder image into the viewport.
- The placeholder image is suddenly replaced by the final image.

An example of image lazy loading can be found on the popular publishing platform
[Medium](https://medium.com/), which loads lightweight placeholder images at
page load, and replaces them with lazily-loaded images as they're scrolled into
the viewport.

<figure>
  {% Img src="image/admin/p5ahQ67QtZ20bgto7Kpy.jpg", alt="A screenshot of the website Medium in the browsing, demonstrating lazy loading in action. The blurry placeholder is on the left, and the loaded resource is on the right.", width="800", height="493" %}
  <figcaption>An example of image lazy loading in action. A
placeholder image is loaded at page load (left), and when scrolled into the
viewport, the final image loads at the time of need.</figcaption>
</figure>

If you're unfamiliar with lazy loading, you might be wondering just how useful
the technique is, and what its benefits are. Read on to find out!

## Why lazy-load images or video instead of just _loading_ them? {: #why }

Because it's possible you're loading stuff the user may never see. This is
problematic for a couple reasons:

- It wastes data. On unmetered connections, this isn't the worst thing that could
happen (although you could be using that precious bandwidth for downloading
other resources that are indeed going to be seen by the user). On limited data
plans, however, loading stuff the user never sees could effectively be a waste
of their money.
- It wastes processing time, battery, and other system resources. After a media
resource is downloaded, the browser must decode it and render its content in the
viewport.

Lazy loading images and video reduces initial page load time, initial
page weight, and system resource usage, all of which have positive impacts on
performance.

## Implementing lazy loading {: #implementing }

There are a number of ways to implement lazy loading.
Your choice of solution must take into account the browsers you support,
and also what you are trying to lazy-load.

Modern browsers implement [browser-level lazy loading](/browser-level-image-lazy-loading/),
which can be enabled using the `loading` attribute on images and iframes.
To provide compatibility with older browsers
or to perform lazy loading on elements without built-in lazy loading
you can implement a solution with your own JavaScript.
There are also a number of existing libraries to help you to do this.
See the posts on this site for full details of all of these approaches:

- [Lazy loading images](/lazy-loading-images/)
- [Lazy loading video](/lazy-loading-video/)

Also, we have compiled a list of [potential issues with lazy loading](/lazy-loading-best-practices),
and things to watch out for in your implementation.

## Conclusion

Used with care, lazy loading images and video can seriously lower the initial
load time and page payloads on your site, including [Core Web Vitals](/vitals/). Users won't incur unnecessary network
activity&mdash;including network contention on slower connections&mdash;and processing costs of media resources they may never see, but they
can still view those resources if they want.

As far as performance improvement techniques go, lazy loading is reasonably
uncontroversial. If you have a lot of inline imagery in your site, it's a
perfectly fine way to cut down on unnecessary downloads. Your site's users and
project stakeholders will appreciate it!

# Lazy loading best practices

While lazy loading images and video have positive and measurable performance
benefits, it's not a task to be taken lightly. If you get it wrong, there could
be unintended consequences. As such, it's important to keep the following
concerns in mind.

## Mind the fold {: #wrong-fold }

It may be tempting to lazy-load every single media resource on the page with
JavaScript, but you need to resist this temptation. Anything resting above the
fold shouldn't be lazy-loaded. Such resources should be considered critical
assets, and thus should be loaded normally.

Lazy loading delays the loading of resources until after the DOM is interactive
when scripts have finished loading and begin execution. For images below the
fold, this is fine, but critical resources above the fold should be loaded with
the standard `<img>` element so they're displayed as soon as possible.

Of course, where the fold lies is not so clear these days when websites are
viewed on so many screens of varying sizes. What lies above the fold on a laptop
may well lie _below_ it on mobile devices. There's no bulletproof advice for
addressing this optimally in every situation. You'll need to conduct an
inventory of your page's critical assets, and load those images in typical
fashion.

Additionally, you may not want to be so strict about the fold line as the
threshold for triggering lazy loading. It may be more ideal for your purposes to
establish a buffer zone some distance below the fold so that images begin
loading well before the user scrolls them into the viewport. For example, the
Intersection Observer API allows you to specify a `rootMargin` property in an
options object when you create a new `IntersectionObserver` instance. This
effectively gives elements a buffer, which triggers lazy loading behavior before
the element is in the viewport:

```javascript
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
  // lazy-loading image code goes here
}, {
  rootMargin: "0px 0px 256px 0px"
});
```

If the value for `rootMargin` looks similar to values you'd specify for a CSS
`margin` property, that's because it is! In this case, the
bottom margin of the observed element (the browser viewport by default, but
this can be changed to a specific element using the `root` property) is broadened by 256
pixels. That means the callback function will execute when an image element is
within 256 pixels of the viewport and the image will begin to load
before the user actually sees it.

To achieve this same effect in browsers that don't support Intersection Observe, use scroll event handling code and adjust your
`getBoundingClientRect` check to include a buffer.

## Layout shifting and placeholders {: #wrong-layout-shifting }

Lazy loading media can cause [shifting in the layout](/cls) if placeholders aren't used.
These changes can be disorienting for users and trigger expensive DOM layout
operations that consume system resources and contribute to jank. At a minimum,
consider using a solid color placeholder occupying the same dimensions as the
target image, or techniques such as
[LQIP](http://www.guypo.com/introducing-lqip-low-quality-image-placeholders) or
[SQIP](https://github.com/technopagan/sqip) that hint at the content of a media
item before it loads.

For `<img>` tags, `src` should initially point to a placeholder until that
attribute is updated with the final image URL. Use the `poster` attribute in a
`<video>` element to point to a placeholder image. Additionally, use `width` and
`height` attributes on both `<img>` and `<video>` tags. This ensures that
transitioning from placeholders to final images won't change the rendered size
of the element as media loads.

## Image decoding delays {: #wrong-decoding-delays }

Loading large images in JavaScript and dropping them into the DOM can tie up the
main thread, causing the user interface to be unresponsive for a short period of
time while decoding occurs. [Asynchronously decoding images using the `decode`
method](https://medium.com/dailyjs/image-loading-with-image-decode-b03652e7d2d2)
prior to inserting them into the DOM can cut down on this sort of jank, but
beware: It's not available everywhere yet, and it adds complexity to lazy loading logic.
If you want to use it, you'll need to check for it. Below shows
how you might use `Image.decode()` with a fallback:

```javascript
var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

if ("decode" in newImage) {
  // Fancy decoding logic
  newImage.decode().then(function() {
    imageContainer.appendChild(newImage);
  });
} else {
  // Regular image load
  imageContainer.appendChild(newImage);
}
```

Check out [this CodePen link](https://codepen.io/malchata/pen/WzeZGW) to see
code similar to this example in action. If most of your images are fairly small,
this may not do much for you, but it can certainly help cut down on jank when
lazy loading large images and inserting them into the DOM.

## When stuff doesn't load {: #wrong-loading-failure }

Sometimes media resources fail to load for one reason or another and errors
occur. When might this happen? It depends, but here's one hypothetical scenario
for you: You have an HTML caching policy for a short period of time (e.g., five
minutes), and the user visits the site _or_ a user has a left a stale tab open for
a long period of time (e.g., several hours) and comes back to read your content.
At some point in this process, a redeployment occurs. During this deployment, an
image resource's name changes due to hash-based versioning, or is removed
altogether. By the time the user lazy-loads the image, the resource is
unavailable, and thus fails.

While these are relatively rare occurrences, it may behoove you to have a backup
plan if lazy loading fails. For images, such a solution may look something like
this:

```javascript
var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

newImage.onerror = function(){
  // Decide what to do on error
};
newImage.onload = function(){
  // Load the image
};
```

What you decide to do in the event of an error depends on your application. For
example, you could replace the image placeholder area with a button that allows
the user to attempt to load the image again, or simply display an error message
in the image placeholder area.

Other scenarios could arise as well. Whatever you do, it's never a bad idea to
signal to the user when an error has occurred, and possibly give them an action
to take if something goes awry.

## JavaScript availability {: #wrong-no-js }

It shouldn't be assumed that JavaScript is always available. If you're going to
lazy-load images, consider offering `<noscript>` markup that will show images in
case JavaScript is unavailable. The simplest possible fallback example involves
using `<noscript>` elements to serve images if JavaScript is turned off:

<!-- An image that eventually gets lazy-loaded by JavaScript -->
<img class="lazy" src="placeholder-image.jpg" data-src="image-to-lazy-load.jpg" alt="I'm an image!">
<!-- An image that is shown if JavaScript is turned off -->
<noscript>
  <img src="image-to-lazy-load.jpg" alt="I'm an image!">
</noscript>

If JavaScript is turned off, users will see _both_ the placeholder image and the
image contained with the `<noscript>` elements. To get around this, place
a class of `no-js` on the `<html>` tag like so:

```html
<html class="no-js">
```

Then place one line of inline script in the `<head>` before any style sheets
are requested via `<link>` tags that removes the `no-js` class from the `<html>`
element if JavaScript is on:

```html
<script>document.documentElement.classList.remove("no-js");</script>
```

Finally, use some CSS to hide elements with a class of lazy when
JavaScript is unavailable:

```css
.no-js .lazy {
  display: none;
}
```

This doesn't prevent placeholder images from loading, but the outcome is more
desirable. People with JavaScript turned off get something more than placeholder
images, which is better than placeholders and no meaningful image content at
all.

# Lazy loading images

Images can appear on a webpage due to being inline in the HTML as `<img>` elements
or as CSS background images. In this post you will find out how to lazy-load both types of image.

## Inline images {: #images-inline }

The most common lazy loading candidates are images as used in `<img>` elements.
With inline images we have three options for lazy loading,
which may be used in combination for the best compatibility across browsers:

- [Using browser-level lazy loading](#images-inline-browser-level)
- [Using Intersection Observer](#images-inline-intersection-observer)

### Using browser-level lazy loading {: #images-inline-browser-level }

Chrome and Firefox both support lazy loading with the `loading` attribute.
This attribute can be added to `<img>` elements, and also to `<iframe>` elements.
A value of `lazy` tells the browser to load the image immediately if it is in the viewport,
and to fetch other images when the user scrolls near them.

{% Aside %}
  Note `<iframe loading="lazy">` is currently non-standard.
  While implemented in Chromium, it does not yet have a specification and is subject to future change when this does happen.
  We suggest not to lazy-load iframes using the `loading` attribute until it becomes part of the specification.
{% endAside %}

See the `loading` field of MDN's
[browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Element/img#Browser_compatibility)
table for details of browser support.
If the browser does not support lazy loading then the attribute will be ignored
and images will load immediately, as normal.

For most websites, adding this attribute to inline images will be a performance boost
and save users loading images that they may not ever scroll to.
If you have large numbers of images and want to be sure that users of browsers without support for lazy loading benefit
you will need to combine this with one of the methods explained next.

To learn more, check out [Browser-level lazy loading for the web](/browser-level-image-lazy-loading/).

### Using Intersection Observer {: #images-inline-intersection-observer }

To polyfill lazy loading of `<img>` elements, we use JavaScript to check if they're in the
viewport. If they are, their `src` (and sometimes `srcset`) attributes are
populated with URLs to the desired image content.

If you've written lazy loading code before, you may have accomplished your task
by using event handlers such as `scroll` or `resize`. While this approach is the
most compatible across browsers, modern browsers offer a more performant and
efficient way to do the work of checking element visibility via [the
Intersection Observer API](https://developer.chrome.com/blog/intersectionobserver/).

Intersection Observer is easier to use and read than code relying on various
event handlers, because you only need to register an observer to watch
elements rather than writing tedious element visibility detection code. All
that's left to do is to decide what to do when an element is visible.
Let's assume this basic markup pattern for your lazily loaded `<img>` elements:

```html
<img class="lazy" src="placeholder-image.jpg" data-src="image-to-lazy-load-1x.jpg" data-srcset="image-to-lazy-load-2x.jpg 2x, image-to-lazy-load-1x.jpg 1x" alt="I'm an image!">
```

There are three relevant pieces of this markup that you should focus on:

1. The `class` attribute, which is what you'll select the element with in
JavaScript.
2. The `src` attribute, which references a placeholder image that will appear when
the page first loads.
3. The `data-src` and `data-srcset` attributes, which are placeholder attributes
containing the URL for the image you'll load once the element is in the viewport.

Now let's see how to use Intersection Observer in JavaScript to lazy-load
images using this markup pattern:

```javascript
document.addEventListener("DOMContentLoaded", function() {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

  if ("IntersectionObserver" in window) {
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          lazyImage.classList.remove("lazy");
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });

    lazyImages.forEach(function(lazyImage) {
      lazyImageObserver.observe(lazyImage);
    });
  } else {
    // Possibly fall back to event handlers here
  }
});
```

On the document's `DOMContentLoaded` event, this script queries the DOM for all
`<img>` elements with a class of `lazy`. If Intersection Observer is available,
create a new observer that runs a callback when `img.lazy` elements enter the
viewport.

{% Glitch {
  id: 'lazy-intersection-observer',
  path: 'index.html',
  previewSize: 0
} %}

Intersection Observer is available in all modern browsers.
Therefore using it as a polyfill for `loading="lazy"` will ensure that lazy loading is available for most visitors.

## Images in CSS {: #images-css }

While `<img>` tags are the most common way of using images on web pages, images
can also be invoked via the CSS
[`background-image`](https://developer.mozilla.org/docs/Web/CSS/background-image)
property (and other properties). Browser-level lazy loading does not apply to CSS background images,
so you need to consider other methods if you have background images to lazy-load.

Unlike `<img>` elements which load regardless
of their visibility, image loading behavior in CSS is done with more
speculation. When [the document and CSS object
models](/critical-rendering-path-constructing-the-object-model/)
and [render
tree](/critical-rendering-path-render-tree-construction/)
are built, the browser examines how CSS is applied to a document before
requesting external resources. If the browser has determined a CSS rule
involving an external resource doesn't apply to the document as it's currently
constructed, the browser doesn't request it.

This speculative behavior can be used to defer the loading of images in CSS by
using JavaScript to determine when an element is within the viewport, and
subsequently applying a class to that element that applies styling invoking a
background image. This causes the image to be downloaded at the time of need
instead of at initial load. For example, let's take an element that contains a
large hero background image:

```html
<div class="lazy-background">
  <h1>Here's a hero heading to get your attention!</h1>
  <p>Here's hero copy to convince you to buy a thing!</p>
  <a href="/buy-a-thing">Buy a thing!</a>
</div>
```

The `div.lazy-background` element would normally contain the hero background
image invoked by some CSS. In this lazy loading example, however, you can isolate
the `div.lazy-background` element's `background-image` property via a `visible`
class added to the element when it's in the viewport:

```css
.lazy-background {
  background-image: url("hero-placeholder.jpg"); /* Placeholder image */
}

.lazy-background.visible {
  background-image: url("hero.jpg"); /* The final image */
}
```

From here, use JavaScript to check if the element is in the viewport (with
Intersection Observer!), and add the `visible` class to the
`div.lazy-background` element at that time, which loads the image:

```javascript
document.addEventListener("DOMContentLoaded", function() {
  var lazyBackgrounds = [].slice.call(document.querySelectorAll(".lazy-background"));

  if ("IntersectionObserver" in window) {
    let lazyBackgroundObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          entry.target.classList.add("visible");
          lazyBackgroundObserver.unobserve(entry.target);
        }
      });
    });

    lazyBackgrounds.forEach(function(lazyBackground) {
      lazyBackgroundObserver.observe(lazyBackground);
    });
  }
});
```

{% Glitch {
  id: 'lazy-background',
  path: 'index.html',
  previewSize: 0
} %}

### Effects on Largest Contentful Paint (LCP)

Lazy loading is a great optimization that reduces both overall data usage and network contention during startup by deferring the loading of images to when they're actually needed. This can improve startup time, and reduce processing on the main thread by reducing time needed for image decodes.

However, lazy loading is a technique that can affect your website's [Largest Contentful Paint LCP](/lcp/) in a negative way if you're too eager with the technique. One thing you will want to avoid is lazy loading images that are in the viewport during startup.

When using JavaScript-based lazy loaders, you will want to avoid lazy loading in-viewport images because these solutions often use a `data-src` or `data-srcset` attribute as a placeholder for `src` and `srcset` attributes. The problem here is that the loading of these images will be delayed because [the browser preload scanner can't find them during startup](/preload-scanner/#lazy-loading-with-javascript).

Even using browser-level lazy loading to lazy load an in-viewport image can backfire. When `loading="lazy"` is applied to an in-viewport image, [that image will be delayed until the browser knows for sure it's in the viewport](/optimize-lcp/#optimize-the-priority-the-resource-is-given), which can affect a page's LCP.

_Never_ lazy load images that are visible in the viewport during startup. It's a pattern that will affect your site's LCP negatively, and therefore the user experience. If you need an image at startup, load it at startup as quickly as possible by not lazy loading it!

## Lazy loading libraries {: #libraries }

You should use browser-level lazy loading whenever possible, but if you find yourself in a situation where that isn't an option&mdash;such as a significant group of users still reliant on older browsers&mdash;the following libraries can be used to lazy-load images:

- [lazysizes](https://github.com/aFarkas/lazysizes) is a full-featured lazy
loading library that lazy-loads images and iframes. The pattern it uses is quite
similar to the code examples shown here in that it automatically binds to a
`lazyload` class on `<img>` elements, and requires you to specify image URLs in
`data-src` and/or `data-srcset` attributes, the contents of which are swapped
into `src` and/or `srcset` attributes, respectively. It uses Intersection
Observer (which you can polyfill), and can be extended with [a number of
plugins](https://github.com/aFarkas/lazysizes#available-plugins-in-this-repo) to
do things like lazy-load video. [Find out more about using lazysizes](/use-lazysizes-to-lazyload-images/).
- [vanilla-lazyload](https://github.com/verlok/vanilla-lazyload) is a
  lightweight option for lazy loading images, background images, videos, iframes,
  and scripts. It leverages Intersection Observer, supports responsive images, and
  enables browser-level lazy loading.
- [lozad.js](https://github.com/ApoorvSaxena/lozad.js) is a another lightweight
option that uses Intersection Observer only. As such, it's highly performant,
but will need to be polyfilled before you can use it on older browsers.
- If you need a React-specific lazy loading library, consider
[react-lazyload](https://github.com/jasonslyvia/react-lazyload). While it
doesn't use Intersection Observer, it _does_ provide a familiar method of lazy
loading images for those accustomed to developing applications with React.

# Lazy loading video

As with [image elements](/lazy-loading-images), you can also lazy-load video. Videos are commonly loaded with the `<video>` element (although [an
alternate method using
`<img>`](https://calendar.perfplanet.com/2017/animated-gif-without-the-gif/) has
emerged with limited implementation). _How_ to lazy-load `<video>` depends on the
use case, though. Let's discuss a couple of scenarios that each require a
different solution.

## For video that doesn't autoplay {: #video-no-autoplay }

For videos where playback is initiated by the user (that is, videos that _don't_
autoplay), specifying the [`preload`
attribute](https://developer.mozilla.org/docs/Web/HTML/Element/video#attr-preload)
on the `<video>` element may be desirable:

```html
<video controls preload="none" poster="one-does-not-simply-placeholder.jpg">
  <source src="one-does-not-simply.webm" type="video/webm">
  <source src="one-does-not-simply.mp4" type="video/mp4">
</video>
```

{% Aside 'important' %}
A video `poster` image can qualify as an [LCP candidates](/lcp/#what-elements-are-considered). If your `poster` image is an LCP candidate, you should [preload it](/preload-critical-assets/) with a [`fetchpriority` attribute value of `"high"`](/fetch-priority/#the-fetchpriority-attribute) so the user sees it as soon as possible.
{% endAside %}

The example above uses a `preload` attribute with a value of `none` to prevent browsers
from preloading _any_ video data. The `poster`
attribute gives the `<video>` element a placeholder that will occupy the space while the video loads. The reason for this is
that default behaviors for loading video can vary from browser to browser:

- In Chrome, the default for `preload` used to be `auto`, but as of Chrome 64, it now
defaults to `metadata`. Even so, on the desktop version of Chrome, a portion of
the video may be preloaded using the `Content-Range` header. Other Chromium-based browsers and Firefox behave similarly.
- As with Chrome on desktop, 11.0 desktop versions of Safari will preload a range
of the video.
From version 11.2, only the video metadata is preloaded. [In Safari on iOS, videos are never
preloaded](https://developer.apple.com/library/content/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/AudioandVideoTagBasics/AudioandVideoTagBasics.html#//apple_ref/doc/uid/TP40009523-CH2-SW9).
- When [Data Saver mode](https://support.google.com/chrome/answer/2392284) is
enabled, `preload` defaults to `none`.

Because browser default behaviors with regard to `preload` are not set in stone,
being explicit is probably your best bet. In this cases where the user initiates
playback, using `preload="none"` is the easiest way to defer loading of video on
all platforms. The `preload` attribute isn't the only way to defer the loading
of video content. [_Fast Playback with Video
Preload_](/fast-playback-with-preload/) may give you
some ideas and insight into working with video playback in JavaScript.

Unfortunately, it doesn't prove useful when you want to use video in place of
animated GIFs, which is covered next.

## For video acting as an animated GIF replacement {: #video-gif-replacement }

While animated GIFs enjoy wide use, they're subpar to video equivalents in a
number of ways, particularly in file size. Animated GIFs can stretch into
the range of several megabytes of data. Videos of similar visual quality tend to
be far smaller.

Using the `<video>` element as a replacement for animated GIF is not as
straightforward as the `<img>` element. Animated GIFs have three characteristics:

1. They play automatically when loaded.
2. They loop continuously ([though that's not always the
case](https://davidwalsh.name/prevent-gif-loop)).
3. They don't have an audio track.

Achieving this with the `<video>` element looks something like this:

```html
<video autoplay muted loop playsinline>
  <source src="one-does-not-simply.webm" type="video/webm">
  <source src="one-does-not-simply.mp4" type="video/mp4">
</video>
```

The `autoplay`, `muted`, and `loop` attributes are self-explanatory.
[`playsinline` is necessary for autoplaying to occur in
iOS](https://webkit.org/blog/6784/new-video-policies-for-ios/). Now you have a
serviceable video-as-GIF replacement that works across platforms. But how to go
about lazy loading it? To start, modify your `<video>` markup accordingly:

```html
<video class="lazy" autoplay muted loop playsinline width="610" height="254" poster="one-does-not-simply.jpg">
  <source data-src="one-does-not-simply.webm" type="video/webm">
  <source data-src="one-does-not-simply.mp4" type="video/mp4">
</video>
```

You'll notice the addition of the [`poster`
attribute](https://developer.mozilla.org/docs/Web/HTML/Element/video#attr-poster),
which lets you specify a placeholder to occupy the `<video>` element's space
until the video is lazy-loaded. As with the [`<img>` lazy-loading examples](/lazy-loading-images/),
 stash the video URL in the `data-src` attribute on each `<source>`
element. From there, use JavaScript code similar to the
Intersection Observer-based image lazy loading examples:

```javascript
document.addEventListener("DOMContentLoaded", function() {
  var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy"));

  if ("IntersectionObserver" in window) {
    var lazyVideoObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(video) {
        if (video.isIntersecting) {
          for (var source in video.target.children) {
            var videoSource = video.target.children[source];
            if (typeof videoSource.tagName === "string" && videoSource.tagName === "SOURCE") {
              videoSource.src = videoSource.dataset.src;
            }
          }

          video.target.load();
          video.target.classList.remove("lazy");
          lazyVideoObserver.unobserve(video.target);
        }
      });
    });

    lazyVideos.forEach(function(lazyVideo) {
      lazyVideoObserver.observe(lazyVideo);
    });
  }
});
```

When you lazy-load a `<video>` element, you need to iterate through all of the child
`<source>` elements and flip their `data-src` attributes to `src` attributes. Once
you've done that, you need to trigger loading of the video by calling the
element's `load` method, after which the media will begin playing automatically
per the `autoplay` attribute.

Using this method, you have a video solution that emulates animated GIF behavior,
but doesn't incur the same intensive data usage as animated GIFs do,
and you can lazy-load that content.

## Lazy loading libraries {: #libraries }

The following libraries can help you to lazy-load video:

- [vanilla-lazyload](https://github.com/verlok/vanilla-lazyload) and
[lozad.js](https://github.com/ApoorvSaxena/lozad.js) are super lightweight options
that use Intersection Observer only. As such, they are highly performant, but
will need to be polyfilled before you can use them on older browsers.
- [yall.js](https://github.com/malchata/yall.js) is a library that uses
Intersection Observer and falls back to event handlers. It can also lazy load video `poster` images using a `data-poster` attribute.
- If you need a React-specific lazy loading library, you might consider
[react-lazyload](https://github.com/jasonslyvia/react-lazyload). While it
doesn't use Intersection Observer, it _does_ provide a familiar method of lazy
loading images for those accustomed to developing applications with React.

Each of these lazy loading libraries is well documented, with plenty of markup
patterns for your various lazy loading endeavors.

# Minify CSS

CSS files can contain unnecessary characters, such as comments, whitespaces, and indentation.
In production, these characters can be safely removed, to reduce file size without affecting how the browser processes the styles. This technique is called **minification**.

## Loading unminified CSS

Take a look at the following CSS block:

```css
body {
  font-family: "Benton Sans", "Helvetica Neue", helvetica, arial, sans-serif;
  margin: 2em;
}

/* all titles need to have the same font, color and background */
h1 {
  font-style: italic;
  color: #373fff;
  background-color: #000000;
}

h2 {
  font-style: italic;
  color: #373fff;
  background-color: #000000;
}
```

This content is easy to read, at the cost of producing a larger than necessary file:

- It uses spaces for indentation purposes and contains comments, which are ignored by the browser, so they can be removed.
- The `<h1>` and `<h2>` elements have the same styles: instead of declaring them separately: "`h1 {...} h2 {...}`" they could be expressed as "`h1, h2{...}`".
- The **background-color**, `#000000` could be expressed as just `#000`.

After making these changes, you would obtain a more compact version of the same styles:

```css
body{font-family:"Benton Sans","Helvetica Neue",helvetica,arial,sans-serif;margin:2em}h1,h2{font-style:italic;color:#373fff;background-color:#000}
```

You probably don't want to write CSS like that. Instead, you can write CSS as usual, and add a minification step to your build process. In this guide, you'll learn how to do it by using a popular build tool: [webpack](https://webpack.js.org/).

## Measure

You'll apply CSS minification to a site that has been used in other guides: [Fav Kitties](https://fav-kitties-animated.glitch.me/). This version of the site uses a cool CSS library: [animate.css](https://github.com/daneden/animate.css), to animate different page elements when a user votes for a cat 😺.

As a first step, you need to understand what would be the opportunity after minifying this file:

1. Open [the measure page](/measure).
1. Enter the URL: `https://fav-kitties-animated.glitch.me` and click **Run Audit**.
1. Click **View report**.
1. Click on **Performance** and go the **Opportunities** section.

The resulting report shows that up to **16&nbsp;KB** can be saved from the **animate.css** file:

{% Img src="image/admin/RFMk5OMAIvOlkUZJTsh4.png", alt="Lighthouse: Minify CSS opportunity.", width="800", height="172", class="screenshot" %}

Now inspect the content of the CSS:

1. Open the [Fav Kitties site](https://fav-kitties-animated.glitch.me/) in Chrome. (It might take a while for Glitch servers to respond the first time.)
{% Instruction 'devtools-network', 'ol' %}
1. Click the **CSS** filter.
1. Select the **Disable cache** checkbox.
{% Instruction 'reload-app', 'ol' %}

{% Img src="image/admin/WgneNAyftk8jneyXxMih.png", alt="DevTools CSS unoptimized trace", width="800", height="138" %}

The page is requesting two CSS files, of **1.9KB** and **76.2KB** respectively.

1. Click **animate.css**.
1. Click the **Response** tab, to see the file contents.

Note that the stylesheet contains characters for whitespaces and indentation:

{% Img src="image/admin/UEB5Xxe5IHhGtMx3XfKD.png", alt="DevTools CSS unoptimized response", width="800", height="286" %}

Next, you'll add some webpack plugins to your build process to minify these files.

{% Aside 'note' %}
**Note:** the previous Lighthouse report only lists `animate.css` as an opportunity for minification. Minifying `style.css` will also save some bytes, but not enough for Lighthouse to consider it a significant savings. However, minifying CSS is a general best practice; so it makes sense to minify all of your CSS files.
{% endAside %}

## CSS Minification with webpack

Before jumping into the optimizations, take some time understanding how build process for the [Fav Kitties site](https://glitch.com/edit/#!/fav-kitties-animated?path=webpack.config.js:1:0]) works:

{% Glitch {
  id: 'fav-kitties-animated',
  path: 'webpack.config.js',
  previewSize: 0
} %}

By default, the resulting JS bundle that webpack produces would contain the content of the CSS files inlined. Since we want to maintain separate CSS files, we are using two complementary plugins:

- [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) will extract each style sheet into its own file, as one of the steps of the build process.
- [webpack-fix-style-only-entries](https://github.com/fqborges/webpack-fix-style-only-entries) is used to correct an issue in wepback 4, to avoid generating an extra JS file for each CSS file listed in **webpack-config.js**.

You will now make some changes in the project:

1. Open [the Fav Kitties project in Glitch](https://glitch.com/~fav-kitties-animated).
{% Instruction 'source', 'ol' %}
{% Instruction 'remix', 'ol' %}
{% Instruction 'console', 'ol' %}

To minify the resulting CSS, you'll use the [optimize-css-assets-webpack-plugin](https://github.com/NMFR/optimize-css-assets-webpack-plugin):

1. In Glitch console, run `npm install --save-dev optimize-css-assets-webpack-plugin`.
1. Run `refresh`, so the changes are synchronized with the Glitch editor.

Next, go back to the Glitch editor, open the **webpack.config.js** file, and make the following modifications:

Load the module at the beginning of the file:
```js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
```

Then, pass an instance of the plugin to the **plugins** array:
```js
  plugins: [
    new HtmlWebpackPlugin({template: "./src/index.html"}),
    new MiniCssExtractPlugin({filename: "[name].css"}),
    new FixStyleOnlyEntriesPlugin(),
    new OptimizeCSSAssetsPlugin({})
  ]
```
After making the changes a rebuild of the project will be triggered.
This is how the resulting **webpack.config.js** will look like:

{% Glitch {
  id: 'fav-kitties-animated-min',
  path: 'webpack.config.js',
  previewSize: 0
} %}

Next, you'll check the result of this optimization with performance tools.

## Verify

{% Instruction 'preview' %}

If you got lost in any previous step, you can click
[here](https://fav-kitties-animated-min.glitch.me/), to open an optimized
version of the site.

To inspect the size and content of the files:

{% Instruction 'devtools-network', 'ol' %}
1. Click the **CSS** filter.
1. Select the **Disable cache** checkbox if it isn't already.
{% Instruction 'reload-app', 'ol' %}

{% Img src="image/admin/id5kWwB3NilmVPWPTM59.png", alt="DevTools CSS unoptimized response", width="800", height="130" %}

You can inspect these files, and see that the new versions don't contain any whitespaces. Both files are much smaller, in particular, the [animate.css](http://fav-kitties-animated-min.glitch.me/animate.css) has been reduced in **~26%**, saving **~20KB**!

As a final step:

1. Open [the measure page](/measure).
1. Enter the URL of the optimized site.
1. Click **View report**.
1. Click on **Performance** and find the **Opportunities** section.

The report doesn't show "Minify CSS" as "Opportunity" anymore, and has now moved to "Passed Audits" section:

{% Img src="image/admin/zegn2qIHYYK58w1GhgYd.png", alt="Lighthouse Passed Audits for optimized page.", width="800", height="163" %}

Since CSS files are [render-blocking resources](https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources), if you apply minification on sites that use large CSS files, you can see improvements on metrics like [First Contentful Paint](/fcp/).

## Next steps and resources

In this guide, we've covered CSS Minification with webpack, but the same approach can be followed with other build tools, like [gulp-clean-css](https://www.npmjs.com/package/gulp-clean-css) for [Gulp](https://gulpjs.com/), or
[grunt-contrib-cssmin](https://www.npmjs.com/package/grunt-contrib-cssmin) for [Grunt](https://gruntjs.com/).

Minification can also be applied to other types of files. Check out the [Minify and compress network payloads guide](/fast/reduce-network-payloads-using-text-compression) to learn more about tools to minify JS, and some complementary techniques, like compression.

# Assessing loading performance in the field with Navigation Timing and Resource Timing

If you've used connection throttling in the network panel in a browser's developer tools (or [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) in Chrome) to assess loading performance, you know how convenient those tools are for performance tuning. You can quickly measure the impact of performance optimizations with a consistent and stable baseline connection speed. The only problem is that this is synthetic testing, which yields [lab data](/lab-and-field-data-differences/#lab-data), not [field data](/lab-and-field-data-differences/#field-data).

Synthetic testing isn't inherently _bad_, but it's not representative of how fast your website is loading for real users. That requires field data, which you can collect from the Navigation Timing and Resource Timing APIs.

## APIs to help you assess loading performance in the field

Navigation Timing and Resource Timing are two similar APIs with significant overlap that measure two distinct things:

- [Navigation Timing](https://w3c.github.io/navigation-timing/) measures the speed of requests for HTML documents (that is, navigation requests).
- [Resource Timing](https://w3c.github.io/resource-timing/) measures the speed of requests for document-dependent resources such as CSS, JavaScript, images, and so on.

These APIs expose their data in a _performance entry buffer_, which can be accessed in the browser with JavaScript. There are multiple ways to query a performance buffer, but a common way is by using [`performance.getEntriesByType`](https://developer.mozilla.org/docs/Web/API/Performance/getEntriesByType):

```javascript
// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');
```

`performance.getEntriesByType` accepts a string describing the type of entries you want to retrieve from the performance entry buffer. `'navigation'` and `'resource'` retrieve timings for the Navigation Timing and Resource Timing APIs, respectively.

{% Aside %}
Try loading a website and then enter either of the commands in the above code snippet in your browser's console to see actual timings captured by your browser.
{% endAside %}

The amount of information these APIs provide can be overwhelming, but they're your key to measuring loading performance in the field, as you can gather these timings from users as they visit your website.

## The life and timings of a network request

Gathering and analyzing navigation and resource timings is sort of like archeology in that you're reconstructing the fleeting life of a network request after the fact. Sometimes it helps to visualize concepts, and where network requests are concerned, your browser's developer tools can help.

<figure>
{% Img src="image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/BxgZyzAEyFnk846wvzNa.png", alt="A diagram of network timings as shown in Chrome's DevTools. The timings depicted are for request queueing, connection negotiation, the request itself, and the response in color-coded bars.", width="800", height="583" %}
  <figcaption>A visualization of a network request in the <a href="https://developer.chrome.com/docs/devtools/network/" rel="noopener">network panel of Chrome's DevTools</a></figcaption>
</figure>

The life of a network request has distinct phases, such as DNS lookup, connection establishment, TLS negotiation, and so on. These timings are represented as a [`DOMHighResTimestamp`](https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp). Depending on your browser, the granularity of timings may be down to the microsecond, or be rounded up to milliseconds. Let's examine these phases in detail, and how they relate to Navigation Timing and Resource Timing.

{% Aside %}
As you read this guide, [this diagram](https://www.w3.org/TR/navigation-timing-2/#processing-model) for both Navigation Timing and Resource Timing may help you to visualize the order of the timings they provide.
{% endAside %}

### DNS lookup

When a user goes to a URL, the Domain Name System (DNS) is queried to translate a domain to an IP address. This process may take significant time&mdash;time you'll want to measure in the field, even. Navigation Timing and Resource Timing expose two DNS-related timings:

- `domainLookupStart` is when DNS lookup begins.
- `domainLookupEnd` is when DNS lookup ends.

Calculating total DNS lookup time can be done by subtracting the start metric from the end metric:

```javascript
// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;
```

{% Aside 'caution' %}
You can't _always_ rely on timings to be populated. Timings provided in both APIs will have a value of `0` in some cases. For example, a DNS lookup may be served by a local cache. Additionally, any timings for cross-origin requests may be unavailable if those origins don't set a [`Timing-Allow-Origin` response header](https://developer.mozilla.org/docs/Web/HTTP/Headers/Timing-Allow-Origin).
{% endAside %}

### Connection negotiation

Another contributing factor to loading performance is connection negotiation, which is latency incurred when connecting to a web server. If HTTPS is involved, this process will also include TLS negotiation time. The connection phase consists of three timings:

- `connectStart` is when the browser starts to open a connection to a web server.
- `secureConnectionStart` marks when the client begins TLS negotiation.
- `connectEnd` is when the connection to the web server has been established.

Measuring total connection time is similar to measuring total DNS lookup time: you subtract the start timing from the end timing. However, there's an additional `secureConnectionStart` property that may be `0` if HTTPS isn't used or [if the connection is persistent](https://en.wikipedia.org/wiki/HTTP_persistent_connection). If you want to measure TLS negotiation time, you'll need to keep that in mind:

```javascript
// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}
```

Once DNS lookup and connection negotiation ends, timings related to fetching documents and their dependent resources come into play.

### Requests and responses

Loading performance is affected by two types of factors:

- **Extrinsic factors:** These are things like latency and bandwidth. Beyond choosing a hosting company and a CDN, they're (mostly) out of our control, as users can access the web from anywhere.
- **Intrinsic factors:** These are things like server and client-side architectures, as well as resource size and our ability to optimize for those things, which are within our control.

Both types of factors affect loading performance. Timings related to these factors are vital, as they describe how long resources take to download. Both Navigation Timing and Resource Timing describe loading performance with the following metrics:

- `fetchStart` marks when the browser begins to fetch a resource (Resource Timing) or a document for a navigation request (Navigation Timing). This precedes the actual request, and is the point at which the browser is checking caches (for example, HTTP and [`Cache` instances](https://developer.mozilla.org/docs/Web/API/Cache)).
- `workerStart` marks when a request starts being handled within a service worker's [`fetch` event handler](https://developer.mozilla.org/docs/Web/API/FetchEvent). This will be `0` when no service worker is controlling the current page.
- `requestStart` is when the browser makes the request.
- `responseStart` is when the first byte of the response arrives.
- `responseEnd` is when the last byte of the response arrives.

These timings allow you to measure multiple aspects of loading performance, such as cache lookup within a service worker _and_ download time:

```javascript
// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}
```

You can also measure other aspects of request/response latency:

```javascript
const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;
```

### Other measurements you can make

Navigation Timing and Resource Timing is useful for more than what the examples above outline. Here are some other situations with relevant timings that may be worth exploring:

- **Page redirects:** Redirects are an overlooked source of added latency, especially redirect chains. Latency gets added in a number of ways, such as HTTP-to-HTTPs hops, as well as 302/uncached 301 redirects. The `redirectStart`, `redirectEnd`, and `redirectCount` timings are helpful in assessing redirect latency.
- **Document unloading:** In pages that run code in an [`unload` event handler](https://developer.mozilla.org/docs/Web/Events/unload), the browser must execute that code before it can navigate to the next page. `unloadEventStart` and `unloadEventEnd` measure document unloading.
- **Document processing:** Document processing time may not be consequential unless your website sends very large HTML payloads. If this describes your situation, the `domInteractive`, `domContentLoadedEventStart`, `domContentLoadedEventEnd`, and `domComplete` timings may be of interest.

{% Aside 'warning' %}
Timings related to document unloading and processing are available only in Navigation Timing, as they only apply to navigation requests.
{% endAside %}

## Acquiring timings in application code

All of the examples shown so far use `performance.getEntriesByType`, but there are other ways to query the performance entry buffer, such as [`performance.getEntriesByName`](https://developer.mozilla.org/docs/Web/API/Performance/getEntriesByName) and [`performance.getEntries`](https://developer.mozilla.org/docs/Web/API/Performance/getEntries). These methods are fine when only light analysis is needed. In other situations, though, they can introduce excessive main thread work by iterating over a large number of entries, or even repeatedly polling the performance buffer to find new entries.

The recommended approach for collecting entries from the performance entry buffer is to use a [`PerformanceObserver`](https://developer.mozilla.org/docs/Web/API/PerformanceObserver). `PerformanceObserver` listens for performance entries, and provides them as they're added to the buffer:

```javascript
// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});
```

{% Aside %}
Adding the `buffered` option to a performance observer's `observe` event ensures that performance entries added to the buffer prior to the instantiation of the performance observer are observable.
{% endAside %}

This method of collecting timings may feel awkward when compared to directly accessing the performance entry buffer, but it's preferable to tying up the main thread with work that doesn't serve a critical and user-facing purpose.

## Phoning home

Once you've collected all the timings you need, you can send them to an endpoint for further analysis. Two ways to do this are with either [`navigator.sendBeacon`](https://developer.mozilla.org/docs/Web/API/Navigator/sendBeacon) or a [`fetch` with the `keepalive` option](https://developer.mozilla.org/docs/Web/API/fetch) set. Both methods will send a request to a specified endpoint in a non-blocking way, and the request will be queued in a way that outlives the current page session if need be:

```javascript
// Caution: If you have lots of performance entries, don't
// do this. This is an example for illustrative purposes.
const data = JSON.stringify(performance.getEntries()));

// The endpoint to transmit the encoded data to
const endpoint = '/analytics';

// Check for fetch keepalive support
if ('keepalive' in Request.prototype) {
  fetch(endpoint, {
    method: 'POST',
    body: data,
    keepalive: true,
    headers: {
      'Content-Type': 'application/json'
    }
  });
} else if ('sendBeacon' in navigator) {
  // Use sendBeacon as a fallback
  navigator.sendBeacon(endpoint, data);
}
```

In this example, the JSON string will arrive in a `POST` payload that you can decode and process/store in an application backend as needed.

## Wrapping up

Once you have metrics collected, it's up to you to figure out how to analyze that field data. When analyzing field data, there are a few general rules to follow to ensure you're drawing meaningful conclusions:

- [Avoid averages](https://www.igvita.com/2016/01/12/the-average-page-is-a-myth/), as they're not representative of any one user's experience, and may be skewed by outliers.
- Rely on percentiles. In datasets of time-based performance metrics, lower is better. This means that when you prioritize low percentiles, you're only paying attention to the fastest experiences.
- [Prioritize the long tail of values](https://timkadlec.com/remembers/2018-06-07-prioritizing-the-long-tail-of-performance/). When you prioritize experiences at the 75th percentile or higher, you're putting your focus where it belongs: on the slowest experiences.

This guide isn't meant to be an exhaustive resource on Navigation or Resource Timing, but a starting point. Below are some additional resources you may find helpful:

- [Navigation Timing Spec](https://www.w3.org/TR/navigation-timing-2/).
- [Resource Timing Spec](https://www.w3.org/TR/resource-timing-2/).
- [ResourceTiming in Practice](https://nicj.net/resourcetiming-in-practice/).
- [Navigation Timing API (MDN)](https://developer.mozilla.org/docs/Web/API/Navigation_timing_API)
- [Resource Timing API (MDN)](https://developer.mozilla.org/docs/Web/API/Resource_Timing_API)

With these APIs and the data they provide, you'll be better equipped to understand how loading performance is experienced by real users, which will give you more confidence in diagnosing and addressing loading performance problems in the field.

# Optimize CSS background images with media queries

Many sites request heavy resources, like images, that are not optimized for certain screens, and send large CSS files containing styles that some devices will never use. Using media queries is a popular technique for delivering tailored stylesheets and assets to different screens to reduce the amount of data transferred to users and improve page load performance. This guide shows you how to use media queries to send images that are only as large as they need to be, a technique commonly known as **responsive images**.

## Prerequisites

This guide assumes that you're familiar with [Chrome DevTools](https://developer.chrome.com/docs/devtools/). You can use another browser's DevTools instead if you prefer. You'll just need to map the Chrome DevTools screenshots in this guide back to the relevant features in your browser of choice.

## Understand responsive background images

First, analyze the network traffic of the unoptimized demo:

1. Open the [unoptimized demo](https://use-media-queries-unoptimized.glitch.me/) in a new Chrome tab.
{% Instruction 'devtools-network', 'ol' %}
{% Instruction 'reload-page', 'ol' %}

{% Aside %}
Check out [Inspect Network Activity With Chrome DevTools](https://developer.chrome.com/docs/devtools/network/) if you need more help with DevTools.
{% endAside %}

You'll see that the only image that's being requested is `background-desktop.jpg`, which has a size of **1006KB**:

<figure>
  {% Img src="image/admin/K8P4MHp2FSnZYTw3ZVkG.png", alt="DevTools network trace for the unoptimized background image.", width="800", height="126" %}
</figure>

Resize the browser window and notice that the Network Log isn't showing any new requests being made by the page. This means that the same image background is being used for all screen sizes.

You can see the styles that control the background image in [style.css](https://use-media-queries-unoptimized.glitch.me/style.css):

```css
body {
  background-position: center center;
  background-attachment: fixed;
  background-repeat: no-repeat; background-size: cover;
  background-image: url(images/background-desktop.jpg);
}
```

Here's the meaning of each of the properties used:

- `background-position: center center`: Center the image vertically and horizontally.
- `background-repeat: no-repeat`: Show the image only once.
- `background-attachment: fixed`: Avoid making the background image scroll.
- `background-size: cover`: Resize the image to cover the entire container.
- `background-image: url(images/background-desktop.jpg)`: The URL of the image.

When combined, these styles tell the browser to adapt the background image to different screen heights and widths. This is the first step towards achieving a responsive background.

Using a single background image for all screen sizes has some limitations:

- The same amount of bytes are sent, regardless of the screen size, even when, for some devices, like phones, a smaller and more lightweight image background would look just as good. In general, you want to send the smallest possible image that still looks good on the user's screen to improve performance and save user data.
- In smaller devices the image will be stretched or cut to cover the entire screen, potentially hiding relevant parts of the background to users.

In the next section, you'll learn how to apply an optimization to load different background images, according to the user's device.

## Use media queries

Using media queries is a common technique to declare stylesheets that will only apply to certain media or device types. They are implemented by using [@media rules](https://developer.mozilla.org/docs/Web/CSS/@media), which let you define a set of breakpoints, where specific styles are defined.
When the conditions defined by the `@media` rule are met (for example, a certain screen width), the group of styles defined inside the breakpoint will be applied.

The following steps can be used to apply media queries to [the site](https://use-media-queries-unoptimized.glitch.me/) so that different images are used, depending on the maximum width of the device requesting the page.

- In `style.css` remove the line that contains the background image URL:

```css//4
body {
  background-position: center center;
  background-attachment: fixed;
  background-repeat: no-repeat; background-size: cover;
  background-image: url(images/background-desktop.jpg);
}
```

- Next, create a breakpoint for each screen width, based on the common dimensions in pixels that mobile, tablet, and desktop screens usually have:

For mobile:

```css
@media (max-width: 480px) {
    body {
        background-image: url(images/background-mobile.jpg);
    }
}
```

For tablets:

```css
@media (min-width: 481px) and (max-width: 1024px) {
    body {
        background-image: url(images/background-tablet.jpg);
    }
}
```

For desktop devices:

```css
@media (min-width: 1025px) {
    body {
	    background-image: url(images/background-desktop.jpg);
   }
}
```

Open the optimized version of [style.css](https://use-media-queries-optimized.glitch.me/style.css) in your browser to see the changes made.

{% Aside %}
The images used in the optimized demo are already resized to fit into different screen sizes. Showing how to resize images is out of the scope of this guide, but if you want to know more about this, the [Serve responsive images guide](/serve-responsive-images/) covers some useful tools, like the [sharp npm package](https://www.npmjs.com/package/sharp) and the [ImageMagick CLI](https://www.imagemagick.org/script/index.php).
{% endAside %}

## Measure for different devices

Next visualize the resulting site in different screen sizes and in simulated mobile devices:

1. Open the [optimized site](https://use-media-queries-optimized.glitch.me/) in a new Chrome tab.
1. Make your viewport narrow (less than `480px`).
{% Instruction 'devtools-network', 'ol' %}
{% Instruction 'reload-page', 'ol' %}
Notice how the `background-mobile.jpg` image was requested.
1. Make your viewport wider. Once it's wider than `480px` notice how `background-tablet.jpg` is requested. Once it's wider than `1025px` notice how `background-desktop.jpg` is requested.

When the width of the browser screen is changed, new images are requested.

In particular when the width is below the value defined in the mobile breakpoint (480px), you see the following Network Log:

<figure>
  {% Img src="image/admin/jd2kHIefYf91udpFEmvx.png", alt="DevTools network trace for the optimized background image.", width="800", height="125" %}
</figure>

The size of the new mobile background is **67% smaller** than the desktop one.

## Effects on Largest Contentful Paint (LCP)

Elements with CSS background images are considered a candidate for [Largest Contentful Paint (LCP)](/lcp/), however, CSS background images [are not discoverable by the browser preload scanner](/preload-scanner/#css-background-images), which means you could be delaying your site's LCP if you're not careful.

The first option you should consider is whether your LCP candidate image could work in an `<img>` element with `srcset` and `sizes` attributes for responsiveness. The browser preload scanner _will_ discover `<img>` elements, and send requests for them while the parser is blocked on rendering.

If you can't (or don't want to) avoid using a CSS background image, the second option would be to [preload responsive images](/preload-responsive-images/) to ensure you preload the right image for the proper viewport size. The `<link>` elements `media`, `imagesrcset`, and `imagesizes` attributes hint to the browser that a given resource hint only applies in certain viewport conditions, avoiding multiple wasted preloads when you only want to load the one resource that's a fit for the current viewport.

## Summary

In this guide you've learned to apply media queries to request background images tailored to specific screen sizes and save bytes when accessing the site on smaller devices, like mobile phones.
You used the `@media` rule to implement a responsive background. This technique is widely supported by all browsers.
A new CSS feature: [image-set()](https://www.w3.org/TR/css-images-4/#image-set-notation), can be used for the same purpose with fewer lines of code. At the time of this writing, this feature is not supported in all browsers, but you might want to keep an eye on how adoption evolves, as it can represent an interesting alternative to this technique.

# Optimize WebFont loading and rendering

A "full" WebFont that includes all stylistic variants,
which you may not need, plus all the glyphs, which may go unused,
can easily result in a multi-megabyte download.
In this post you will find out how to optimize loading of WebFonts
so visitors only download what they will use.

To address the problem of large files containing all variants,
the `@font-face` CSS rule is specifically designed
to allow you to split the font family into a collection of resources. For example
unicode subsets, and distinct style variants.

Given these declarations,
the browser figures out the required subsets and variants and downloads the minimal set required to render the text, which is very convenient.
However, if you're not careful,
it can also create a performance bottleneck in the critical rendering path and delay text rendering.

### The default behavior

Lazy loading of fonts carries an important hidden implication that may delay text rendering.
The browser must [construct the render tree](/critical-rendering-path-render-tree-construction/),
which is dependent on the DOM and CSSOM trees,
before it knows which font resources it needs to render the text.
As a result, font requests are delayed well after other critical resources,
and the browser may be blocked from rendering text until the resource is fetched.

{% Img src="image/admin/NgSTa9SirmikQAq1G5fN.png", alt="Font critical rendering path", width="800", height="303" %}

1. The browser requests the HTML document.
2. The browser begins parsing the HTML response and constructing the DOM.
3. The browser discovers CSS, JS, and other resources and dispatches requests.
4. The browser constructs the CSSOM after all of the CSS content is received and combines it with
the DOM tree to construct the render tree.
    - Font requests are dispatched after the render tree indicates which font variants are needed to
    render the specified text on the page.
5. The browser performs layout and paints content to the screen.
    - If the font is not yet available, the browser may not render any text pixels.
    - After the font is available, the browser paints the text pixels.

The "race" between the first paint of page content,
which can be done shortly after the render tree is built,
and the request for the font resource is what creates the "blank text problem"
where the browser might render page layout but omits any text.

By preloading WebFonts and using `font-display` to control how browsers behave with unavailable fonts,
you can prevent blank pages and layout shifts due to font loading.

### Preload your WebFont resources

If there's a high probability that your page will need a specific WebFont hosted at a URL you know in advance,
you can take advantage of [resource prioritization](/prioritize-resources/).
Using `<link rel="preload">` will trigger a request for the WebFont early in the critical rendering path,
without having to wait for the CSSOM to be created.

### Customize the text rendering delay

While preloading makes it more likely that a WebFont will be available when a page's content is rendered,
it offers no guarantees.
You still need to consider how browsers behave when rendering text that uses a `font-family` which is not yet available.

In the post [Avoid invisible text during font loading](/avoid-invisible-text/) you can see that default browser behavior is not consistent.
However, you can tell modern browsers how you want them to behave by using
[`font-display`](https://developer.mozilla.org/docs/Web/CSS/@font-face/font-display).

{% BrowserCompat 'css.at-rules.font-face.font-display' %}

Similar to the existing font timeout behaviors that some browsers implement,
`font-display` segments the lifetime of a font download into three major periods:

1. The first period is the **font block period**.
During this period, if the font face is not loaded,
any element attempting to use it must instead render with an invisible fallback font face.
If the font face successfully loads during the block period, the font face is then used normally.
2. The **font swap period** occurs immediately after the font block period.
During this period, if the font face is not loaded,
any element attempting to use it must instead render with a fallback font face.
If the font face successfully loads during the swap period, the font face is then used normally.
3. The **font failure period** occurs immediately after the font swap period.
If the font face is not yet loaded when this period starts,
it's marked as a failed load, causing normal font fallback.
Otherwise, the font face is used normally.

Understanding these periods means you can use `font-display` to decide how your
font should render depending on whether or when it was downloaded.

To work with the `font-display` property, add it to your `@font-face` rules:

```css
@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 400;
  font-display: auto; /* or block, swap, fallback, optional */
  src: local('Awesome Font'),
       url('/fonts/awesome-l.woff2') format('woff2'), /* will be preloaded */
       url('/fonts/awesome-l.woff') format('woff'),
       url('/fonts/awesome-l.ttf') format('truetype'),
       url('/fonts/awesome-l.eot') format('embedded-opentype');
  unicode-range: U+000-5FF; /* Latin glyphs */
}
```

`font-display` currently supports the following range of values:

* `auto`
* `block`
* `swap`
* `fallback`
* `optional`

For more information on preloading fonts, and the `font-display` property, see the following posts:

- [Avoid invisible text during font loading](/avoid-invisible-text/)
- [Controlling font performance using font-display](https://developer.chrome.com/blog/font-display/)
- [Prevent layout shifting and flashes of invisibile text (FOIT) by preloading optional fonts](/preload-optional-fonts/)

### The Font Loading API

Used together, `<link rel="preload">` and the CSS `font-display` give you a great deal of control over font loading and rendering,
without adding in much overhead.
But if you need additional customizations,
and are willing to incur the overhead introduced by running JavaScript, there is another option.

The [Font Loading API](https://www.w3.org/TR/css-font-loading/) provides a scripting interface to define and manipulate CSS font faces,
track their download progress, and override their default lazyload behavior.
For example, if you're sure that a particular font variant is required,
you can define it and tell the browser to initiate an immediate fetch of the font resource:

{% BrowserCompat 'api.FontFace' %}

```javascript
var font = new FontFace("Awesome Font", "url(/fonts/awesome.woff2)", {
  style: 'normal', unicodeRange: 'U+000-5FF', weight: '400'
});

// don't wait for the render tree, initiate an immediate fetch!
font.load().then(function() {
  // apply the font (which may re-render text and cause a page reflow)
  // after the font has finished downloading
  document.fonts.add(font);
  document.body.style.fontFamily = "Awesome Font, serif";

  // OR... by default the content is hidden,
  // and it's rendered after the font is available
  var content = document.getElementById("content");
  content.style.visibility = "visible";

  // OR... apply your own render strategy here...
});
```

Further, because you can check the font status
(via the [`check()`](https://www.w3.org/TR/css-font-loading/#font-face-set-check)) method
and track its download progress,
you can also define a custom strategy for rendering text on your pages:

- You can hold all text rendering until the font is available.
- You can implement a custom timeout for each font.
- You can use the fallback font to unblock rendering and inject a new style that uses the desired
font after the font is available.

Best of all, you can also mix and match the above strategies for different content on the page.
For example, you can delay text rendering on some sections until the font is available,
use a fallback font, and then re-render after the font download has finished.

{% Aside %}
The Font Loading API is [not available in older browsers](http://caniuse.com/#feat=font-loading).
Consider using the [FontLoader polyfill](https://github.com/bramstein/fontloader) or the
[WebFontloader library](https://github.com/typekit/webfontloader) to deliver similar functionality,
albeit with even more overhead from an additional JavaScript dependency.
{% endAside %}

### Proper caching is a must

Font resources are, typically, static resources that don't see frequent updates.
As a result, they are ideally suited for a long max-age expiry&mdash;
ensure that you specify both a [conditional ETag header](/http-cache/),
and an [optimal Cache-Control policy](/http-cache/) for all font resources.

If your web application uses a [service worker](https://developer.chrome.com/docs/workbox/service-worker-overview/),
serving font resources with a [cache-first strategy](/offline-cookbook/#cache-then-network) is appropriate for most use cases.

You should not store fonts using [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage)
or [IndexedDB](https://developer.mozilla.org/docs/Web/API/IndexedDB_API);
each of those has its own set of performance issues.
The browser's HTTP cache provides the best and most robust mechanism to deliver font resources to the browser.

## WebFont loading checklist

- **Customize font loading and rendering using `<link rel="preload">`, `font-display`, or the Font
Loading API:** default lazyloading behavior may result in delayed text rendering. These web platform
features allow you to override this behavior for particular fonts, and to specify custom rendering
and timeout strategies for different content on the page.
- **Specify revalidation and optimal caching policies:** fonts are static resources that are
infrequently updated. Make sure that your servers provide a long-lived max-age timestamp and a
revalidation token to allow for efficient font reuse between different pages. If using a service
worker, a cache-first strategy is appropriate.

## Automated testing for WebFont loading behavior with Lighthouse

[Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/)
can help automate the process of making sure that you're following web font optimization best practices.

The following audits can help you make sure that your pages are continuing to follow web font optimization best practices over time:

* [Preload key requests](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preload/)
* [Uses inefficient cache policy on static assets](https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/)
* [All text remains visible during WebFont loads](https://developer.chrome.com/docs/lighthouse/performance/font-display/)

# Performance budgets 101

Performance is an important part of the user experience and it [affects business metrics](https://wpostats.com/). It's tempting to think that if you are a good developer you'll end up with a performant site, but the truth is that good performance is rarely a side effect. As with most other things—to reach a goal you have to define it clearly. Start the journey by setting a **performance budget**.

## Definition

A performance budget is a set of limits imposed on metrics that affect site performance. This could be the total size of a page, the time it takes to load on a mobile network, or even the number of HTTP requests that are sent. Defining a budget helps get the web performance conversation started. It serves as a point of reference for making decisions about design, technology, and adding features.

Having a budget enables designers to think about the effects of high-resolution images and the number of web fonts they pick. It also helps developers compare different approaches to a problem and evaluate frameworks and libraries based on their size and [parsing cost](https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4).

## Choose metrics

### Quantity-based metrics ⚖️

These metrics are useful in the early stages of development because they highlight the impact of including heavy images and scripts. They are also easy to communicate to both designers and developers.

We've already mentioned a few things you can include in a performance budget such as page weight and the number of HTTP requests, but you can split these up into more granular limits like:

* Maximum size of images
* Maximum number of web fonts
* Maximum size of scripts, including frameworks
* Total number of external resources, such as third-party scripts

However, these numbers don't tell you much about the user experience. Two pages with the same number of requests or same weight can render differently depending on the order in which resources get requested. If a [critical resource](/critical-rendering-path/) like a hero image or a stylesheet on one of the pages is loaded late in the process, the users will wait longer to see something useful and perceive the page as slower. If on the other page the most important parts load quickly, they may not even notice if the rest of the page doesn't.

<figure>
  {% Img src="image/admin/U0QhA82KFyED4r1y3tAq.png", alt="Image of progressive page rendering based on the critical-path", width="611", height="300" %}
</figure>

This is why it's important to keep track of another type of metric.

### Milestone timings ⏱️

Milestone timings mark events that happen during page load, such as [DOMContentLoaded](https://developer.mozilla.org/docs/Web/Events/DOMContentLoaded) or [load](https://developer.mozilla.org/docs/Web/Events/load) event. The most useful timings are [user-centric performance metrics](/user-centric-performance-metrics/) that tell you something about the experience of loading a page. These metrics are available through [browser APIs](/user-centric-performance-metrics/#in-the-field) and as part of [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) reports.

[First Contentful Paint (FCP)](/fcp/) measures when the browser displays the first bit of content from the DOM, like text or images.

[Time to Interactive (TTI)](/tti/) measures how long it takes for a page to become fully interactive and reliably respond to user input. It's a very important metric to track if you expect any kind of user interaction on the page like clicking links, buttons, typing or using form elements.

### Rule-based metrics 💯

[Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) and [WebPageTest](https://www.webpagetest.org/) calculate [performance scores](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/#perf-scoring) based on general best practice rules, that you can use as guidelines. As a bonus, Lighthouse also offers you hints for simple optimizations.

You'll get the best results if you keep track of a combination of quantity-based and user-centric performance metrics. Focus on asset sizes in the early phases of a project and start tracking FCP and TTI as soon as possible.

## Establish a baseline

The only way to really know what works best for your site is to try it—research and then test your findings. Analyze the competition to see how you stack up. 🕵️

If you don't have time for that, here are good default numbers to get you started:

* Under **5 s** Time to Interactive
* Under **170 KB** of [critical-path](/critical-rendering-path/) resources (compressed/minified)

These [numbers](https://infrequently.org/2017/10/can-you-afford-it-real-world-web-performance-budgets/) are calculated based on real-world baseline devices and **3G network speed**. [Over half of the internet traffic](https://www.statista.com/statistics/277125/share-of-website-traffic-coming-from-mobile-devices/) today happens on mobile networks, so you should use 3G network speed as a starting point.

### Examples of budgets

You should have a budget in place for different types of pages on your site since the content will vary. For example:

* Our product page must ship less than 170 KB of JavaScript on mobile
* Our search page must include less than 2 MB of images on desktop
* Our home page must load and get interactive in < 5 s on slow 3G on a Moto G4 phone
* Our blog must score > 80 on Lighthouse performance audits

## Add performance budgets to your build process

{% Img src="image/admin/YKJcgI9Yd8qEZM0nzPuv.png", alt="Webpack, bundlesize and Lighthouse logos", width="800", height="267" %}

Choosing a tool for this will depend a lot on the scale of your project and resources that you can dedicate to the task. There are a few open-source tools that can help you add budgeting to your build process:

* [Webpack performance features](https://webpack.js.org/configuration/performance/)
* [bundlesize](https://github.com/siddharthkp/bundlesize)
* [Lighthouse CI](https://github.com/GoogleChrome/lighthouse-ci)

If something goes over a defined threshold, you can either:

* Optimize an existing feature or asset 🛠️
* Remove an existing feature or asset 🗑️
* Not add the new feature or asset ✋⛔

## Track performance

Making sure your site is fast enough means you have to keep measuring after the initial launch. Monitoring these metrics over time and [getting data from real users](/navigation-and-resource-timing/) will show you how changes in performance impact key business metrics.

## Wrap up

The purpose of a performance budget is to make sure you focus on performance throughout a project and setting it early will help prevent backtracking later. It should be the point of reference for helping you figure out what to include on your website. The main idea is to set goals so that you can better balance performance without harming functionality or user experience.🎯

The next guide will walk you through defining your first performance budget in a few simple steps.

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

<figure>
{% Img src="image/admin/Ad9PLq3DcQt9Ycp63z6O.png", alt="Screenshot of Chrome DevTools Network panel.", width="701", height="509" %}
<figcaption>In this example, Pacifico font is defined in the stylesheet with a <a href="/reduce-webfont-size/#defining-a-font-family-with-@font-face)"><code>@font-face</code></a> rule. The browser loads the font file only after it has finished downloading and parsing the stylesheet.</figcaption>
</figure>

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.

<figure>
{% Img src="image/admin/PgRbERrxLGfF439yBMeY.png", alt="Screenshot of Chrome DevTools Network panel after applying preloading.", width="701", height="509" %}
<figcaption>In this example, Pacifico font is preloaded, so the download happens in parallel with the stylesheet.</figcaption>
</figure>

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**](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preload/) audit to identify which resources to preload.

{% Img src="image/admin/BPUTHBNZFbeXqb0dVx2f.png", alt="Lighthouse's preload key requests audit.", width="745", height="97" %}

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

```html
<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.)

{% Aside %}
After implementing preloading, many sites, including [Shopify, Financial Times and Treebo, saw 1-second improvements](https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf) in user-centric metrics such as [Time to Interactive](/tti/) and [First Contentful Paint](/fcp/).
{% endAside %}

Resource hints, for example, [`preconnect`](/preconnect-and-dns-prefetch)and [`prefetch`](/link-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.

{% Img src="image/admin/z4FbCezjXHxaIhq188TU.png", alt="Chrome DevTools Console warning about unused preloaded resources.", width="800", height="228" %}

{% Aside %}
`preload` is supported  in all modern browsers.
{% BrowserCompat 'html.elements.link.rel.preload' %}
{% endAside %}

## Use cases

{% Aside 'caution' %}
At the time of writing, Chrome has an open
[bug](https://bugs.chromium.org/p/chromium/issues/detail?id=788757) for preloaded requests that are
fetched sooner than other higher priority resources. Until this is resolved, be wary of how
preloaded resources can "jump the queue" and be requested sooner than they should.
{% endAside %}

### Preloading resources defined in CSS

Fonts defined with [`@font-face`](/reduce-webfont-size/#defining-a-font-family-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](/extract-critical-css), 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](https://developer.chrome.com/docs/lighthouse/performance/bootup-time/) which can improve metrics such as Time to Interactive. Preloading works best if you [split](/reduce-javascript-payloads-with-code-splitting) 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:

```html
<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](https://developer.mozilla.org/docs/Web/HTML/Element/link#Attributes).

{% Aside %}
Take a look at the [Chrome Resource Priorities and Scheduling](https://docs.google.com/document/d/1bCDuq9H1ih9iNjgzyAL0gpwNFiEP4TZS-YLRp_RuMlc/edit) document to learn more about how the browser prioritizes different types of resources.
{% endAside %}

{% Aside 'caution' %}
Omitting the `as` attribute, or having an invalid value is equivalent to an [XHR request,](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest) where the browser doesn't know what it is fetching so it can't determine the correct priority. It can also cause some resources, such as scripts, to be fetched twice.
{% endAside %}

Some types of resources, such as fonts, are loaded in [anonymous mode](https://www.w3.org/TR/css-fonts-3/#font-fetching-requirements). For those you must set the `crossorigin` attribute with `preload`:

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

{% Aside 'caution' %}
Fonts preloaded without the `crossorigin` attribute will be fetched twice!
{% endAside %}

`<link>` elements also accept a [`type` attribute](https://developer.mozilla.org/docs/Web/HTML/Element/link#attr-type), which contains the [MIME type](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/MIME_types) 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">`.

{% Aside 'codelab' %}
[Improve the performance of a page by preloading web fonts](/codelab-preload-web-fonts).
{% endAside %}

You can also preload any type of resource via the [`Link` HTTP header](https://developer.mozilla.org/docs/Web/HTTP/Headers/Link):

`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](https://webpack.js.org/) version 4.6.0 or later, preloading is supported through the use of [magic comments](https://webpack.js.org/api/module-methods/#magic-comments) inside `import()`:

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

If you are using an older version of webpack, use a third-party plugin such as [preload-webpack-plugin](https://github.com/GoogleChromeLabs/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](/vitals/), and it's important to be aware them.

### Largest Contentful Paint (LCP)

Preloading has a powerful effect on [Largest Contentful Paint (LCP)](/lcp/) when it comes to fonts and images, as both images and text nodes can be [LCP candidates](/lcp/#what-elements-are-considered). 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&mdash;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](https://caniuse.com/woff2), 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](/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)](/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](https://developer.mozilla.org/docs/Web/CSS/@font-face/font-display) 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](/lab-and-field-data-differences/#lab-data), but trust your [field data](/lab-and-field-data-differences/#field-data) to determine if your font loading strategies are improving CLS or making it worse.

### Responsiveness metrics

[First Input Delay (FID)](/fid/) and [Interaction to Next Paint](/inp/) 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&mdash;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](/reduce-javascript-payloads-with-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](/fast#measure-performance-in-the-field).

# Prioritize resources

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.
There are ways that you can influence this decision-making using
`<link rel="preload">`, `<link rel="preconnect">`, and `<link rel="prefetch">`.

## Default priorities in the browser

The browser assigns different relative priorities to different types of resources based on how critical they might be.
So, for example, a `<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 asynchronously).

Priorities become important when investigating loading performance in your site.
Beyond the usual techniques of [measuring](/critical-rendering-path-measure-crp/)
and [analyzing the critical rendering path](/critical-rendering-path-analyzing-crp/),
it's useful to know Chrome's priority for each resource.
You can find that in the **Network** panel in Chrome DevTools.
Here's what it looks like:


<figure>
    {% Img src="image/admin/oqcdfVAVj2RajVWfs6ap.png", alt="An example of how priorities are displayed in Chrome Developer Tools", width="800", height="249" %}
  <figcaption>
    The <b>Priority</b> column, which is hidden by default (see
    <a href="https://developer.chrome.com/docs/devtools/network/reference/#columns">
      Add or remove columns
    </a>.
  </figcaption>
</figure>


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, [Preload, Prefetch And Priorities in Chrome](https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf)
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?

There are three different declarative solutions,
which are all relatively new `<link>` types.

* [`<link rel="preload">`](/preload-critical-assets/) 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.
* [`<link rel="preconnect">`](/preconnect-and-dns-prefetch/) 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.
* [`<link rel="prefetch">`](/link-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.

# Publish, ship, and install modern JavaScript for faster applications

Over 90% of browsers are capable of running modern JavaScript, but the
prevalence of legacy JavaScript remains a large source of performance problems
on the web today.

## Modern JavaScript

Modern JavaScript is not characterized as code written in a specific ECMAScript
specification version, but rather in syntax that is supported by all modern
browsers. Modern web browsers like Chrome, Edge, Firefox, and Safari make up
more than [90% of the browser market](https://www.caniuse.com/usage-table), and
different browsers that rely on the same underlying rendering engines make up an
additional 5%. This means that 95% of global web traffic comes from browsers
that support the most widely used JavaScript language features from the past 10
years, including:

- Classes (ES2015)
- Arrow functions (ES2015)
- Generators (ES2015)
- Block scoping (ES2015)
- Destructuring (ES2015)
- Rest and spread parameters (ES2015)
- Object shorthand (ES2015)
- Async/await (ES2017)

Features in newer versions of the language specification generally have less
consistent support across modern browsers. For example, many ES2020 and ES2021
features are only supported in 70% of the browser market—still the majority of
browsers, but not enough that it's safe to rely on those features directly. This
means that although "modern" JavaScript is a moving target, ES2017 has the
widest range of browser compatibility
[while including most of the commonly used modern syntax features](https://dev.to/garylchew/bringing-modern-javascript-to-libraries-432c).
In other words, **ES2017 is the closest to modern syntax today**.

## Legacy JavaScript

Legacy JavaScript is code that specifically avoids using all the above language
features. Most developers write their source code using modern syntax, but
compile everything to legacy syntax for increased browser support. Compiling
to legacy syntax does increase browser support, however the effect is often
smaller than we realize. In many cases the support increases from around 95%
to 98% while incurring a significant cost:

- Legacy JavaScript is typically around 20% larger and slower than
  equivalent modern code. Tooling deficiencies and misconfiguration often
  widen this gap even further.

- Installed libraries account for as much as 90% of typical production
  JavaScript code. Library code incurs an even higher legacy JavaScript
  overhead due to polyfill and helper duplication that could be avoided
  by publishing modern code.

## Modern JavaScript on npm

Recently, Node.js has standardized an `"exports"` field to define
[entry points for a package](https://nodejs.org/api/packages.html#packages_package_entry_points):

```json
{
  "exports": "./index.js"
}
```

Modules referenced by the `"exports"` field imply a Node version of at least
12.8, which supports ES2019. This means that any module referenced using the
`"exports"` field can be _written in modern JavaScript_. Package consumers must
assume modules with an `"exports"` field contain modern code and transpile if
necessary.

### Modern-only

If you want to publish a package with modern code and leave it up to the
consumer to handle transpiling it when they use it as a dependency—use only the
`"exports"` field.

```json
{
  "name": "foo",
  "exports": "./modern.js"
}
```

{% Aside 'caution' %}
This approach is _not recommended_. In a perfect world, every developer
would have already configured their build system to transpile all dependencies
(`node_modules`) to their required syntax. However, this is not currently the
case, and publishing your package using only modern syntax would prevent its
usage in applications that would be accessed through legacy browsers.
{% endAside %}

### Modern with legacy fallback

Use the `"exports"` field along with `"main"` in order to publish your package
using modern code but also include an ES5 + CommonJS fallback for legacy
browsers.

```json
{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs"
}
```

### Modern with legacy fallback and ESM bundler optimizations

In addition to defining a fallback CommonJS entrypoint, the `"module"` field can
be used to point to a similar legacy fallback bundle, but one that uses
JavaScript module syntax (`import` and `export`).

```json
{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs",
  "module": "./module.js"
}
```

Many bundlers, such as webpack and Rollup, rely on this field to take advantage
of module features and enable
[tree shaking](/commonjs-larger-bundles/#how-does-commonjs-affect-your-final-bundle-size).
This is still a legacy bundle that does not contain any modern code aside from
`import`/`export` syntax, so use this approach to ship modern code with a
legacy fallback that is still optimized for bundling.

## Modern JavaScript in applications

Third-party dependencies make up the vast majority of typical production
JavaScript code in web applications. While npm dependencies have historically
been published as legacy ES5 syntax, this is no longer a safe assumption and
risks dependency updates breaking browser support in your application.

With an increasing number of npm packages moving to modern JavaScript, it's
important to ensure that the build tooling is set up to handle them. There's a
good chance some of the npm packages you depend on are already using modern
language features. There are a number of options available to use modern code
from npm without breaking your application in older browsers, but the general
idea is to have the build system transpile dependencies to the same syntax
target as your source code.

## webpack

As of webpack 5, it is now possible to configure what syntax webpack will use
when generating code for bundles and modules. This doesn't transpile your
code or dependencies, it only affects the "glue" code generated by webpack.
To specify the browser support target, add a
[browserslist configuration](https://github.com/browserslist/browserslist#readme)
to your project, or do it directly in your webpack configuration:

```js
module.exports = {
  target: ['web', 'es2017'],
};
```

It is also possible to configure webpack to generate optimized bundles that
omit unnecessary wrapper functions when targeting a modern ES Modules
environment. This also configures webpack to load code-split bundles using
`<script type="module">`.

```js
module.exports = {
  target: ['web', 'es2017'],
  output: {
    module: true,
  },
  experiments: {
    outputModule: true,
  },
};
```

There are a number of webpack plugins available that make it possible to
compile and ship modern JavaScript while still supporting legacy browsers,
such as Optimize Plugin and BabelEsmPlugin.

### Optimize Plugin

[Optimize Plugin](https://github.com/developit/optimize-plugin) is a webpack
plugin that transforms final bundled code from modern to legacy JavaScript
instead of each individual source file. It's a self-contained setup that allows
your webpack configuration to assume everything is modern JavaScript with no
special branching for multiple outputs or syntaxes.

Since Optimize Plugin operates on bundles instead of individual modules, it
processes your application's code and your dependencies equally. This makes it
safe to use modern JavaScript dependencies from npm, because their code will be
bundled and transpiled to the correct syntax. It can also be faster than
traditional solutions involving two compilation steps, while still generating
separate bundles for modern and legacy browsers. The two sets of bundles are
designed to be loaded using the
[module/nomodule pattern](/serve-modern-code-to-modern-browsers/).

```js
// webpack.config.js
const OptimizePlugin = require('optimize-plugin');

module.exports = {
  // ...
  plugins: [new OptimizePlugin()],
};
```

`Optimize Plugin` can be faster and more efficient than custom webpack
configurations, which typically bundle modern and legacy code separately. It
also handles running [Babel](https://babeljs.io/) for you, and minifies
bundles using [Terser](https://terser.org/) with separate optimal settings for
the modern and legacy outputs. Finally, polyfills needed by the generated
legacy bundles are extracted into a dedicated script so they are never
duplicated or unnecessarily loaded in newer browsers.

<figure>
  <video controls autoplay loop muted>
    <source src="https://storage.googleapis.com/web-dev-assets/fast-publish-modern-javascript/transpile-before-after.webm" type="video/webm">
    <source src="https://storage.googleapis.com/web-dev-assets/fast-publish-modern-javascript/transpile-before-after.mp4" type="video/mp4">
  </video>
  <figcaption>
    Comparison: transpiling source modules twice versus transpiling generated bundles.
  </figcaption>
</figure>

### BabelEsmPlugin

[BabelEsmPlugin](https://github.com/prateekbh/babel-esm-plugin) is a webpack
plugin that works along with
[@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env)
to generate modern versions of existing bundles to ship less transpiled code to
modern browsers. It is the most popular off-the-shelf solution for
module/nomodule, used by [Next.js](https://nextjs.org/) and
[Preact CLI](https://preactjs.com/cli/).

```js
// webpack.config.js
const BabelEsmPlugin = require('babel-esm-plugin');

module.exports = {
  //...
  module: {
    rules: [
      // your existing babel-loader configuration:
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  plugins: [new BabelEsmPlugin()],
};
```

`BabelEsmPlugin` supports a wide array of webpack configurations, because it
runs two largely separate builds of your application. Compiling twice can take a
little bit of extra time for large applications, however this technique allows
`BabelEsmPlugin` to integrate seamlessly into existing webpack configurations
and makes it one of the most convenient options available.

### Configure babel-loader to transpile node_modules

If you are using `babel-loader` without one of the previous two plugins,
there's an important step required in order to consume modern JavaScript npm
modules. Defining two separate `babel-loader` configurations makes it possible
to automatically compile modern language features found in `node_modules` to
ES2017, while still transpiling your own first-party code with the Babel
plugins and presets defined in your project's configuration. This doesn't
generate modern and legacy bundles for a module/nomodule setup, but it does
make it possible to install and use npm packages that contain modern JavaScript
without breaking older browsers.

[webpack-plugin-modern-npm](https://www.npmjs.com/package/webpack-plugin-modern-npm)
uses this technique to compile npm dependencies that have an `"exports"` field
in their `package.json`, since these may contain modern syntax:

```js
// webpack.config.js
const ModernNpmPlugin = require('webpack-plugin-modern-npm');

module.exports = {
  plugins: [
    // auto-transpile modern stuff found in node_modules
    new ModernNpmPlugin(),
  ],
};
```

Alternatively, you can implement the technique manually in your webpack
configuration by checking for an `"exports"` field in the `package.json` of
modules as they are resolved. Omitting caching for brevity, a custom
implementation might look like this:

```js
// webpack.config.js
module.exports = {
  module: {
    rules: [
      // Transpile for your own first-party code:
      {
        test: /\.js$/i,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
      // Transpile modern dependencies:
      {
        test: /\.js$/i,
        include(file) {
          let dir = file.match(/^.*[/\\]node_modules[/\\](@.*?[/\\])?.*?[/\\]/);
          try {
            return dir && !!require(dir[0] + 'package.json').exports;
          } catch (e) {}
        },
        use: {
          loader: 'babel-loader',
          options: {
            babelrc: false,
            configFile: false,
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
};
```

When using this approach, you'll need to ensure modern syntax is supported by
your minifier. Both [Terser](https://github.com/terser/terser#minify-options)
and [uglify-es](https://github.com/mishoo/UglifyJS/tree/harmony#minify-options)
have an option to specify `{ecma: 2017}` in order to preserve and in some cases
generate ES2017 syntax during compression and formatting.

## Rollup

Rollup has built-in support for generating multiple sets of bundles as part of
a single build, and generates modern code by default. As a result, Rollup can
be configured to generate modern and legacy bundles with the official plugins
you're likely already using.

### @rollup/plugin-babel

If you use Rollup, the
[`getBabelOutputPlugin()` method](https://github.com/rollup/plugins/tree/master/packages/babel#running-babel-on-the-generated-code)
(provided by Rollup's
[official Babel plugin](https://github.com/rollup/plugins/tree/master/packages/babel))
transforms the code in generated bundles rather than individual source modules.
Rollup has built-in support for generating multiple sets of bundles as part of
a single build, each with their own plugins. You can use this to produce
different bundles for modern and legacy by passing each through a different
Babel output plugin configuration:

```js
// rollup.config.js
import {getBabelOutputPlugin} from '@rollup/plugin-babel';

export default {
  input: 'src/index.js',
  output: [
    // modern bundles:
    {
      format: 'es',
      plugins: [
        getBabelOutputPlugin({
          presets: [
            [
              '@babel/preset-env',
              {
                targets: {esmodules: true},
                bugfixes: true,
                loose: true,
              },
            ],
          ],
        }),
      ],
    },
    // legacy (ES5) bundles:
    {
      format: 'amd',
      entryFileNames: '[name].legacy.js',
      chunkFileNames: '[name]-[hash].legacy.js',
      plugins: [
        getBabelOutputPlugin({
          presets: ['@babel/preset-env'],
        }),
      ],
    },
  ],
};
```

## Additional build tools

Rollup and webpack are highly-configurable, which generally means each project
must update its configuration enable modern JavaScript syntax in dependencies.
There are also higher-level build tools that favor convention and defaults over
configuration, like [Parcel], [Snowpack], [Vite] and [WMR]. Most of these tools
assume npm dependencies may contain modern syntax, and will transpile them to
the appropriate syntax level(s) when building for production.

In addition to dedicated plugins for webpack and Rollup, modern JavaScript
bundles with legacy fallbacks can be added to any project using
[devolution](https://github.com/theKashey/devolution). Devolution is a
standalone tool that transforms the output from a build system to produce legacy
JavaScript variants, allowing bundling and transformations to assume a modern
output target.

[parcel]: https://parceljs.org/
[snowpack]: https://www.snowpack.dev/
[vite]: https://github.com/vitejs/vite
[wmr]: https://github.com/preactjs/wmr

# Measure performance with the RAIL model

**RAIL** is a **user-centric** performance model that provides a structure for
thinking about performance. The model breaks down the user's experience into key
actions (for example, tap, scroll, load) and helps you define performance goals
for each of them.

{% Aside 'important' %}
[Core Web Vitals](/vitals/) is a newer initiative by Google to provide unified guidance for quality signals that are essential to delivering a great user experience on the web. It is the recommended approach for defining performance goals over RAIL, and has [different thresholds](/defining-core-web-vitals-thresholds/) than those detailed here.
{% endAside %}

RAIL stands for four distinct aspects of web app life cycle: response,
animation, idle, and load. Users have different performance expectations for
each of these contexts, so performance goals are defined based on the context
and [UX research on how users perceive
delays](https://www.nngroup.com/articles/response-times-3-important-limits/).

<figure>
  {% Img src="image/admin/uc1IWVOW2wEhIY6z4KjJ.png", alt="The 4 parts of the RAIL performance model: response, animation, idle, and load.", width="800", height="290" %}
  <figcaption>The 4 parts of the RAIL performance model
</figcaption>
</figure>


## Focus on the user

Make users the focal point of your performance effort. The table below describes
key metrics of how users perceive performance delays:

<table class="table-wrapper">
  <thead>
  User perception of performance delays
  </thead>
  <tr>
    <td>0&nbsp;to&nbsp;16&nbsp;ms</td>
    <td>Users are exceptionally good at tracking motion, and they dislike it when animations aren't smooth. They perceive animations as smooth so long as 60 new frames are rendered every second. That's 16&nbsp;ms per frame, including the time it takes for the browser to paint the new frame to the screen, leaving an app about 10&nbsp;ms to produce a frame.</td>
  </tr>
  <tr>
    <td>0&nbsp;to&nbsp;100&nbsp;ms</td>
    <td>Respond to user actions within this time window and users feel like the result is immediate. Any longer, and the connection between action and reaction is broken.</td>
  </tr>
  <tr>
    <td>100&nbsp;to&nbsp;1000&nbsp;ms</td>
    <td>Within this window, things feel part of a natural and continuous progression of tasks. For most users on the web, loading pages or changing views represents a task.</td>
  </tr>
  <tr>
    <td>1000&nbsp;ms&nbsp;or&nbsp;more</td>
    <td>Beyond 1000 milliseconds (1 second), users lose focus on the task they are performing.</td>
  </tr>
  <tr>
    <td>10000&nbsp;ms or more</td>
    <td>Beyond 10000 milliseconds (10 seconds), users are frustrated and are likely to abandon tasks. They may or may not come back later.</td>
  </tr>
</table>

{% Aside %} Users perceive performance delays differently, depending on network
conditions and hardware. For example, loading sites on a powerful desktop
machine over a fast Wi-Fi connection commonly happens in under 1&nbsp;s and
users have grown accustomed to that. Loading sites on mobile devices with slow
3G connections takes more time, so mobile users are generally more patient and
loading in 5&nbsp;s on mobile is a more realistic goal. {% endAside %}

## Goals and guidelines

In the context of RAIL, the terms **goals** and **guidelines** have specific
meanings:

* **Goals**. Key performance metrics related to user experience. For example,
  tap to paint in under 100 milliseconds. Since human perception is relatively
  constant, these goals are unlikely to change any time soon.

* **Guidelines**. Recommendations that help you achieve goals. These may be
  specific to current hardware and network connection conditions, and therefore
  may change over time.

## Response: process events in under 50ms

**Goal**: Complete a transition initiated by user input within 100&nbsp;ms, so
users feel like the interactions are instantaneous.

**Guidelines**:

* To ensure a visible response within 100&nbsp;ms, process user input events
  within 50&nbsp;ms. This applies to most inputs, such as clicking buttons,
  toggling form controls, or starting animations. This does not apply to touch
  drags or scrolls.

* Though it may sound counterintuitive, it's not always the right call to
  respond to user input immediately. You can use this 100&nbsp;ms window to do
  other expensive work, but be careful not to block the user. If possible, do
  work in the background.

* For actions that take longer than 50&nbsp;ms to complete, always provide
  feedback.

### 50&nbsp;ms or 100&nbsp;ms?

The goal is to respond to input in under 100&nbsp;ms, so why is our budget only
50&nbsp;ms? This is because there is generally other work being done in addition
to input handling, and that work takes up part of the time available for
acceptable input response. If an application is performing work in the
recommended 50&nbsp;ms chunks during idle time, that means input can be queued
for up to 50&nbsp;ms if it occurs during one of those chunks of work. Accounting
for this, it's safe to assume only the remaining 50&nbsp;ms is available for
actual input handling. This effect is visualized in the diagram below which
shows how input received during an idle task is queued, reducing the available
processing time:

<figure>
  {% Img src="image/admin/I7HDZ9qGxe0jAzz6PxNq.png", alt="Diagram showing how input received during an idle task is queued, reducing available input processing time to 50ms", width="800", height="400" %}
  <figcaption>How idle tasks affect input response budget.</figcaption>
</figure>

## Animation: produce a frame in 10&nbsp;ms

**Goals**:

* Produce each frame in an animation in 10&nbsp;ms or less. Technically, the
  maximum budget for each frame is 16&nbsp;ms (1000&nbsp;ms / 60 frames per
  second ≈ 16&nbsp;ms), but browsers need about 6&nbsp;ms to render each frame,
  hence the guideline of 10&nbsp;ms per frame.

* Aim for visual smoothness. Users notice when frame rates vary.

**Guidelines**:

* In high pressure points like animations, the key is to do nothing where you
  can, and the absolute minimum where you can't. Whenever possible, make use of
  the 100&nbsp;ms response to pre-calculate expensive work so that you maximize
  your chances of hitting 60&nbsp;fps.

* See [Rendering
  Performance](/rendering-performance/)
  for various animation optimization strategies.

{% Aside %} Recognize all the types of animations. Animations aren't just fancy
UI effects. Each of these interactions are considered animations:

* Visual animations, such as entrances and exits,
  [tweens](https://www.webopedia.com/TERM/T/tweening.html), and loading
  indicators.
* Scrolling. This includes flinging, which is when the user starts scrolling,
  then lets go, and the page continues scrolling.
* Dragging. Animations often follow user interactions, such as panning a map or
  pinching to zoom. {% endAside %}

## Idle: maximize idle time

**Goal**: Maximize idle time to increase the odds that the page responds to user
input within 50&nbsp;ms.

**Guidelines**:

* Use idle time to complete deferred work. For example, for the initial page
  load, load as little data as possible, then use [idle
  time](https://developer.mozilla.org/docs/Web/API/Window/requestIdleCallback)
  to load the rest.

* Perform work during idle time in 50&nbsp;ms or less. Any longer, and you risk
  interfering with the app's ability to respond to user input within 50&nbsp;ms.

* If a user interacts with a page during idle time work, the user interaction
  should always take the highest priority and interrupt the idle time work.

## Load: deliver content and become interactive in under 5 seconds

When pages load slowly, user attention wanders, and users perceive the task as
broken. Sites that load quickly have [longer average sessions, lower bounce
rates, and higher ad
viewability](https://www.thinkwithgoogle.com/intl/en-154/insights-inspiration/research-data/need-mobile-speed-how-mobile-latency-impacts-publisher-revenue/).

**Goals**:

* Optimize for fast loading performance relative to the device and network
  capabilities of your users. Currently, a good target for first loads is to
  load the page and be [interactive](/tti/) in [5 seconds
  or less on mid-range mobile devices with slow 3G
  connections](/performance-budgets-101/#establish-a-baseline).

* For subsequent loads, a good target is to load the page in under 2 seconds.

{% Aside %}

Be aware that these targets may change over time.

{% endAside %}

**Guidelines**:

* Test your load performance on the mobile devices and network connections that
  are common among your users. You can use [Chrome User Experience
  Report](/chrome-ux-report/) to find out the [connection
  distribution](https://developer.chrome.com/blog/chrome-ux-report-looker-studio-dashboard/#using-the-dashboard)
  of your users. If the data is not available for your site, [The Mobile Economy
  2019](https://www.gsma.com/mobileeconomy/) suggests that a good global
  baseline is a mid-range Android phone, such as a Moto G4, and a slow 3G
  network (defined as 400&nbsp;ms RTT and 400&nbsp;kbps transfer speed). This
  combination is available on [WebPageTest](https://www.webpagetest.org/easy).

* Keep in mind that although your typical mobile user's device might claim that
  it's on a 2G, 3G, or 4G connection, in reality the [effective connection
  speed](/adaptive-serving-based-on-network-quality/#how-it-works)
  is often significantly slower, due to packet loss and network variance.

* [Eliminate render blocking
  resources](https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources/).

* You don't have to load everything in under 5 seconds to produce the perception
  of a complete load. Consider [lazy loading
  images](/browser-level-image-lazy-loading/), [code-splitting JavaScript
  bundles](/reduce-javascript-payloads-with-code-splitting/), and
  other [optimizations suggested on web.dev](/fast/).

{% Aside %}
<p>Recognize the factors that affect page load performance:</p>
<ul>
<li>Network speed and latency</li>
<li>Hardware (slower CPUs, for example)</li>
<li>Cache eviction</li>
<li>Differences in L2/L3 caching</li>
<li>Parsing JavaScript</li>
</ul>
{% endAside %}

## Tools for measuring RAIL

There are a few tools to help you automate your RAIL measurements. Which one you
use depends on what type of information you need, and what type of workflow you
prefer.

### Chrome DevTools

[Chrome DevTools](https://developer.chrome.com/docs/devtools/)
provides in-depth analysis on everything that happens while your page loads or
runs. See [Get Started With Analyzing Runtime
Performance](https://developer.chrome.com/docs/devtools/evaluate-performance/)
to get familiar with the **Performance** panel UI.

The following DevTools features are especially relevant:

* [Throttle your
  CPU](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#cpu-throttle)
  to simulate a less-powerful device.

* [Throttle the
  network](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#network-throttle)
  to simulate slower connections.

* [View main thread
  activity](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#main)
  to view every event that occurred on the main thread while you were recording.

* [View main thread activities in a
  table](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#activities)
  to sort activities based on which ones took up the most time.

* [Analyze frames per second
  (FPS)](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#fps)
  to measure whether your animations truly run smoothly.

* [Monitor CPU usage, JS heap size, DOM nodes, layouts per second, and
  more](https://developer.chrome.com/blog/new-in-devtools-64/#perf-monitor)
  in real-time with the **Performance Monitor**.

* [Visualize network
  requests](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#network)
  that occurred while you were recording with the **Network** section.

* [Capture screenshots while
  recording](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#screenshots)
  to play back exactly how the page looked while the page loaded, or an
  animation fired, and so on.

* [View
  interactions](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#interactions)
  to quickly identify what happened on a page after a user interacted with it.

* [Find scroll performance issues in
  real-time](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#scrolling-performance-issues)
  by highlighting the page whenever a potentially problematic listener fires.

* [View paint events in
  real-time](https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#paint-flashing)
  to identify costly paint events that may be harming the performance of your
  animations.

### Lighthouse

[Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) is available in
Chrome DevTools,  at [PageSpeed Insights](https://pagespeed.web.dev/), as a
Chrome Extension, as a Node.js module, and within WebPageTest. You give it a
URL, it simulates a mid-range device with a slow 3G connection, runs a series of
audits on the page, and then gives you a report on load performance, as well as
suggestions on how to improve.

The following audits are especially relevant:

**Response**

* [Max Potential First Input
  Delay](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-max-potential-fid/). Estimates how long your
  app will take to respond to user input, based on main thread idle time.

* [Does not use passive listeners to improve scrolling
  performance](https://developer.chrome.com/docs/lighthouse/best-practices/uses-passive-event-listeners/).

* [Total Blocking Time](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/).
  Measures the total amount of time that a page is blocked from responding to
  user input, such as mouse clicks, screen taps, or keyboard presses.

* [Time To
  Interactive](https://developer.chrome.com/docs/lighthouse/performance/interactive/).
  Measures when a user can consistently interact with all page elements.

**Load**

* [Does not register a service worker that controls page and
  start_url](https://developer.chrome.com/docs/lighthouse/pwa/service-worker/). A service worker can cache common
  resources on a user's device, reducing time spent fetching resources over the
  network.

* [Page load is not fast enough on mobile
  networks](https://developer.chrome.com/docs/lighthouse/pwa/load-fast-enough-for-pwa/).

* [Eliminate render-blocking
  resources](https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources/).

* [Defer offscreen images](https://developer.chrome.com/docs/lighthouse/performance/offscreen-images/). Defer the loading
  of offscreen images until they're needed.

* [Properly size images](https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images/). Don't serve
  images that are significantly larger than the size that's rendered in the
  mobile viewport.

* [Avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains/).

* [Does not use HTTP/2 for all of its resources](https://developer.chrome.com/docs/lighthouse/best-practices/uses-http2/).

* [Efficiently encode images](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/).

* [Enable text compression](https://developer.chrome.com/docs/lighthouse/performance/uses-text-compression/).

* [Avoid enormous network payloads](https://developer.chrome.com/docs/lighthouse/performance/total-byte-weight/).

* [Avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/). Reduce network bytes
  by only shipping DOM nodes that are needed for rendering the page.

### WebPageTest

WebPageTest is a web performance tool that uses real browsers to access web
pages and collect timing metrics. Enter a URL at
[webpagetest.org/easy](https://webpagetest.org/easy) to get a detailed report on
the page's load performance on a real Moto G4 device with a slow 3G connection.
You can also configure it to include a Lighthouse audit.

## Summary

RAIL is a lens for looking at a website's user experience as a journey composed
of distinct interactions. Understand how users perceive your site in order to
set performance goals with the greatest impact on user experience.

* Focus on the user.

* Respond to user input in under 100&nbsp;ms.

* Produce a frame in under 10&nbsp;ms when animating or scrolling.

* Maximize main thread idle time.

* Load interactive content in under 5000&nbsp;ms.

# Reduce JavaScript payloads with code splitting

Nobody likes waiting.
**[Over 50% of users abandon a website if it takes longer than 3 seconds to load](https://www.thinkwithgoogle.com/intl/en-154/insights-inspiration/research-data/need-mobile-speed-how-mobile-latency-impacts-publisher-revenue/)**.

Sending large JavaScript payloads impacts the speed of your site
significantly. Instead of shipping all the JavaScript to your user as soon as
the first page of your application is loaded, split your bundle into
multiple pieces and only send what's necessary at the very beginning.

## Why is code splitting beneficial?

Code splitting is a technique that seeks to minimize startup time. When we ship less JavaScript at startup, we can get applications to be [interactive faster](/tti/) by minimizing main thread work during this critical period.

When it comes to [Core Web Vitals](/vitals/), reducing JavaScript payloads downloaded at startup will contribute to better [First Input Delay (FID)](/fid/) and [Interaction to Next Paint (INP)](/inp/) times. The reasoning behind this is that, by freeing up the main thread, the application is able to respond to user inputs more quickly by reducing JavaScript parse, compile, and execution-related startup costs.

Depending on your website's architecture&mdash;particularly if your website relies heavily on client-side rendering&mdash;reducing the size of JavaScript payloads responsible for rendering markup may lead to improved [Largest Contentful Paint (LCP)](/lcp/) times. This can occur when the LCP resource is [delayed in being discovered by the browser](/optimize-lcp/#optimize-when-the-resource-is-discovered) until after client-side markup is completed, or when the main thread is too busy to [render that LCP element](/optimize-lcp/#2-eliminate-element-render-delay). Both scenarios can delay the LCP time for the page.

{% Aside 'important' %}
If your website relies on client-side rendering exclusively, you should seek to use [Server-Side Rendering (SSR)](/rendering-on-the-web/#server-rendering) to ensure that the server is sending meaningful markup to the client in response to the navigation request. This can help [the browser preload scanner](/preload-scanner/) to opportunistically fetch resources [more effectively](/preload-scanner/#rendering-markup-with-client-side-javascript).
{% endAside %}

## Measure

Lighthouse displays a failed audit when a significant amount of time is taken to
execute all the JavaScript on a page.

{% Img src="image/admin/p0Ahh3pzXog3jPdDp6La.png", alt="A failing Lighthouse audit showing scripts taking too long to execute.", width="797", height="100" %}

Split the JavaScript bundle to only send the code needed for the initial route when the
user loads an application. This minimizes the amount of script that needs to be
parsed and compiled, which results in faster page load times.

Popular module bundlers like [webpack](https://webpack.js.org/guides/code-splitting/),
[Parcel](https://parceljs.org/code_splitting.html), and
[Rollup](https://rollupjs.org/guide/en#dynamic-import) allow you to split your
bundles using [dynamic imports](https://v8.dev/features/dynamic-import).
For example, consider the following code snippet that shows an example of a
`someFunction` method that gets fired when a form is submitted.

```js
import moduleA from "library";

form.addEventListener("submit", e => {
  e.preventDefault();
  someFunction();
});

const someFunction = () => {
  // uses moduleA
}
```

In here, `someFunction` uses a module imported from a particular library. If
this module is not being used elsewhere, the code block can be modified to use a
dynamic import to fetch it only when the form is submitted by the user.

```js/2-5
form.addEventListener("submit", e => {
  e.preventDefault();
  import('library.moduleA')
    .then(module => module.default) // using the default export
    .then(() => someFunction())
    .catch(handleError());
});

const someFunction = () => {
    // uses moduleA
}
```

The code that makes up the module does not get included into the initial bundle
and is now **lazy loaded**, or provided to the user only when it is needed after
the form submission. To further improve page performance, [preload critical chunks to prioritize and fetch them sooner](/preload-critical-assets).

Although the previous code snippet is a simple example, lazy loading third party
dependencies is not a common pattern in larger applications. Usually, third
party dependencies are split into a separate vendor bundle that can be cached
since they don't update as often. You can read more about how the
[**SplitChunksPlugin**](https://webpack.js.org/plugins/split-chunks-plugin/) can
help you do this.

Splitting on the route or component level when using a client-side framework is
a simpler approach to lazy loading different parts of your application. Many
popular frameworks that use webpack provide abstractions to make lazy loading
easier than diving into the configurations yourself.

# Reduce JavaScript payloads with tree shaking

Today's web applications can get pretty big, especially the JavaScript part of them. As of mid-2018, HTTP Archive puts the [median transfer size of JavaScript on mobile devices](https://httparchive.org/reports/state-of-javascript#bytesJs) at approximately 350 KB. And this is just transfer size! JavaScript is often compressed when sent over the network, meaning that the _actual_ amount of JavaScript is quite a bit more after the browser decompresses it. That's important to point out, because as far as resource _processing_ is concerned, compression is irrelevant. 900 KB of decompressed JavaScript is still 900 KB to the parser and compiler, even though it may be roughly 300 KB when compressed.

<figure>
  {% Img src="image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/VytWruOnBN3nHJnSm18w.svg", alt="A diagram illustrating the process of downloading, decompressing, parsing, compiling, and executing JavaScript.", width="600", height="106" %}
  <figcaption>
    The process of downloading and running JavaScript. Note that even though the transfer size of the script is 300 KB compressed, it is still 900 KB worth of JavaScript that must be parsed, compiled, and executed.
  </figcaption>
</figure>

JavaScript is an expensive resource to process. Unlike images which only incur relatively trivial decode time once downloaded, JavaScript must be parsed, compiled, and then finally executed. Byte for byte, this makes JavaScript more expensive than other types of resources.

<figure>
  {% Img src="image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/rEdnN5fuzBC6DOjUqgEL.png", alt="A diagram comparing the processing time of 170 KB of JavaScript versus an equivalently sized JPEG image. The JavaScript resource is far more resource-intensive byte for byte than the JPEG.", width="800", height="504" %}
  <figcaption>
    The processing cost of parsing/compiling 170 KB of JavaScript vs decode time of an equivalently sized JPEG. (<a href="https://medium.com/dev-channel/the-cost-of-javascript-84009f51e99e" rel="noopener">source</a>).
  </figcaption>
</figure>

[While improvements are continually being made](https://v8.dev/blog/background-compilation) to [improve the efficiency of JavaScript engines](https://blog.mozilla.org/javascript/2017/12/12/javascript-startup-bytecode-cache/), improving JavaScript performance is&mdash;as always&mdash;a task for developers.

To that end, there are techniques to improve JavaScript performance. [Code splitting](/reduce-javascript-payloads-with-code-splitting/), is one such technique that improves performance by partitioning application JavaScript into chunks, and serving those chunks to only the routes of an application that need them.

While this technique works, it doesn't address a common problem of JavaScript-heavy applications, which is the inclusion of code that's never used. Tree shaking attempts to solve this problem.

## What is tree shaking?

[Tree shaking](https://en.wikipedia.org/wiki/Tree_shaking) is a form of dead code elimination. [The term was popularized by Rollup](https://github.com/rollup/rollup#tree-shaking), but the concept of dead code elimination has existed for some time. The concept has also found purchase in [webpack](https://webpack.js.org/guides/tree-shaking/), which is demonstrated in this article by way of a sample app.

The term "tree shaking" comes from the mental model of your application and its dependencies as a tree-like structure. Each node in the tree represents a dependency that provides distinct functionality for your app. In modern apps, these dependencies are brought in via [static `import` statements](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import) like so:

```js
// Import all the array utilities!
import arrayUtils from "array-utils";
```

{% Aside %}
Note: If you're not sure what ES6 modules are, [this explainer at Pony Foo](https://ponyfoo.com/articles/es6-modules-in-depth) is worth a look. This guide assumes you have working knowledge of how ES6 modules work, so if you don't know anything about them, give that article a read!
{% endAside %}

When an app is young&mdash;a sapling, if you will&mdash;it may have few dependencies. It's also using most&mdash;if not all&mdash;the dependencies you add. As your app matures, however, more dependencies can get added. To compound matters, older dependencies fall out of use, but may not get pruned from your codebase. The end result is that an app ends up shipping with a lot of [unused JavaScript](/unused-javascript/). Tree shaking addresses this by taking advantage of how static `import` statements pull in specific parts of ES6 modules:

```js
// Import only some of the utilities!
import { unique, implode, explode } from "array-utils";
```

The difference between this `import` example and the previous one is that rather than importing _everything_ from the `"array-utils"` module&mdash;which could be a lot of code)&mdash;this example imports only specific parts of it. In dev builds, this doesn't change anything, as the entire module gets imported regardless. In production builds, webpack can be configured to "shake" off [exports](https://developer.mozilla.org/docs/web/javascript/reference/statements/export) from ES6 modules that weren't explicitly imported, making those production builds smaller. In this guide, you'll learn how to do just that!

## Finding opportunities to shake a tree

For illustrative purposes, [a sample one-page app](https://github.com/malchata/webpack-tree-shaking-example) is available that demonstrates how tree shaking works. You can clone it and follow along if you like, but we'll cover every step of the way together in this guide, so cloning isn't necessary (unless hands-on learning is your thing).

The sample app is a searchable database of guitar effect pedals. You enter a query and a list of effect pedals will appear.

<figure>
  {% Img src="image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/bBmp460zNYmTVeAgQhw5.png", alt="A screenshot of a sample one page application for searching a database of guitar effect pedals.", width="800", height="673" %}
  <figcaption>
    A screenshot of the sample app.
  </figcaption>
</figure>

The behavior that drives this app is separated into vendor (i.e., [Preact](https://preactjs.com/) and [Emotion](https://emotion.sh/)) and app-specific code bundles (or "chunks", as webpack calls them):

<figure>
  {% Img src="image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/oreVh5rQcXxIDenvQxE7.png", alt="A screenshot of two application code bundles (or chunks) shown in the network panel of Chrome's DevTools.", width="800", height="282" %}
  <figcaption>
    The app's two JavaScript bundles. These are uncompressed sizes.
  </figcaption>
</figure>

The JavaScript bundles shown in the figure above are production builds, meaning they're optimized through uglification. 21.1 KB for an app-specific bundle isn't bad, but it should be noted that no tree shaking is occurring whatsoever. Let's look at the app code and see what can be done to fix that.

{% Aside %}
If you don't care for a long-winded explanation, and want to dive into code, go ahead and check out [the `tree-shake` branch](https://github.com/malchata/webpack-tree-shaking-example/tree/tree-shake) in the app's GitHub repo. You can also [diff this branch](https://github.com/malchata/webpack-tree-shaking-example/compare/tree-shake) to see exactly what was changed to make tree shaking work!
{% endAside %}

In any application, finding tree shaking opportunities are going to involve looking for static `import` statements. [Near the top of the main component file](https://github.com/malchata/webpack-tree-shaking-example/blob/master/src/components/FilterablePedalList/FilterablePedalList.js#L4), you'll see a line like this:

```js
import * as utils from "../../utils/utils";
```

You can [import ES6 modules in a variety of ways](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import#description), but ones like this should get your attention. This specific line says "`import` _everything_ from the `utils` module, and put it in a namespace called `utils`." The big question to ask here is, "just how much _stuff_ is in that module?"

If you look at [the `utils` module source code](https://github.com/malchata/webpack-tree-shaking-example/blob/master/src/utils/utils.js), you'll find there's about 1,300 lines of code.

Do you _need_ all that stuff? Let's double check by searching [the main component file](https://github.com/malchata/webpack-tree-shaking-example/blob/master/src/components/FilterablePedalList/FilterablePedalList.js) that imports the `utils` module to see how many instances of that namespace come up.

<figure>
  {% Img src="image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/Grk6jcj6IiMHIiJPqiEM.png", alt="A screenshot of a search in a text editor for 'utils.', returning only 3 results.", width="800", height="117" %}
  <figcaption>
    The <code>utils</code> namespace we've imported tons of modules from is only invoked three times within the main component file.
  </figcaption>
</figure>

As it turns out, the `utils` namespace appears in only three spots in our application&mdash;but for what functions? If you take a look at the main component file again, it appears to be only one function, which is `utils.simpleSort`, which is used to sort the search results list by a number of criteria when the sorting dropdowns are changed:

```js
if (this.state.sortBy === "model") {
  // `simpleSort` gets used here...
  json = utils.simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
  // ..and here...
  json = utils.simpleSort(json, "type", this.state.sortOrder);
} else {
  // ..and here.
  json = utils.simpleSort(json, "manufacturer", this.state.sortOrder);
}
```

Out of a 1,300 line file with a bunch of exports, only one of them is used. This results in shipping a lot of unused JavaScript.

{% Aside %}
Note: This project is purposefully kept simple, so it's a bit easier in this case to find out where the bloat is coming from. In large projects with many modules, however, things get more complicated. Tools such as [Webpack Bundle Analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer) and [source-map-explorer](https://www.npmjs.com/package/source-map-explorer) may provide further insight.
{% endAside %}

While this example app is admittedly a bit contrived, it doesn't change the fact that this synthetic sort of scenario resembles actual optimization opportunities you may encounter in a production web app. Now that you've identified an opportunity for tree shaking to be useful, how is it actually done?

## Keeping Babel from transpiling ES6 modules to CommonJS modules

[Babel](https://babeljs.io/) is an indispensable tool, but it may make the effects of tree shaking a bit more difficult to observe. If you're using [`@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env), Babel _may_ transform ES6 modules into more widely compatible CommonJS modules&mdash;that is, modules you `require` instead of `import`.

Because tree shaking is more difficult to do for CommonJS modules, webpack won't know what to prune from bundles if you decide to use them. The solution is to configure `@babel/preset-env` to explicitly leave ES6 modules alone. Wherever you configure Babel&mdash;be it in `babel.config.js` or `package.json`&mdash;this involves adding a little something extra:

```js
// babel.config.js
export default {
  presets: [
    [
      "@babel/preset-env", {
        modules: false
      }
    ]
  ]
}
```

Specifying `modules: false` in your `@babel/preset-env` config gets Babel to behave as desired, which allows webpack to analyze your dependency tree and shake off unused dependencies.

## Keeping side effects in mind

Another aspect to consider when shaking dependencies from your app is whether
your project's modules have side effects. An example of a side effect is when a
function modifies something outside of its own scope, which is a _side effect_
of its execution:

```js
let fruits = ["apple", "orange", "pear"];

console.log(fruits); // (3) ["apple", "orange", "pear"]

const addFruit = function(fruit) {
  fruits.push(fruit);
};

addFruit("kiwi");

console.log(fruits); // (4) ["apple", "orange", "pear", "kiwi"]
```

In this example, `addFruit` produces a side effect when it modifies the `fruits` array, which is outside its scope.

Side effects also apply to ES6 modules, and that matters in the context of tree shaking. Modules that take predictable inputs and produce equally predictable outputs without modifying anything outside of their own scope are dependencies that can be safely dropped if we're not using them. They're self-contained, _modular_ pieces of code. Hence, "modules".

Where webpack is concerned, a hint can be used to specify that a package and its dependencies are free of side effects by specifying `"sideEffects": false` in a project's `package.json` file:

```json
{
  "name": "webpack-tree-shaking-example",
  "version": "1.0.0",
  "sideEffects": false
}
```

Alternatively, you can tell webpack which specific files are not side effect-free:

```json
{
  "name": "webpack-tree-shaking-example",
  "version": "1.0.0",
  "sideEffects": [
    "./src/utils/utils.js"
  ]
}
```

In the latter example, any file that isn't specified will be assumed to be free of side effects. If you don't want to add this to your `package.json` file, [you can also specify this flag in your webpack config via `module.rules`](https://github.com/webpack/webpack/issues/6065#issuecomment-351060570).

## Importing only what's needed

After instructing Babel to leave ES6 modules alone, a slight adjustment to our `import` syntax is required to bring in only the functions needed from the `utils` module. In this guide's example, all that's needed is the `simpleSort` function:

```js
import { simpleSort } from "../../utils/utils";
```

Because only `simpleSort` is being imported instead of the entire `utils` module, every instance of `utils.simpleSort` will need to changed to `simpleSort`:

```js
if (this.state.sortBy === "model") {
  json = simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
  json = simpleSort(json, "type", this.state.sortOrder);
} else {
  json = simpleSort(json, "manufacturer", this.state.sortOrder);
}
```

This should be all that's needed for tree shaking to work in this example. This is the webpack output before shaking the dependency tree:

```shell
                 Asset      Size  Chunks             Chunk Names
js/vendors.16262743.js  37.1 KiB       0  [emitted]  vendors
   js/main.797ebb8b.js  20.8 KiB       1  [emitted]  main
```

This is the output _after_ tree shaking is successful:

```shell
                 Asset      Size  Chunks             Chunk Names
js/vendors.45ce9b64.js  36.9 KiB       0  [emitted]  vendors
   js/main.559652be.js  8.46 KiB       1  [emitted]  main
```

While both bundles shrank, it's really the `main` bundle that benefits most. By shaking off the unused parts of the `utils` module, the `main` bundle shrinks by about 60%. This not only lowers the amount of time the script takes to the download, but processing time as well.

## Go shake some trees!

Whatever mileage you get out of tree shaking depends on your app and its dependencies and architecture. Try it! If you know for a fact you haven't set up your module bundler to perform this optimization, there's no harm trying and seeing how it benefits your application.

You may realize a significant performance gain from tree shaking, or not much at all. But by configuring your build system to take advantage of this optimization in production builds and selectively importing only what your application needs, you'll be proactively keeping your application bundles as small as possible.

_Special thanks to Kristofer Baxter, [Jason Miller](/authors/developit/), [Addy Osmani](/authors/addyosmani/), [Jeff Posnick](/authors/jeffposnick/), Sam Saccone, and [Philip Walton](/authors/philipwalton/) for their valuable feedback, which significantly improved the quality of this article._

# Minify and compress network payloads

There are two useful techniques that can be used to improve the performance of
your web page:

* Minification
* Data compression

Incorporating both of these techniques reduces payload sizes and in turn
improves page load times.

## Measure

Lighthouse displays a failed audit if it detects any CSS or JS resources on your
page that can be minified.

{% Img src="image/admin/ZT9ESeCStegt0SklYbni.png", alt="Lighthouse Minify CSS Audit", width="800", height="90" %}

{% Img src="image/admin/vDaAnUSvQxmGcoasQj1k.png", alt="Lighthouse Minify JS Audit", width="800", height="112" %}

It also audits for any uncompressed assets.

{% Img src="image/admin/xfqzdLuu3w3lanxo5Ggc.png", alt="Lighthouse: Enable text compression", width="800", height="123" %}

## Minification

**Minification** is the process of removing whitespace and any code that is not
necessary to create a smaller but perfectly valid code file.
[Terser](https://github.com/terser-js/terser) is a popular JavaScript
compression tool and [webpack](https://webpack.js.org/) v4 includes a plugin
for this library by default to create minified build files.

* If you're using webpack v4 or greater, you should be good to go
    without doing any additional work. 👍
* If you are using an older version of webpack, install and include
`TerserWebpackPlugin` into your webpack configuration settings. Follow
the [documentation](https://webpack.js.org/plugins/terser-webpack-plugin/) to
learn how.
* If you are not using a module bundler, use `Terser` as a CLI tool or
include it directly as a dependency to your application. The project
[documentation](https://github.com/terser-js/terser) provides instructions.

## Data compression

**Compression** is the process of modifying data using a compression algorithm.
[Gzip](https://www.youtube.com/watch?v=whGwm0Lky2s&feature=youtu.be&t=14m11s) is
the most widely used compression format for server and client interactions.
[Brotli](https://opensource.googleblog.com/2015/09/introducing-brotli-new-compression.html)
is a newer compression algorithm which can provide even better compression
results than Gzip.

{% Aside %}
Compressing files can significantly improve the performance of a
webpage, but you rarely need to do this yourself. Many hosting
platforms, CDNs and reverse proxy servers either encode assets with compression
by default or allow you to easily configure them. Read the documentation for the
tool that you are using to see if compression is already supported before
attempting to roll out your own solution.
{% endAside %}

There are two different ways to compress files sent to a browser:

* Dynamically
* Statically

Both approaches have their own advantages and disadvantages which is covered in
the next section. Use whichever works best for your application.

## Dynamic compression

This process involves compressing assets on-the-fly as they get requested by the
browser. This can be simpler than compressing files manually or with a build
process, but can cause delays if high compression levels are used.

[Express](https://expressjs.com/) is a popular web framework for Node and
provides a [compression](https://github.com/expressjs/compression) middleware
library. Use it to compress any asset as it gets requested. Here is an example
of an entire server file that uses it correctly:

```js/5
const express = require('express');
const compression = require('compression');

const app = express();

app.use(compression());

app.use(express.static('public'));

const listener = app.listen(process.env.PORT, function() {
	console.log('Your app is listening on port ' + listener.address().port);
});
```

This compresses your assets using `gzip`. If your web server supports it,
consider using a separate module like
[shrink-ray](https://github.com/aickin/shrink-ray#readme) to compress via
Brotli to achieve better compression ratios.

{% Aside 'codelab' %}
Use express.js to compress assets with [gzip](/codelab-text-compression) and [Brotli](/codelab-text-compression-brotli).
{% endAside %}

## Static compression

Static compression  involves compressing and saving assets ahead of time. This
can make the build process take longer, especially if high compression levels
are used, but ensures that no delays happen when the browser fetches the
compressed resource.

If your web server supports Brotli, use a plugin like
[BrotliWebpackPlugin](https://github.com/mynameiswhm/brotli-webpack-plugin) with
webpack to compress your assets as part of your build step. Otherwise, use
[CompressionPlugin](https://github.com/webpack-contrib/compression-webpack-plugin)
to compress your assets with gzip. It can be included just like any other plugin
in the webpack configurations file:

```js/4
module.exports = {
	//...
	plugins: [
		//...
		new CompressionPlugin()
	]
}
```

Once compressed files are part of the build folder, create a route in your
server to handle all JS endpoints to serve the compressed files. Here is an
example of how this can be done with Node and Express for gzipped assets.

<pre>
const express = require('express');
const app = express();

<strong>app.get('*.js', (req, res, next) => {
	req.url = req.url + '.gz';
	res.set('Content-Encoding', 'gzip');
	next();
});</strong>

app.use(express.static('public'));
</pre>

# Reduce web font size

Typography is fundamental to good design, branding, readability, and accessibility. Web fonts enable all of the above and more: the text is selectable, searchable, zoomable, and high-DPI friendly, providing consistent and sharp text rendering regardless of the screen size and resolution. WebFonts are critical to good design, UX, and performance.

Web font optimization is a critical piece of the overall performance strategy. Each font is an additional resource, and some fonts may block rendering of the text, but just because the page is using WebFonts doesn't mean that it has to render slower. On the contrary, optimized fonts, combined with a judicious strategy for how they are loaded and applied on the page, can help reduce the total page size and improve page rendering times.

## Anatomy of a web font

A *web font* is a collection of glyphs, and each glyph is a vector shape that describes a letter or symbol. As a result, two simple variables determine the size of a particular font file: the complexity of the vector paths of each glyph and the number of glyphs in a particular font. For example, Open Sans, which is one of the most popular WebFonts, contains 897 glyphs, which include Latin, Greek, and Cyrillic characters.

<figure>
  {% Img src="image/admin/B92rhiBJD9sx88a5CvVy.png", alt="Font glyph table", width="800", height="309" %}
</figure>

When picking a font, it's important to consider which character sets are supported. If you need to localize your page content to multiple languages, you should use a font that can deliver a consistent look and experience to your users. For example, [Google's Noto font family](https://www.google.com/get/noto/) aims to support all the world's languages. Note, however, that the total size of Noto, with all languages included, results in a 1.1GB+ ZIP download.

In this post you will find out how to reduce the delivered filesize of your web fonts.

### Web font formats

Today there are two recommeded font container formats in use on the web:

* [EOT](https://en.wikipedia.org/wiki/Embedded_OpenType)
* [TTF](https://en.wikipedia.org/wiki/TrueType)
* [WOFF](https://en.wikipedia.org/wiki/Web_Open_Font_Format)
* [WOFF2](https://www.w3.org/TR/WOFF2/).

[WOFF](http://caniuse.com/#feat=woff) and [WOFF 2.0](http://caniuse.com/#feat=woff2) enjoy wide support and are supported by all modern browsers.

* Serve WOFF 2.0 variant to modern browsers.
* If absolutely necessary&mdash;such as if you still need to support Internet Explorer 11, for example&mdash;serve the WOFF as a fallback.
* Alternatively, consider not using web fonts for legacy browsers and falling back to the system fonts. This may be more performant for older, more constrained, devices as well.
* Since WOFF and WOFF 2.0 cover all bases for modern and legacy browsers still in use, use of EOT and TTF are no longer necessary and can result in longer web font download times.

### Web fonts and compression


Both WOFF and WOFF 2.0 have built-in compression. WOFF 2.0's internal compression uses Brotli, and offers up to 30% better compression than WOFF. For more information, see [the WOFF 2.0 evaluation report](http://www.w3.org/TR/WOFF20ER/).


Finally, it's worth noting that some font formats contain additional metadata, such as [font hinting](https://en.wikipedia.org/wiki/Font_hinting) and [kerning](https://en.wikipedia.org/wiki/Kerning) information that may not be necessary on some platforms, which allows for further file-size optimization. For example, [Google Fonts](https://fonts.google.com/) maintains 30+ optimized variants for each font and automatically detects and delivers the optimal variant for each platform and browser.

## Define a font family with `@font-face`

The `@font-face` CSS at-rule allows you to define the location of a particular font resource, its style characteristics, and the Unicode codepoints for which it should be used. A combination of such `@font-face` declarations can be used to construct a "font family," which the browser will use to evaluate which font resources need to be downloaded and applied to the current page.

### Consider a variable font

Variable fonts can significantly reduce the filesize of your fonts in cases where you need multiple variants of a font. Instead of needing to load the regular and bold styles plus their italic versions, you can load a single file that contains all of the information. However, variable font file sizes will be larger than an individual font variant—though smaller than the combination of many variants. Rather than one large variable font, it may be better to serve critical font variants first, with other variants downloaded later.

Variable fonts are now supported by all modern browsers, find out more in the [Introduction to variable fonts on the web](/variable-fonts/).

### Select the right format

Each `@font-face` declaration provides the name of the font family, which acts as a logical group of multiple declarations, [font properties](http://www.w3.org/TR/css3-fonts/#font-prop-desc) such as style, weight, and stretch, and the [src descriptor](http://www.w3.org/TR/css3-fonts/#src-desc), which specifies a prioritized list of locations for the font resource.

```css
@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 400;
  src: local('Awesome Font'),
       url('/fonts/awesome.woff2') format('woff2'),
       /* Only serve WOFF if necessary. Otherwise,
          WOFF 2.0 is fine by itself. */
       url('/fonts/awesome.woff') format('woff');
}

@font-face {
  font-family: 'Awesome Font';
  font-style: italic;
  font-weight: 400;
  src: local('Awesome Font Italic'),
       url('/fonts/awesome-i.woff2') format('woff2'),
       url('/fonts/awesome-i.woff') format('woff');
}
```

First, note that the above examples define a single _Awesome Font_ family with two styles (normal and _italic_), each of which points to a different set of font resources. In turn, each `src` descriptor contains a prioritized, comma-separated list of resource variants:

* The `local()` directive allows you to reference, load, and use locally installed fonts. If the user already has the font installed on their system, this bypasses the network entirely, and is the fastest.
* The `url()` directive allows you to load external fonts, and are allowed to contain an optional `format()` hint indicating the format of the font referenced by the provided URL.

{% Aside %}
Unless you're referencing one of the default system fonts, it is rare for the user to have it locally installed, especially on mobile devices, where it is effectively impossible to "install" additional fonts. Even if you start with a `local()` entry, you should always provide a list of `url()` entries for those that do not have it downloaded.
{% endAside %}

When the browser determines that the font is needed, it iterates through the provided resource list in the specified order and tries to load the appropriate resource. For example, following the example above:

1. The browser performs page layout and determines which font variants are required to render the specified text on the page. Fonts that aren't part of the page's [CSS Object Model (CSSOM)](/critical-rendering-path-constructing-the-object-model/#css-object-model-cssom) are not downloaded by the browser, as they are not required.
2. For each required font, the browser checks if the font is available locally.
3. If the font is not available locally, the browser iterates over external definitions:
  * If a format hint is present, the browser checks if it supports the hint before initiating the download. If the browser doesn't support the hint, the browser advances to the next one.
  * If no format hint is present, the browser downloads the resource.

The combination of local and external directives with appropriate format hints allows you to specify all of the available font formats and let the browser handle the rest. The browser determines which resources are required and selects the optimal format.

{% Aside 'important' %}
If you're shipping more formats than just WOFF 2.0, the order in which the font variants are specified matters. The browser picks the first format it supports. Therefore, if you want the newer browsers to use WOFF 2.0, then you should place the WOFF 2.0 declaration above WOFF.
{% endAside %}

### Unicode-range subsetting

In addition to font properties such as style, weight, and stretch, the `@font-face` rule allows you to define a set of Unicode codepoints supported by each resource. This enables you to split a large Unicode font into smaller subsets (for example, Latin, Cyrillic, and Greek subsets) and only download the glyphs required to render the text on a particular page.

The [`unicode-range` descriptor](http://www.w3.org/TR/css3-fonts/#descdef-unicode-range) allows you to specify a comma-delimited list of range values, each of which can be in one of three different forms:

* Single codepoint (for example, `U+416`)
* Interval range (for example, `U+400-4ff`): indicates the start and end codepoints of a range
* Wildcard range (for example, `U+4??`): `?` characters indicate any hexadecimal digit

For example, you can split your _Awesome Font_ family into Latin and Japanese
subsets, each of which the browser downloads on an as-needed basis:

```css
@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 400;
  src: local('Awesome Font'),
       url('/fonts/awesome-l.woff2') format('woff2');
  /* Latin glyphs */
  unicode-range: U+000-5FF;
}

@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 400;
  src: local('Awesome Font'),
       url('/fonts/awesome-jp.woff2') format('woff2');
  /* Japanese glyphs */
  unicode-range: U+3000-9FFF, U+ff??;
}
```

{% Aside 'important' %}
Unicode-range subsetting is particularly important for Asian languages, where the number of glyphs is much larger than in Western languages and a typical "full" font is often measured in megabytes instead of tens of kilobytes.
{% endAside %}

The use of Unicode range subsets and separate files for each stylistic variant of the font allows you to define a composite font family that is both faster and more efficient to download. Visitors only download the variants and subsets they need, and they aren't forced to download subsets that they may never see or use on the page.

Nearly all browsers [support `unicode-range`](https://caniuse.com/font-unicode-range). For compatibility with older browsers you may need to fall back to "manual subsetting". In this case you have to fall back to providing a single font resource that contains all the necessary subsets and hide the rest from the browser. For example, if the page is only using Latin characters, then you can strip other glyphs and serve that particular subset as a standalone resource.

1. **Determine which subsets are needed:**
   * If the browser supports unicode-range subsetting, then it will automatically select the right subset. The page just needs to provide the subset files and specify appropriate unicode-ranges in the `@font-face` rules.
   * If the browser doesn't support unicode-range subsetting, then the page needs to hide all unnecessary subsets; that is, the developer must specify the required subsets.
1. **Generate font subsets:**
   - Use the open-source [pyftsubset tool](https://github.com/behdad/fonttools/) to subset and optimize your fonts.
   - Some font servers—such as Google Font—will automatically subset by default.
   - Some font services allow manual subsetting via custom query parameters, which you can use to manually specify the required subset for your page. Consult the documentation from your font provider.

### Font selection and synthesis

Each font family may be composed of multiple stylistic variants (regular, bold, italic) and multiple weights for each style. Each of which, in turn, may contain very different glyph shapes&mdash;for example, different spacing, sizing, or a different shape altogether.

<figure>
  {% Img src="image/admin/FNtAc2xRmx2MuUt2MADj.png", alt="Font weights", width="697", height="127" %}
</figure>

The above diagram illustrates a font family that offers three different bold weights:

- 400 (regular).
- 700 (bold).
- 900 (extra bold).

All other in-between variants (indicated in gray) are automatically mapped to the closest variant by the browser.

<blockquote>
  <p>
    When a weight is specified for which no face exists, a face with a nearby weight is used. In general, bold weights map to faces with heavier weights and light weights map to faces with lighter weights.
  </p>
  <cite>
    <a href="http://www.w3.org/TR/css3-fonts/#font-matching-algorithm" rel="noopener">CSS font matching algorithm</a>
  </cite>
</blockquote>

Similar logic applies to _italic_ variants. The font designer controls which variants they will produce, and you control which variants you'll use on the page. Because each variant is a separate download, it's a good idea to keep the number of variants small. For example, you can define two bold variants for the _Awesome Font_ family:

```css
@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 400;
  src: local('Awesome Font'),
       url('/fonts/awesome-l.woff2') format('woff2');
  /* Latin glyphs */
  unicode-range: U+000-5FF;
}

@font-face {
  font-family: 'Awesome Font';
  font-style: normal;
  font-weight: 700;
  src: local('Awesome Font'),
       url('/fonts/awesome-l-700.woff2') format('woff2');
  /* Latin glyphs */
  unicode-range: U+000-5FF;
}
```

The above example declares the _Awesome Font_ family that is composed of two resources that cover the same set of Latin glyphs (`U+000-5FF`) but offer two different "weights": normal (400) and bold (700). However, what happens if one of your CSS rules specifies a different font weight, or sets the `font-style` property to `italic`?

- If an exact font match isn't available, the browser substitutes the closest match.
- If no stylistic match is found (for example, no italic variants were declared in the example above), then the browser synthesizes its own font variant.

<figure>
  {% Img src="image/admin/a8Jo2cIO1tPsj71AzftS.png", alt="Font synthesis", width="800", height="356" %}
</figure>

{% Aside 'warning' %}
Be aware that synthesized approaches may not be suitable for scripts like Cyrillic, where italic forms are very different in shape. For proper fidelity in those scripts, use an actual italic font.
{% endAside %}

The example above illustrates the difference between the actual vs. synthesized font results for Open Sans. All synthesized variants are generated from a single 400-weight font. As you can see, there's a noticeable difference in the results. The details of how to generate the bold and oblique variants are not specified. Therefore, the results vary from browser to browser, and are highly dependent on the font.

{% Aside %}
For best consistency and visual results, don't rely on font synthesis. Instead, minimize the number of used font variants and specify their locations, such that the browser can download them when they are used on the page. Or, choose to use a variable font. That said, in some cases a synthesized variant [may be a viable option](https://www.igvita.com/2014/09/16/optimizing-webfont-selection-and-synthesis/), but be cautious in using synthesized variants.
{% endAside %}

## Web font size optimization checklist

- **Audit and monitor your font use:** don't use too many fonts on your pages, and, for each font, minimize the number of used variants. This helps produce a more consistent and a faster experience for your users.
- **Avoid legacy formats if possible:** EOT, TTF, and WOFF formats are larger than WOFF 2.0. EOT and TTF are strictly unnecessary formats, where as WOFF may be acceptable if you need to support Internet Explorer 11. If you're only target modern browsers, using WOFF 2.0 only is the simplest and most performant option.
- **Subset your font resources:** many fonts can be subset, or split into multiple unicode-ranges to deliver just the glyphs that a particular page requires. This reduces the file size and improves the download speed of the resource. However, when defining the subsets, be careful to optimize for font re-use. For example, don't download a different but overlapping set of characters on each page. A
good practice is to subset based on script: for example, Latin, and Cyrillic.
- **Give precedence to `local()` in your `src` list:** listing `local('Font Name')` first in your `src` list ensures that HTTP requests aren't made for fonts that are already installed.
- **Use [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/)** to test for [text compression](https://developer.chrome.com/docs/lighthouse/performance/uses-text-compression/).

## Effects on Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS)

Depending on your page's content, text nodes can be considered candidates for [Largest Contentful Paint (LCP)](/lcp/). It's therefore vital to ensure your web fonts are as small as possible by following the advice in this article so that your users will see the text on your page [as soon as they possibly can](/optimize-webfont-loading/).

If you're concerned that, despite your optimization efforts, page text might take too long to appear because of large web font resource, the `font-display` property has a number of settings that can help you to [avoid invisible text](/avoid-invisible-text) while a font is downloading. However, using the `swap` value may cause significant layout shifts that affect your site's [Cumulative Layout Shift (CLS)](/cls/). Consider using the `optional` or `fallback` values if possible.

If your web fonts are crucial to your branding&mdash;and by extension, the user experience&mdash; consider preloading your fonts so that the browser has a head start on requesting them. This can reduce both the swap period if you use `font-display: swap`, or the blocking period if you're not using `font-display`.

# Remove unused code

Registries like [npm](https://docs.npmjs.com/getting-started/what-is-npm) have
transformed the JavaScript world for the better by allowing anyone to easily
download and use over _half a million_ public packages. But we often include
libraries we're not fully utilizing. To fix this issue, **analyze your bundle**
to detect unused code. Then remove **unused** and **unnecessary** libraries.

## Impact on Core Web Vitals

By removing unused code, you can improve your website's [Core Web Vitals](/vitals/). [Largest Contentful Paint](/lcp/), for example, can be affected by unused code by increased bandwidth contention caused by larger than necessary assets. LCP can also be affected if large JavaScript assets that render markup solely on the client [contain references to LCP candidates](/preload-scanner/#rendering-markup-with-client-side-javascript) by [delaying when these resources can load](/optimize-lcp/#1-eliminate-resource-load-delay).

Other metrics such as [First Input Delay (FID)](/fid/) and [Interaction to Next Paint (INP)](/inp/) can also be affected by unused code, because even unused JavaScript must be downloaded, parsed, compiled, and then executed. The unused code can introduce unnecessary delays in resource load time, memory usage, and main thread activity that contribute to poor page responsiveness.

This guide will help you get a handle on your project's unused code by showing you how to analyze your project's codebase, and offer strategies for pruning unused code from the JavaScript assets you ship to your users in production.

## Analyze your bundle

DevTools makes it easy to see the size of all network requests:
{% Instruction 'devtools-network', 'ol' %}
{% Instruction 'disable-cache', 'ol' %}
{% Instruction 'reload-page', 'ol' %}

{% Img src="image/admin/aq6QZj5p4KTuaWnUJnLC.png", alt="Network panel with bundle request", width="800", height="169" %}

The [Coverage](https://developer.chrome.com/docs/devtools/coverage/)
tab in DevTools will also tell you how much CSS and JS code in your application
is unused.

{% Img src="image/admin/xlPdOMaeykJhYqGcaMJr.png", alt="Code Coverage in DevTools", width="800", height="562" %}

By specifying a full Lighthouse configuration through its Node CLI, an "Unused
JavaScript" audit can also be used to trace how much unused code is being
shipped with your application.

{% Img src="image/admin/tdC0d65gEIiHZy6eyo82.png", alt="Lighthouse Unused JS Audit", width="800", height="347" %}

If you happen to be using [webpack](https://webpack.js.org/) as your bundler,
[Webpack Bundle Analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer)
will help you investigate what makes up the bundle. Include the plugin in your
webpack configurations file like any other plugin:

```js/4
module.exports = {
  //...
  plugins: [
    //...
    new BundleAnalyzerPlugin()
  ]
}
```

Although webpack is commonly used to build single-page applications, other
bundlers, such as [Parcel](https://parceljs.org/) and
[Rollup](https://rollupjs.org/guide/en), also have visualization tools that you
can use to analyze your bundle.

Reloading the application with this plugin included shows a zoomable treemap of
your entire bundle.

{% Img src="image/admin/pLAHEtl5C011wTk2IJij.png", alt="Webpack Bundle Analyzer", width="800", height="468" %}

Using this visualization allows you to inspect which parts of your bundle are
larger than others, as well as get a better idea of all the libraries that
you're importing. This can help identify if you are using any unused or
unnecessary libraries.

## Remove unused libraries

In the previous treemap image, there are quite a few packages within a single
`@firebase` domain. If your website only needs the firebase database component,
update the imports to fetch that library:

```js/1-2/0
import firebase from 'firebase';
import firebase from 'firebase/app';
import 'firebase/database';
```

It is important to emphasize that this process is significantly more complex for
larger applications.

For the mysterious looking package that you're quite sure is not being used
anywhere, take a step back and see which of your top-level dependencies are
using it. Try to find a way to only import the components that you need from it.
If you aren't using a library, remove it.  If the library isn't required for the
initial page load, consider if it can be [lazy loaded](/reduce-javascript-payloads-with-code-splitting).

And in case you're using webpack, check out [the list of plugins
that automatically remove unused code from popular libraries](https://github.com/GoogleChromeLabs/webpack-libs-optimizations).

{% Aside 'codelab' %}
[Remove unused code.](/codelab-remove-unused-code)
{% endAside %}

## Remove unnecessary libraries

Not all libraries can be easily broken down into parts and selectively imported.
In these scenarios, consider if the library could be removed entirely. Building
a custom solution or leveraging a lighter alternative should always be options
worth considering. However, it is important to weigh the complexity and effort
required for either of these efforts before removing a library entirely from an
application.

# Replace animated GIFs with video for faster page loads

Have you ever seen an animated GIF on a service like Imgur or Gfycat, inspected
it in your dev tools, only to find out that GIF was really a video? There's a
good reason for that. Animated GIFs can be downright _huge_.

{% Img src="image/admin/3UZ0b9dDotVIXWQT5Auk.png", alt="DevTools network panel showing a 13.7 MB gif.", width="800", height="155" %}

Thankfully, this is one of those areas of loading performance where you can do
relatively little work to realize huge gains! **By converting large GIFs to
videos, you can save big on users' bandwidth**.

## Measure first

Use Lighthouse to check your site for GIFs that can be converted to videos. In
DevTools, click on the Audits tab and check the Performance checkbox. Then run
Lighthouse and check the report.
If you have any GIFs that can be converted, you should see a suggestion to "Use
video formats for animated content":

{% Img src="image/admin/KOSr9IivnkyaFk6RJ5u1.png", alt="A failing Lighthouse audit, use video formats for animated content.", width="800", height="173" %}

## Create MPEG videos

There are a number of ways to convert GIFs to video,
**[FFmpeg](https://www.ffmpeg.org/)** is the tool used in this guide.
To use FFmpeg to convert the GIF, `my-animation.gif` to an MP4 video, run the
following command in your console:

```bash
ffmpeg -i my-animation.gif -b:v 0 -crf 25 -f mp4 -vcodec libx264 -pix_fmt yuv420p my-animation.mp4
```

This tells FFmpeg to take `my-animation.gif` as the **input**, signified by the
`-i` flag, and to convert it to a video called `my-animation.mp4`.

The libx264 encoder only works with files that have even dimensions, like 320px
by 240px. If the input GIF has odd dimensions you can include a crop filter to
avoid FFmpeg throwing a 'height/width not divisible by 2' error:

```bash
ffmpeg -i my-animation.gif -vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" -b:v 0 -crf 25 -f mp4 -vcodec libx264 -pix_fmt yuv420p my-animation.mp4
```

## Create WebM videos

While MP4 has been around since 1999, WebM is a relatively new file format
initially released in 2010. WebM videos are much smaller than MP4 videos, but
not all browsers support WebM so it makes sense to generate both.

To use FFmpeg to convert `my-animation.gif` to a WebM video, run the following
command in your console:

```bash
ffmpeg -i my-animation.gif -c vp9 -b:v 0 -crf 41 my-animation.webm
```

## Compare the difference

The cost savings between a GIF and a video can be pretty significant.

{% Img src="image/admin/LWzvOWaOdMnNLTPWjayt.png", alt="File size comparison showing 3.7 MB for the gif, 551 KB for the mp4 and 341 KB for the webm.", width="800", height="188" %}

In this example, the initial GIF is 3.7 MB, compared to the MP4 version, which
is 551 KB, and the WebM version, which is only 341 KB!

## Replace the GIF img with a video

Animated GIFs have three key traits that a video needs to replicate:

+  They play automatically.
+  They loop continuously (usually, but it is possible to prevent looping).
+  They're silent.

Luckily, you can recreate these behaviors using the `<video>` element.

```bash
<video autoplay loop muted playsinline></video>
```

A `<video>` element with these attributes plays automatically, loops endlessly,
plays no audio, and plays inline (that is, not full screen), all the hallmark
behaviors expected of animated GIFs! 🎉

Finally, the `<video>` element requires one or more `<source>` child elements
pointing to different video files that the browser can choose from, depending on
the browser's format support. Provide both WebM and MP4, so that if a browser
doesn't support WebM, it can fall back to MP4.

```html
<video autoplay loop muted playsinline>
  <source src="my-animation.webm" type="video/webm">
  <source src="my-animation.mp4" type="video/mp4">
</video>
```

{% Aside 'codelab' %}
[Replace an animated GIF with a video](/codelab-replace-gifs-with-video).
{% endAside %}

{% Aside %}
Browsers don't speculate about which `<source>` is optimal, so the order of
`<source>`'s matters. For example, if you specify an MP4 video first and the
browser supports WebM, browsers will skip the WebM `<source>` and use the MPEG-4
instead. If you prefer a WebM `<source>` be used first, specify it first!
{% endAside %}

## Effect on Largest Contentful Paint (LCP)

It should be noted that while `<img>` elements are candidates for LCP, `<video>` elements without a [`poster` image](https://developer.mozilla.org/docs/Web/HTML/Element/video#attr-poster) are not [LCP candidates](/lcp/#what-elements-are-considered). The solution in the case of emulating animated GIFs is _not_ to add `poster` attribute to your `<video>` elements, because that image will go unused.

What does this mean for your website? The recommendation is to stick with using a `<video>` instead of an animated GIF, but with the understanding that such media will not be a candidate for LCP, and the next largest candidate will be used instead. As GIFs and `<video>`s are typically larger and so slower to download, moving to a different LCP candidate will likely even improve the site's LCP.

# Use WebP images

WebP images are smaller than their JPEG and PNG counterparts—usually on the
magnitude of a 25–35% reduction in filesize. This decreases page sizes and
improves performance.

-  YouTube found that switching to WebP thumbnails resulted in [10%
    faster page loads](https://www.youtube.com/watch?v=rqXMwLbYEE4).
-  Facebook
    [experienced](https://code.fb.com/android/improving-facebook-on-android/) a
    25-35% filesize savings for JPEGs and an 80% filesize savings for PNGs when
    they switched to using WebP.

WebP is an excellent replacement for JPEG, PNG, and GIF images. In addition,
WebP offers both lossless and lossy compression. In lossless compression no data
is lost. Lossy compression reduces file size, but at the expense of possibly
reducing image quality.

## Convert images to WebP

People generally use one of the following approaches for converting their images
to WebP: the
[cwebp command-line tool](https://developers.google.com/speed/webp/docs/using)
or the [Imagemin WebP plugin](https://github.com/imagemin/imagemin-webp) (npm
package).
The Imagemin WebP plugin is generally the best choice if your project uses build
scripts or build tools (e.g. Webpack or Gulp), whereas the CLI is a good choice
for simple projects or if you'll only need to convert images once.

When you convert images to WebP, you have the option to set a wide variety of
compression settings—but for most people the only thing you'll ever need to
care about is the quality setting. You can specify a quality level from 0
(worst) to 100 (best). It's worth playing around with this, find
which level is the right tradeoff between image quality and filesize for your
needs.

### Use cwebp

Convert a single file, using cwebp's default compression settings:

```bash
cwebp images/flower.jpg -o images/flower.webp
```

Convert a single file, using a quality level of `50`:

```bash
cwebp -q 50 images/flower.jpg -o images/flower.webp
```

Convert all files in a directory:

```bash
for file in images/*; do cwebp "$file" -o "${file%.*}.webp"; done
```

### Use Imagemin

The Imagemin WebP plugin can be used by itself or with your favorite build tool
(Webpack/Gulp/Grunt/etc.). This usually involves adding \~10 lines of code to a
build script or the configuration file for your build tool.
Here are examples of how to do that for
[Webpack](https://glitch.com/~webp-webpack),
[Gulp](https://glitch.com/~webp-gulp), and
[Grunt](https://glitch.com/~webp-grunt).

If you are not using one of those build tools, you can use Imagemin by itself as
a Node script. This script will convert the files in the `images` directory and
save them in the `compressed_images` directory.

```js
const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');

imagemin(['images/*'], {
  destination: 'compressed_images',
  plugins: [imageminWebp({quality: 50})]
}).then(() => {
  console.log('Done!');
});
```

## Serve WebP images

If your site only supports WebP compatible
[browsers](https://caniuse.com/#search=webp), you can stop reading. Otherwise,
serve WebP to newer browsers and a fallback image to older browsers:

**Before:**
```html
<img src="flower.jpg" alt="">
```

**After:**
```html
<picture>
  <source type="image/webp" srcset="flower.webp">
  <source type="image/jpeg" srcset="flower.jpg">
  <img src="flower.jpg" alt="">
</picture>
```

The
[`<picture>`](https://developer.mozilla.org/docs/Web/HTML/Element/picture),
[`<source>`](https://developer.mozilla.org/docs/Web/HTML/Element/source),
and `<img>` tags, including how they are ordered relative to each other, all
interact to achieve this end result.

### `<picture>`

The `<picture>` tag provides a wrapper for zero or more `<source>` tags and one `<img>` tag.

### `<source>`

The `<source>` tag specifies a media resource.

The browser uses the first listed source that's in a format it supports. If the browser does not support any of the formats listed in the `<source>` tags, it falls back to loading the image specified by the `<img>` tag.

{% Aside 'gotchas' %}

- The `<source>` tag for the "preferred" image format (in this case that is WebP) should be listed first, before other `<source>` tags.

- The value of the `type` attribute should be the MIME type corresponding to the image format. An image's [MIME type](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types) and its file extension are often similar, but they aren't necessarily the same thing (e.g. `.jpg` vs. `image/jpeg`).

{% endAside %}

### `<img>`

The `<img>` tag is what makes this code work on browsers
that don't support the `<picture>` tag.
If a browser does not support the `<picture>` tag, it will
ignore the tags it doesn't support. Thus, it only "sees" the
`<img src="flower.jpg" alt="">` tag and loads that image.

{% Aside 'gotchas' %}
<ul>
  <li>The `<img>` tag should always be included, and it should always be listed last, after all `<source>` tags.</li>
  <li>The resource specified by the `<img>` tag should be in a universally supported format (e.g. JPEG), so it can be used as a fallback.</li>
</ul>
{% endAside %}

### Reading the HTTP `Accept` header

If you have an application back end or web server that allows you to rewrite requests, you can read the value of the [HTTP `Accept` header](https://developer.mozilla.org/docs/Web/HTTP/Headers/Accept), which will advertise what alternative image formats are supported:

```http
Accept: image/webp,image/svg+xml,image/*,*/*;q=0.8
```

Reading this request header and rewriting the response based on its contents has the benefit of simplifying your image markup. `<picture>` markup can get rather long with many sources. Below is an Apache `mod_rewrite` rule that can serve WebP alternates:

```apacheconf
RewriteEngine On
RewriteCond %{HTTP:Accept} image/webp [NC]
RewriteCond %{HTTP:Content-Disposition} !attachment [NC]
RewriteCond %{DOCUMENT_ROOT}/$1.webp -f [NC]
RewriteRule (.+)\.(png|jpe?g)$ $1.webp [T=image/webp,L]
```

If you go this route, you'll need to set the [HTTP `Vary` response header](https://developer.mozilla.org/docs/Web/HTTP/Headers/Vary) to ensure caches will understand that the image may be served with varying content types:

```apacheconf
<FilesMatch ".(jpe?g|png)$">
  <IfModule mod_headers.c>
    Header set Vary "Content-Type"
  </IfModule>
</FilesMatch>
```

The rewrite rule above will look for a WebP version of any requested JPEG or PNG image. If a WebP alternate is found, it will be served with the proper `Content-Type`  header. This will allow you to use image markup similar to the following with automatic WebP support:

```html
<img src="flower-320w.jpg" srcset="flower-320w.jpg 320w, flower-640w.jpg 640w, flower-960w.jpg 960w">
```

## Verify WebP usage

Lighthouse can be used to verify that all images on your site are being served
using WebP. Run the Lighthouse Performance Audit (**Lighthouse > Options >
Performance**) and look for the results of the **Serve images in next-gen
formats** audit. Lighthouse will list any images that are not being served in
WebP.

# Serve images with correct dimensions

We've all been there: you forgot to scale down an image before adding it to the
page. The image looks fine, but it is wasting users' data and hurting page
performance.

## Identify incorrectly sized images

Lighthouse makes it easy to identify incorrectly-sized images. Run the
Performance Audit (**Lighthouse > Options > Performance**) and look for the
results of the **Properly size images** audit. The audit lists any images that
need to be resized.

## Determine the correct image size

Image sizing can be deceptively complicated. For this reason, we've provided two
approaches: the "good" and the "better." Both will improve performance, but the
"better" approach may take a bit longer to understand and implement. However, it
will also reward you with bigger performance improvements. The best choice for
you is the one that you feel comfortable implementing.

### A quick note on CSS units

There are two types of CSS units for specifying the size of HTML elements,
including images:

-  Absolute units: Elements styled using absolute units will always be
    displayed at the same size, regardless of device. Examples of valid,
    absolute CSS units: px, cm, mm, in.
-  Relative units: Elements styled using relative units will be displayed at
    varying sizes, depending on the relative length specified. Examples of
    valid, relative CSS units: %, vw (1vw = 1% of the width of the viewport),
    em (1.5 em = 1.5 times font size).

### The "Good" Approach

For images with sizing based on…

- **Relative units**: Resize the image to a size that will work across all devices.

You may find it helpful to check your analytics data (e.g. Google
Analytics) to see which display sizes are commonly used by your users.
Alternatively, [screensiz.es](http://screensiz.es/)
provides information about the displays of many common devices.
- **Absolute units**: Resize the image to match the size that it is displayed at.

The DevTools Elements panel can be used to determine what size an image is
displayed at.

{% Img src="image/admin/pKQa0Huu0KGInOekdz6M.png", alt="DevTools element's panel", width="800", height="364" %}

### The "Better" approach

For images with both absolute and relative sizing, use [`srcset`](https://developer.mozilla.org/docs/Web/HTML/Element/source#attr-srcset)
and [`sizes`](https://developer.mozilla.org/docs/Web/HTML/Element/source#attr-sizes)
attributes to serve different images to different display densities.
Read the [guide on responsive images](/serve-responsive-images).

"Display density" refers to the fact that different displays have different
densities of pixels. All other things being equal, a high pixel density
display will look sharper than a low pixel density display.

As a result, multiple image versions are necessary if you want users to
experience the crispest possible images, regardless of the pixel density of
their device.

{% Aside %}
Some sites find that this difference in image quality matters, some find that it
does not.
{% endAside %}

Responsive image techniques make this possible by allowing you to list
multiple image versions and for the device to choose the image that works
best for it.

An image that works across all devices will be unnecessarily large for
smaller devices. Responsive image techniques, specifically [srcset](https://developer.mozilla.org/docs/Web/HTML/Element/source#attr-srcset)
and [sizes](https://developer.mozilla.org/docs/Web/HTML/Element/source#attr-sizes), allow you to specify multiple image versions and for the device to choose
the size that works best for it.

## Resize images

Regardless of the approach that you choose, you may find it helpful to use
ImageMagick to resize your images.
[ImageMagick](https://www.imagemagick.org/script/index.php) is the most popular
command line tool for creating and editing images. Most people can resize images
far more quickly when using the CLI than a GUI-based image editor.

Resize image to 25% the size of the original:

```bash
convert flower.jpg -resize 25% flower_small.jpg
```

Scale image to fit within "200px wide by 100px tall":

```bash
# macOS/Linux
convert flower.jpg -resize 200x100 flower_small.jpg

# Windows
magick convert flower.jpg -resize 200x100 flower_small.jpg
```

If you'll be resizing many images, you may find it more convenient to use a
script or service to automate the process. You can learn more about this in the
Responsive Images guide.

## Avoid layout shifts by specifying dimensions

While this guide discusses image dimensions in the context of reducing the amount of unnecessary bytes downloaded, it's important to note that reserving the correct space for images in the layout is another crucial component of minimizing a page's [Cumulative Layout Shift](/cls/) metric. When serving images in HTML, be sure to use proper `width` and `height` attributes so that the browser knows how much space to allocate in the layout for the image:

```html
<img src="flower.jpg" width="640" height="480" alt="A picture of a siberian iris.">
```

Without these attributes, or the equivalent CSS sizing, the browser has no idea how much space the image will take up until it has loaded. This will cause layout shifts in the document, which can be frustrating to users when content moves after they have started consuming it. This can result in users losing their place when reading, or to "miss" their intended hit target and end up clicking on something else they didn't intend to during page load.

An alternative to providing the width and height explicitly, is to use the  CSS [`aspect-ratio` property](https://developer.mozilla.org/docs/Web/CSS/aspect-ratio) on the image. This has a similar effect on an element's size that `width` and `height` attributes do in the sense that the container will maintain a consistent aspect ratio. However, the difference is that this may result in a different aspect-ratio being used than the image is provided in, so you will likely want to use an `object-fit` setting to ensure the image is not distorted in this explicit 16/9 view:

```css
img {
  aspect-ratio: 16 / 9;
  width: 100%;
  object-fit: cover;
}
```

## Verify

Once you've resized all your images, re-run Lighthouse to verify that you didn't
miss anything.

# Serve modern code to modern browsers for faster page loads

Building websites that work well on all major browsers is a core tenet of an
open web ecosystem. However, this means additional work of ensuring that all of
the code you write is supported in each browser that you plan to target. If you
want to use new JavaScript language features, you need to transpile these
features to backwards-compatible formats for browsers that do not yet support
them.

[Babel](https://babeljs.io/docs/en) is the most widely used tool to compile code
that contains newer syntax into code that different browsers and environments
(such as Node) can understand. This guide assumes you are using Babel, so you
need to follow the [setup instructions](https://babeljs.io/setup) to
include it into your application if you haven't already. Select `webpack`
in `Build Systems` if you are using webpack as the module bundler in your app.

{% Aside %}
Fun fact: Lebab is a separate library that does the opposite of what Babel does.
It converts older code into newer syntax.
{% endAside %}

To use Babel to only transpile what is needed for your users, you
need to:

1. Identify which browsers you want to target.
2. Use `@babel/preset-env` with appropriate browser targets.
3. Use `<script type="module">` to stop sending transpiled code to browsers that don't need it.

## Identify which browsers you want to target

Before you begin to modify how the code in your application is transpiled, you
need to identify which browsers access your application. Analyze which browsers
your users are currently using as well as those that you plan to target to make an
informed decision.

## Use @babel/preset-env

Transpiling code usually results in a file that is larger in file size than
their original forms. By minimizing the amount of compilation that you do you
can reduce the size of your bundles to improve the performance of a web page.

Instead of including specific plugins to selectively compile certain language
features you are using, Babel provides a number of presets that bundles plugins
together. Use [@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env)
to only include the transforms and polyfills needed for the browsers you plan on
targeting.

Include `@babel/preset-env` within the `presets` array in your Babel
configurations file, `.babelrc`:

```json
{
 "presets": [
   [
     "@babel/preset-env",
     {
       "targets": ">0.25%"
     }
   ]
 ]
}
```

Use the `targets` field to specify which browser versions you want to include
by adding an appropriate query to the `browsers` field. `@babel/preset-env`
integrates with browserslist, an open-source configuration shared between different
tools for targeting browsers. A full list of compatible queries is in the
[browserslist documentation](https://github.com/browserslist/browserslist#full-list).
Another option is to use a [`.browserslistrc`](https://babeljs.io/docs/en/babel-preset-env#browserslist-integration) file to list the environments
you wish to target.

The `">0.25%"` value tells Babel to only include the transforms
needed to support browsers that make up more than 0.25% of global
usage. This ensures your bundle does not contain unnecessary transpiled
code for browsers that are used by a very small percentage of users.

In most cases, this is a better approach than using the following
configuration:

```json
  "targets": "last 2 versions"
```

The `"last 2 versions"` value transpiles your code for the
[last two versions](http://browserl.ist/?q=last+2+versions) of every browser,
which means support is provided for discontinued browsers such as Internet Explorer.
This can unnecessarily increase the size of your bundle if you do not expect these
browsers to be used to access your application.

Ultimately, you should select the appropriate combination of queries to only
target browsers that fit your needs.

### Enable modern bugfixes

`@babel/preset-env` groups multiple JavaScript syntax features into collections and enables/disables
them based on the target browsers specified. Although this works well, an entire collection of
syntax features is transformed when a targeted browser contains a bug with just a single feature.
This often results in more transformed code than is necessary.

Originally developed as a [separate preset](https://github.com/babel/preset-modules), the
[bugfixes option](https://babeljs.io/docs/en/babel-preset-env#bugfixes) in `@babel/preset-env`
solves this problem by converting modern syntax that is broken in some browsers to the closest
equivalent syntax that is not broken in those browsers. The result is nearly identical modern code
with a few small syntax tweaks that guarantee compatibility in all target browsers. To use this
optimization, make sure you have `@babel/preset-env` 7.10 or later installed, then set the
[`bugfixes`](https://babeljs.io/docs/en/babel-preset-env#bugfixes) property to `true`:

```json
{
 "presets": [
   [
     "@babel/preset-env",
     {
       "bugfixes": true
     }
   ]
 ]
}
```

In Babel 8, the `bugfixes` option will be enabled by default.

## Use `<script type="module">`

JavaScript modules, or ES modules, are a relatively new feature supported in
[all major browsers](https://caniuse.com/#feat=es6-module). You can use modules
to create scripts that can import and export from other modules, but you can
also use them with `@babel/preset-env` to only target browsers that support
them.


Instead of querying for specific browser versions or market share, consider
specifying `"esmodules" : true` inside your `.babelrc` file's `targets` field.

```json
{
   "presets":[
      [
         "@babel/preset-env",
         {
            "targets":{
               "esmodules": true
            }
         }
      ]
   ]
}
```

Many newer ECMAScript features compiled with Babel are already supported
in environments that support JavaScript modules. So by doing this, you
simplify the process of making sure that only transpiled code is used
for browsers that actually need it.

Browsers that support modules ignore scripts with a `nomodule` attribute.
Conversely, browsers that do not support modules ignore script elements with
`type="module"`. This means you can include a module as well as a compiled fallback.

Ideally, the two version scripts of an application are included like this:

```html
  <script type="module" src="main.mjs"></script>
  <script nomodule src="compiled.js" defer></script>
```

Browsers that support modules fetch and execute `main.mjs` and ignore `compiled.js`.
The browsers that do not support modules do the opposite.

{% Aside %}
  Module scripts are deferred by default. The `defer` attribute is added to the
  `nomodule` script for the same behavior.
{% endAside %}

If you use webpack, you can set different targets in your configurations for two
separate versions of your application:

* A version only for browsers that support modules.
* A version that includes a compiled script which works in any legacy browser. This has a larger file size, since transpilation needs to support a wider range of browsers.

{% Aside %}
  Although this HTML approach can provide performance benefits, certain browsers have been found to
  double-fetch when specifying both module and nomodule scripts. Jason Miller's [Modern Script
  Loading](https://jasonformat.com/modern-script-loading/) explains this in more detail and covers a
  few options that can be used to circumvent this.
{% endAside %}

_With thanks to Connor Clark and Jason Miller for their reviews._

# Serve responsive images

Serving desktop-sized images to mobile devices can use 2–4x more data than
needed. Instead of a "one-size-fits-all" approach to images, serve different
image sizes to different devices.

## Responsive images and Core Web Vitals

When you serve responsive images, you're evaluating the display capabilities of
the user's device and choosing one of a set of image candidates that are optimal
for display based on those criteria. The result&mdash;as stated previously&mdash;is
rather than delivering too much image data to devices that won't benefit from it,
you're serving an appropriately sized image for the device. For smaller devices such
as phones and tablets, this equates to reduced data usage as compared to devices
with larger screens, such as laptops.

The effects of faster image loading can also extend to your page's [Largest Contentful Paint (LCP)](/lcp/).
For example, if your page's [LCP element](/lcp/#what-elements-are-considered) is an image,
you're reducing that LCP candidate's [resource load time](/optimize-lcp/#3-reduce-resource-load-time).

Lower resource load times will lower the load time for an LCP image, which will improve
the page's LCP score. A lower LCP means users will perceive that your site is loading faster,
particularly the largest piece of content visible in the viewport during page load. Serving
responsive images can also reduce bandwidth contention for other resources on the page,
which can improve how fast your page loads in general.

## Resize images

Two of the most popular image resizing tools are the [sharp npm
package](https://www.npmjs.com/package/sharp) and the [ImageMagick CLI
tool](https://www.imagemagick.org/script/index.php).

The sharp package is a good choice for automating image resizing (for example,
generating multiple sizes of thumbnails for all the videos on your website). It
is fast and easily integrated with build scripts and tools. On the other hand,
ImageMagick is convenient for one-off image resizing because it is used entirely
from the command line.

### sharp

To use sharp as a Node script, save this code as a separate script in your project,
and then run it to convert your images:

```javascript
const sharp = require('sharp');
const fs = require('fs');
const directory = './images';

fs.readdirSync(directory).forEach(file => {
  sharp(`${directory}/${file}`)
    .resize(200, 100) // width, height
    .toFile(`${directory}/${file}-small.jpg`);
  });
```

### ImageMagick

To resize an image to 33% of its original size, run the following command in
your terminal:

```bash
convert -resize 33% flower.jpg flower-small.jpg
```

To resize an image to fit within 300px wide by 200px high, run the following command:

```bash
# macOS/Linux
convert flower.jpg -resize 300x200 flower-small.jpg

# Windows
magick convert flower.jpg -resize 300x200 flower-small.jpg
```

### How many image versions should you create?

There is no single "correct" answer to this question. However, it's common to
serve 3-5 different sizes of an image. Serving more image sizes is better for
performance, but will take up more space on your servers and require writing a
tiny bit more HTML.

### Other options

Image services like [Thumbor](https://github.com/thumbor/thumbor) (open-source)
and [Cloudinary](https://cloudinary.com/) are also worth checking out. Image
services provide responsive images (and image manipulation) on-demand. Thumbor
is setup by installing it on a server; Cloudinary takes care of these details
for you and requires no server setup. Both are easy ways to create responsive
images.

## Serve multiple image versions

Specify multiple image versions and the browser will choose the best one to
use:

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th><strong>Before</strong></th>
        <th><strong>After</strong></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>
          &lt;img src="flower-large.jpg"&gt;
        </td>
        <td>
          &lt;img src="flower-large.jpg" srcset="flower-small.jpg 480w,
          flower-large.jpg 1080w" sizes="50vw"&gt;
        </td>
      </tr>
    </tbody>
  </table>
</div>


The `<img>` tag's
[`src`](https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-src),
[`srcset`](https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-srcset),
and
[`sizes`](https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-sizes)
attributes all interact to achieve this end result.

### The "src" attribute

The src attribute makes this code work for browsers that don't
[support](https://caniuse.com/#search=srcset) the `srcset` and `sizes`
attributes. If a browser does not support these attributes, it will fall back to
loading the resource specified by the `src` attribute.

{% Aside 'gotchas' %}
The resource specified by the `src` attribute should be large enough to work
well on all device sizes.
{% endAside %}

### The "srcset" attribute

The `srcset` attribute is a comma-separated list of image filenames and their
width or density descriptors.

This example uses
[width descriptors](https://www.w3.org/TR/html5/semantics-embedded-content.html#width-descriptor).
`480w` is a width descriptor tells the browser that `flower-small.jpg` is
480px wide; `1080w` is a width descriptor tells the browser that
`flower-large.jpg` is 1080px wide.

"Width descriptor" sounds fancy but is just a way to tell the browser the width
of an image. This saves the browser from needing to download the image to
determine its size.

{% Aside 'gotchas' %}
Use the `w` unit (instead of `px`) to write width descriptors. For example, a
1024px wide image would be written as `1024w`.
{% endAside %}

**Extra Credit:**
You don't need to know about density descriptors to serve different image sizes.
However, if you're curious about how density descriptors work, check out the
[Resolution Switching code lab](/codelab-density-descriptors).
Density descriptors are used to serve different images based on the device's
[pixel density](https://en.wikipedia.org/wiki/Pixel_density).

### The "sizes" attribute

The sizes attribute tells the browser how wide the image will be when it is
displayed. However, the sizes attribute has no effect on display size; you
still need CSS for that.

The browser uses this information, along with what it knows about the user's
device (i.e. its dimensions and pixel density), to determine which image to
load.

If a browser does not recognize the "`sizes`" attribute, it will fallback to
loading the image specified by the "`src`" attribute. (Browsers shipped support
for the "`sizes`" and "`srcset`" attributes at the same time, so a browser will
either support both attributes or neither.)

{% Aside 'gotchas' %}
Slot width can be specified using a variety of units. The following are all
valid sizes:

- `100px`
- `33vw`
- `20em`
- `calc(50vw-10px)`

The following is not a valid size:

+  `25%` (percentages cannot be used with the sizes attribute)
{% endAside %}

**Extra Credit:**
If you want to be fancy, you can also use the sizes attribute to specify
multiple slot sizes. This accommodates websites that use different layouts for
different viewport sizes. Check out this [multiple slot code sample](/codelab-specifying-multiple-slot-widths)
to learn how to do this.

### (Even more) Extra Credit

In addition to all the extra credit already listed (images are complex!), you
can also use these same concepts for
[art direction](https://developer.mozilla.org/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#Art_direction).
Art direction is the practice of serving completely different looking images
(rather than different versions of the same image) to different viewports. You
can learn more in the [Art Direction code lab](/codelab-art-direction).

## Verify

Once you've implemented responsive images, you can use Lighthouse to make sure
that you didn't miss any images. Run the Lighthouse Performance Audit
(**Lighthouse > Options > Performance**) and look for the results of the
**Properly size images** audit. These results will list the images that need to
be resized.

# Signed Exchanges (SXGs)

Signed exchanges (SXG) are a delivery mechanism that makes it possible to authenticate the origin of a resource independently of how it was delivered. Implementing SXG can improve Largest Contentful Paint (LCP) by enabling [privacy-preserving cross-origin prefetch](#speeding-up-page-loads-with-signed-exchanges). Additionally, this decoupling advances a variety of use cases such as offline internet experiences and serving from third-party caches.

This article provides a comprehensive overview of SXG: how it works, use cases, and tooling.

## Browser compatibility {: #browser-compatibility }

SXG is [supported](https://caniuse.com/#feat=sxg) by Chromium-based browsers
(starting with versions: Chrome 73, Edge 79, and Opera 64).

## Overview

As its primary use case, SXG uses a cache to prefetch and serve content that has been cryptographically signed by the origin. This helps speed cross origin navigations from referer sites while also ensuring that pages remain unaltered and properly attributed to their origin. Any potentially identifying information is hidden until after the user navigates to a site thereby protecting the user’s privacy.
Google Search is an early adopter of SXG prefetching capabilities and for sites that receive a large portion of their traffic from Google Search, SXG can be an important tool for delivering faster page loads to users. Over time, we hope this impact will expand to additional referrers. 

### How it Works

A site signs a request/response pair (an "HTTP exchange") in a way that makes it possible for
the browser to verify the origin and integrity of the content independently of
how the content was distributed. As a result, the browser can display the URL of
the origin site in the address bar, rather than the URL of the server that
delivered the content.

{% Img src="image/GiC3si2rdySvEXRggAJBPhgTHFr2/kGUL8NalZ2OaC81a5q8g.PNG", alt="Diagram explaining how Signed Exchanges Works. Browser communicating with the cache which communicates with the destination site", width="800", height="392" %}

Historically, the only way for a
site to use a third-party to distribute its content while maintaining
attribution has been for the site to share its SSL certificates with the
distributor. This has security drawbacks; moreover, it is a far stretch from
making content truly portable.

### The SXG format

An SXG is encapsulated in a [binary-encoded](https://cbor.io/) file that has two
primary components: an HTTP exchange and a
[signature](https://developer.mozilla.org/docs/Glossary/Signature/Security)
that covers the exchange. The HTTP exchange consists of a request URL, content
negotiation information, and an HTTP response.

{% Details %}
{% DetailsSummary %}

Here's an example of a decoded SXG file.
{% endDetailsSummary %}

```html
format version: 1b3
request:
  method: GET
  uri: https://example.org/
  headers:
response:
  status: 200
  headers:
    Cache-Control: max-age=604800
    Digest: mi-sha256-03=kcwVP6aOwYmA/j9JbUU0GbuiZdnjaBVB/1ag6miNUMY=
    Expires: Mon, 24 Aug 2020 16:08:24 GMT
    Content-Type: text/html; charset=UTF-8
    Content-Encoding: mi-sha256-03
    Date: Mon, 17 Aug 2020 16:08:24 GMT
    Vary: Accept-Encoding
signature:
    label;cert-sha256=*ViFgi0WfQ+NotPJf8PBo2T5dEuZ13NdZefPybXq/HhE=*;
    cert-url="https://test.web.app/ViFgi0WfQ-NotPJf8PBo2T5dEuZ13NdZefPybXq_HhE";
    date=1597680503;expires=1598285303;integrity="digest/mi-sha256-03";sig=*MEUCIQD5VqojZ1ujXXQaBt1CPKgJxuJTvFlIGLgkyNkC6d7LdAIgQUQ8lC4eaoxBjcVNKLrbS9kRMoCHKG67MweqNXy6wJg=*;
    validity-url="https://example.org/webpkg/validity"
header integrity: sha256-Gl9bFHnNvHppKsv+bFEZwlYbbJ4vyf4MnaMMvTitTGQ=

The exchange has a valid signature.
payload [1256 bytes]:
<!doctype html>
<html>
<head>
    <title>SXG example</title>
    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <style type="text/css">
    body {
        background-color: #f0f0f2;
        margin: 0;
        padding: 0;
    }
    </style>
</head>
<body>
<div>
    <h1>Hello</h1>
</div>
</body>
</html>
```

{% endDetails %}

The `expires` parameter in the signature indicates a SXG's expiration date. A
SXG may be valid for at most 7 days. Find more information on
the signature header in the [Signed HTTP Exchanges
spec](https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#section-3.1).

#### Support for server-side personalization

An SXG containing a `Vary: Cookie` header will be shown only to users who don't
have cookies for the signed request URL. If your site presents different HTML
to its logged-in users, you can use this feature to take advantage of SXGs
without altering that experience. See details on [server-side personalization
with Dynamic SXG](/blog/sxg-desktop/#support-for-server-side-personalization).

### Web Packaging

SXG is part of the broader [Web
Packaging](https://github.com/WICG/webpackage) spec proposal family. In addition
to SXGs, the other major component of the Web Packaging spec is [Web Bundles](/web-bundles/)
("bundled HTTP exchanges"). Web Bundles are a collection of HTTP resources and
the metadata necessary to interpret the bundle.

The relationship between SXGs and Web Bundles is a common point of confusion.
SXG and Web Bundles are two distinct technologies that don't depend on each
other—Web Bundles can be used with both signed and unsigned exchanges. A common
goal advanced by both SXGs and Web Bundles is the creation of a "web packaging"
format that allows sites to be shared in their entirety for offline consumption.


## Speeding up Page Loads with Signed Exchanges

Enabling Signed Exchanges can help speed up web page performance and thereby impact your site’s Core Web Vitals, in Particular [Largest Contentful Paint (LCP)](/lcp/). As an early adopter, Google Search uses SXG to provide users with a faster page load experience for pages loaded from the search results page. 


Google Search crawls and caches SXGs when available and prefetches SXG that the user is likely to visit—for example, the page corresponding to the first search result.

SXG works best in tandem with other performance optimizations such as use of CDNs and reduction of render-blocking subresources. After implementing, follow [these recommendations](https://developer.chrome.com/blog/optimizing-lcp-using-signed-exchanges/) to maximize the LCP benefit from prefetching SXGs. In many cases, such optimization can result in nearly instant page loads coming from Google Search:

{% Video src="video/rULxC7pPw3PFS4o9xr7v8isFmCv1/MQwtXWQD41XWNTzRHLie.mp4", controls=true, poster="image/rULxC7pPw3PFS4o9xr7v8isFmCv1/cdP5lEt76GS8N3Bix2X2.jpg" %}

### Impact of Signed Exchanges 

From past experiments we have observed an average of 300ms to 400ms reduction in LCP from SXG-enabled prefetches. This helps sites make a better first-impression on users and often has a positive impact on business metrics. 

Several global brands and sites have already benefited from Signed Exchanges. As a case study, lets look at how implementing Signed Exchanges helped [RebelMouse](https://www.rebelmouse.com/signed-exchange?utm_medium=post&utm_source=google&utm_campaign=signed-exchange-co-marketing&utm_content=signed-exchange), a prominent Content Management System (CMS), improve their customers' performance and business metrics:

- Narcity **improved LCP by 41%**
- Paper Magazine noticed a **27% increase in Sessions per user** 
- MLT Blog **decreased Page Load time by 21%**

[Cloudflare found](https://blog.cloudflare.com/automatic-signed-exchanges-desktop-android/) that SXG **improved TTFB for 98% of sites** it tested, and **improved LCP for 85% of sites**, with a median improvement of over 20% in SXG-eligible page loads.

{% Aside %}
SXG enables [cross-origin prefetching](https://developer.chrome.com/blog/cross-origin-prefetch/) because it allows prefetch requests via an SXG cache server operated by the referrer, ensuring that the user's potentially identifying information is not revealed to the origin server until they navigate. This technique is available to any site that wishes to make its outlinks faster or more resilient to limited network access.
{% endAside %}

#### Indexing

The SXG and non-SXG representations of a page are not ranked or indexed
differently by Google Search. SXG is ultimately a delivery mechanism—it does not
change the underlying content. 

### AMP

AMP content can be delivered using SXG. SXG allows AMP content to be prefetched
and displayed using its canonical URL, rather than its AMP URL.AMP has its own separate
[tooling](https://github.com/ampproject/amppackager) for generating SXGs.Learn how to serve AMP using signed exchanges on
[amp.dev](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/signed-exchange/).


## Debugging SXGs with Chrome DevTools {: #debugging }

To see a SXG firsthand, use a Chromium browser, open DevTools, open the Network panel, and visit this [example search page](https://www.google.com/search?q=site%3Asigned-exchange-testing.dev+valid). Signed Exchanges can be identified by looking for `signed-exchange` in the **Type** column.

<figure>
  {% Img src="image/admin/cNdohSaeXqGHFBwD7L3B.png", alt="Screenshot showing a SXG request within the 'Network' panel in DevTools", width="696", height="201" %}
  <figcaption>The <b>Network</b> panel in DevTools</figcaption>
</figure>

The **Preview** tab provides more information about the contents of a SXG.

<figure>
  {% Img src="image/admin/E0rBwuxk4BxFmLJ3gXhP.png", alt="Screenshot of the 'Preview' tab for a SXG", width="800", height="561" %}
  <figcaption>The <b>Preview</b> tab in DevTools</figcaption>
</figure>

## Tooling

Implementing SXGs consists of generating the SXG corresponding to a given URL
and then serving that SXG to requestors (usually crawlers). 

### Certificates

To generate a SXG you will need a certificate that can sign SXGs, although some tools acquire these automatically. [This page](https://github.com/google/webpackager/wiki/Certificate-Authorities) lists the certificate authorities that can issue this type of certificate.
Certificates can be obtained automatically from the Google certificate authority using any ACME client. Web Packager Server has a built-in ACME client, and sxg-rs will soon.

{% Aside %}
Production use of SXGs requires a certificate that supports the
`CanSignHttpExchanges` extension. Per
[spec](https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#section-4.2),
certificates with this extension must have a validity period no longer than 90
days and require that the requesting domain have a [DNS CAA
record](https://en.wikipedia.org/wiki/DNS_Certification_Authority_Authorization)
configured.
{% endAside %}

### Platform-specific SXG tooling

These tools support specific technology stacks. If you are already using a
platform supported by one of these tools, you may find it easier to set up than
a general-purpose tool.

- [`sxg-rs/cloudflare_worker`](https://github.com/google/sxg-rs/tree/main/cloudflare_worker)
  runs on [Cloudflare Workers](https://workers.cloudflare.com/).

- [`sxg-rs/fastly_compute`](https://github.com/google/sxg-rs/tree/main/fastly_compute)
  runs on [Fastly
  Compute@Edge](https://www.fastly.com/products/edge-compute/serverless).

- [Automatic Signed
  Exchanges](https://blog.cloudflare.com/automatic-signed-exchanges/) is a
  Cloudflare feature that automatically acquires certificates and generates
  Signed Exchanges.

- [NGINX SXG module](https://github.com/google/nginx-sxg-module) generates
  and serves SXGs for sites that use [nginx](https://nginx.org/). Setup
  instructions can be found [here](/how-to-set-up-signed-http-exchanges/).

- [Envoy SXG
  Filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/sxg_filter)
  generates and serves SXGs for sites that use
  [Envoy](https://www.envoyproxy.io/).

### General-purpose SXG tooling

#### sxg-rs HTTP server

The [sxg-rs
http\_server](https://github.com/google/webpackager/blob/main/cmd/webpkgserver/README.md)
acts as a [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) for
serving SXGs. For requests from SXG crawlers, `http_server` will sign the
responses from the backend and respond with an SXG. For installation
instructions, see [the
README](https://github.com/google/webpackager/blob/main/cmd/webpkgserver/README.md).

#### Web Packager Server

The [Web Packager
Server](https://github.com/google/webpackager/blob/main/cmd/webpkgserver/README.md),
`webpkgserver`, is an alternative to sxg-rs http\_server, written in Go. For
instructions on setting up the Web Packager server, see [How to set up signed
exchanges using Web Packager](/signed-exchanges-webpackager).

#### Web Packager CLI

The [Web Packager CLI](https://github.com/google/webpackager) generates a SXG
corresponding to a given URL.

```shell
webpackager \
    --private\_key=private.key \
    --cert\_url=https://example.com/certificate.cbor \
    --url=https://example.com
```

Once the SXG file has been generated, upload it to your server and serve it with
the `application/signed-exchange;v=b3` MIME type. In addition, you will need to
serve the SXG certificate as `application/cert-chain+cbor`.

### SXG libraries

These libraries could be used to build your own SXG generator:

- [`sxg_rs`](https://github.com/google/sxg-rs/tree/main/sxg_rs) is a Rust library for
  generating SXGs. It is the most featureful SXG library and is used as the
  basis for the `cloudflare_worker` and `fastly_compute` tools.

- [`libsxg`](https://github.com/google/libsxg) is a minimal C library for
  generating SXGs. It is used as the basis for the NGINX SXG module and the
  Envoy SXG Filter.

- [`go/signed-exchange`](https://github.com/WICG/webpackage/tree/main/go/signedexchange)
  is a minimal Go library provided by the webpackage specification as a
  [reference
  implementation](https://en.wikipedia.org/wiki/Reference_implementation) of
  generating SXGs. It is used as the basis for its reference CLI tool,
  [`gen-signedexchange`](https://github.com/WICG/webpackage/tree/main/go/signedexchange)
  and the more featureful Web Packager tools.
  
### Content negotiation

Servers should serve SXG when the Accept header indicates that the q-value for application/signed-exchange is greater than or equal to the q-value for text/html. In practice, this means that an origin server will serve SXG to crawlers, but not browsers. Many of the above tools do this by default, but for other tools, the following regular expression can be used to match the Accept header of requests that should be served as SXG:
```http
Accept: /(^|,)\s\*application\/signed-exchange\s\*;\s\*v=[[:alnum:]\_-]+\s\*(,|$)/
```

[This recommendation](https://github.com/google/webpackager/blob/main/cmd/webpkgserver/README.md#content-negotiation) includes examples for Apache and nginx.

{% Aside %}
SXG can deliver superior performance when used with caching or prefetching. However, for content that is loaded directly from the origin server without the benefit of these optimizations, text/html delivers better performance than SXG. Serving content as SXG allows crawlers and other intermediaries to cache SXGs for faster delivery to users.
{% endAside %}

### Update cache API

The Google SXG Cache has an API that site owners can use to remove SXGs from the cache before they've expired due to `Cache-Control: max-age`. See the [update cache API reference](https://github.com/google/webpackager/blob/main/docs/update_cache_api.md) for details.

### Linking to SXG

Any site can cache, serve, and prefetch SXGs of the pages that it links to, where available, using the <link> and <a> tags:
```html
<a href="https://example.com/article.html.sxg">
<link rel="prefetch" as="document" href="https://example.com/article.html.sxg">
```
[This article](/how-to-distribute-signed-http-exchanges/) illustrates how to use nginx to distribute SXGs.

## Unique Advantages

SXG is one of many possible technologies to enable cross-origin prefetching. When deciding which technology to use, you may need to trade off between optimizing different aspects. The following sections illustrate a few of the unique values that SXG provides in the space of possible solutions. These factors may change over time as the space of available solutions evolves.

### Fewer requests to serve

With cross-site prefetching, your server may need to serve additional requests. This corresponds to cases where a page was prefetched, but either the user didn't visit the page, or the prefetched bytes couldn't be shown to the user. For SXG, these additional unused requests can be significantly reduced:

- SXGs are cached and may be sent to users until they expire. Thus, many prefetches can be handled solely by the cache server.
- SXGs can be shown to users both with and without cookies on your site. Thus, there are fewer times when the page will need to be fetched again after navigation.

### Page speed improvement

You may see additional page speed improvement due to the prefetch surfaces and capabilities it currently supports:

- SXGs can be shown to users with cookies for your site.
- SXG also prefetches subresources for your pages, such as JavaScript, CSS, fonts, and images, when specified using a `Link` header.
- In the near future, SXG prefetching from Google Search will be available on more search result types.

## Conclusion
 
Signed Exchanges are a delivery mechanism that make it possible to verify the
origin and validity of a resource independently of how the resource was
delivered. As a result, SXGs can be distributed by third-parties while
maintaining full publisher attribution.

## Further reading

*   [Draft spec for Signed HTTP Exchanges](https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html)
*   [Web Packaging explainers](https://github.com/WICG/webpackage/tree/main/explainers)
*   [Get started with signed exchanges on Google Search](https://developers.google.com/search/docs/advanced/experience/signed-exchange)
*   [How to set up Signed Exchanges using Web Packager](/signed-exchanges-webpackager)
*   [Demo of Signed Exchanges](https://signed-exchange-testing.dev/)

# How to set up Signed Exchanges using Web Packager

A [signed exchange (SXG)](/signed-exchanges) is a delivery mechanism that makes it
possible to authenticate the origin of a resource independently of how it was delivered.
The following instructions explain how to set up Signed Exchanges using
[Web Packager](https://github.com/google/webpackager). Instructions are included for
both self-signed and `CanSignHttpExchanges` certificates.


## Serve SXGs using a self-signed certificate

Using a self-signed certificate to serve SXGs is primarily used for
demonstration and testing purposes. SXGs signed with a self-signed certificate
will generate error messages in the browser when used outside of testing
environments and should not be served to crawlers.

### Prerequisites

To follow these instructions you will need to have
[openssl](https://github.com/openssl/openssl#build-and-install) and
[Go](https://golang.org/doc/install) installed in your development environment.


### Generate a self-signed certificate

This section explains how to generate a self-signed certificate that can be
used with signed exchanges.

#### Instructions

1. Generate a private key.

    ```shell
    openssl ecparam -out priv.key -name prime256v1 -genkey
    ```

    The private key will be saved as a file named `priv.key`.

2. Create a [certificate signing
   request](https://en.wikipedia.org/wiki/Certificate_signing_request) (CSR).

    ```shell
    openssl req -new -sha256 -key priv.key -out cert.csr -subj '/O=Web Packager Demo/CN=example.com'
    ```

    A certificate signing request is a block of encoded text that conveys the
    information necessary to request a certificate from a [certificate authority(CA)](https://en.wikipedia.org/wiki/Certificate_authority). Although you will not be requesting a certificate from a
    CA, it is still necessary to create a certificate signing request.

    The command above creates a certificate signing request for an organization
    named `Web Packager Demo` that has the [common
    name](https://knowledge.digicert.com/solution/SO7239.html) `example.com`. The
    common name should be the fully qualified domain name of the site that contains
    the content that you want to package as SXG.

    In a production SXG setup, this would be a site that you own. However, in a
    testing environment like the one described in these instructions, it can be any
    site.

3. Create a certificate that has the `CanSignHttpExchanges` extension.

    ```shell
    openssl x509 -req -days 90 -in cert.csr -signkey priv.key -out cert.pem -extfile <(echo -e "1.3.6.1.4.1.11129.2.1.22 = ASN1:NULL\nsubjectAltName=DNS:example.com")
    ```

    This command uses the private key and the CSR created in steps 1 and 2 to create the
    certificate file `cert.pem`. The `-extfile` flag associates the certificate with
    the `CanSignHttpExchanges` certificate extension (`1.3.6.1.4.1.11129.2.1.22` is
    the [object
    identifier](https://access.redhat.com/documentation/en-us/red_hat_certificate_system/9/html/administration_guide/standard_x.509_v3_certificate_extensions)
    for the `CanSignHttpExchanges` extension). In addition, the `-extfile` flag also
    defines `example.com` as a [Subject Alternative
    Name](https://en.wikipedia.org/wiki/Subject_Alternative_Name).

    If you are curious about the contents of `cert.pem`, you can view them using the
    following command:

    ```shell
    openssl x509 -in cert.pem -noout -text
    ```

    You are done creating private keys and certificates. You will need the
    `priv.key` and `cert.pem` files in the next section.


### Setup the Web Packager server for testing


#### Prerequisites



1. Install [Web Packager](https://github.com/google/webpackager).

    ```shell
    git clone https://github.com/google/webpackager.git
    ```



2. Build `webpkgserver`.

    ```shell
    cd webpackager/cmd/webpkgserver
    go build .
    ```

    `webpkgserver` is a specific binary within the Web Packager project.



3. Verify that `webpkgserver` has been installed correctly.

    ```shell
    ./webpkgserver --help
    ```

    This command should return information about the usage of `webpkgserver`. If
    this does not work, a good first troubleshooting step is to verify that your
    [GOPATH](https://golang.org/doc/gopath_code.html#GOPATH) is configured
    correctly.


#### Instructions



1. Navigate to the `webpkgserver` directory (you might already be in this
   directory).

    ```shell
    cd /path/to/cmd/webpkgserver
    ```



2. Create a `webpkgsever.toml` file by copying the example.

    ```shell
    cp ./webpkgserver.example.toml ./webpkgserver.toml
    ```

    This file contains the configuration options for `webpkgserver`.



3. Open `webpkgserver.toml` with an editor of your choice and make the following
   changes:
    *   Change the line `#AllowTestCert = false` to `AllowTestCert = true`.
    *   Change the line `PEMFile = 'path/to/your.pem'` to reflect the path to
        the PEM certificate, `cert.pem`, that you created. Do not change the
        line mentioning `TLS.PEMFile`—this is a different configuration option.
    *   Change the line `KeyFile = 'priv.key'` to reflect the path of the
        private key, `priv.key`, that you created. Do not change the line
        mentioning `TLS.KeyFile`—this is a different configuration option.
    *   Change the line `#CertURLBase = '/webpkg/cert'` to `CertURLBase =
        'data:'`. `CertURLBase` indicates the serving location of the SXG
        certificate. This information is used to set the `cert-url` parameter in
        the
        [`Signature`](https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#name-the-signature-header)
        header of the SXG. In production environments, `CertURLBase` is used
        like this: `CertURLBase = 'https://mysite.com/'`. However, for local
        testing, `CertURLBase = 'data:'` can be used to instruct `webpkgserver`
        to use a [data
        URL](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs)
        to inline the certificate in the `cert-url` field. For local testing,
        this is the most conveninent way to serve the SXG certificate.
    *   Change the line `Domain = 'example.org'` to reflect the domain that you
        created a certificate for. If you have followed the instructions in this
        article verbatim, this should be changed to `example.com`.
        `webpkgserver` will only fetch content from the domain indicated by
        `webpkgserver.toml`. If you try to fetch pages from a different domain
        without updating `webpkgserver.toml`, the `webpkgserver` logs will show
        the error message `URL doesn't match the fetch targets`.

    **Optional**

    If you want to enable or disable [subresource
    preloading](https://github.com/WICG/webpackage/blob/main/explainers/signed-exchange-subresource-substitution.md),
    the following `webpkgserver.toml` configuration options are relevant:

    *   To have `webpkgserver` insert directives for preloading stylesheet
        and script subresources as SXGs, change the line `#PreloadCSS = false`
        to `PreloadCSS = true`. In addition, change the line `#PreloadJS =
        false` to `PreloadJS = true`.

        As an alternative to using this configuration option, you can manually
        add `Link: rel="preload"` headers and `<link rel="preload">` tags to a
        page's HTML.

    *   By default, `webpkgserver` replaces existing `<link rel="preload">` tags
        with the equivalent `<link>` tags necessary for fetching this content as
        SXG. In doing so, `webpkgserver` will set the
        [`allowed-alt-sxg`](https://github.com/WICG/webpackage/blob/main/explainers/signed-exchange-subresource-substitution.md#use-cases)
        and
        [`header-integrity`](https://github.com/WICG/webpackage/blob/main/explainers/signed-exchange-subresource-substitution.md#use-cases)
        directives as needed—HTML authors do not need to add these by hand. To
        override this behavior and keep existing non-SXG preloads, change
        `#KeepNonSXGPreloads (default = false)` to `KeepNonSXGPreloads = true`.
        Keep in mind that enabling this option may make the SXG ineligible for
        the Google SXG cache per these
        [requirements](https://github.com/google/webpackager/blob/main/docs/cache_requirements.md).

4. Start `webpkgserver`.

    ```shell
    ./webpkgserver
    ```

    If the server has started successfully, you should see the following log messages:
    ```shell
    Listening at 127.0.0.1:8080
    Successfully retrieved valid OCSP.
    Writing to cache in /private/tmp/webpkg
    ```

    Your log messages may look slightly different. In particular, the directory
    that `webpkgserver` uses to cache certificates varies by operating system.

    If you do not see these messages, a good first troubleshooting
    step is to double-check `webpkgserver.toml`.

    If you update `webpkgserver.toml` you should restart `webpkgserver`.

5. Launch Chrome using the following command:
    ```shell
    /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
    --user-data-dir=/tmp/udd \
    --ignore-certificate-errors-spki-list=`openssl x509 -noout -pubkey -in cert.pem | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64`
    ```

    This command instructs Chrome to ignore the certificate errors associated
    with `cert.pem`. This makes it possible to test SXGs using a test
    certificate. If Chrome is launched without this command, inspecting the SXG
    in DevTools will display the error `Certificate verification error:
    ERR_CERT_INVALID`.

    **Note:**

    You may need to adjust this command to reflect the location of Chrome on
    your machine, as well as the location of `cert.pem`. If you've done this
    correctly, you should see a warning displayed below the address bar. The
    warning should be similar to this: `You are using an unsupported
    command-line flag:
    --ignore-certificate-errors-spki-list=9uxADcgc6/ho0mJLRMBcOjfBaN21k0sOInoMchr9CMY=.`

    If the warning does not include a hash string, you have not correctly
    indicated the location of the SXG certificate.


6. Open the DevTools **Network** tab, then visit the following URL:
   `http://localhost:8080/priv/doc/https://example.com`.

   This makes a request to the `webpackager` instance running at
   `http://localhost:8080` for a SXG containing the contents of
   `https://example.com`. `/priv/doc/` is the default API endpoint used by
   `webpackager`.

  {% Img
    src="image/j2RDdG43oidUy6AL6LovThjeX9c2/DPw05KcHLdWOgHYiDsgf.png",
    alt="Screenshot of the DevTools Network tab showing a SXG and its certificate.",
    width="800",
    height="236"
  %}

  The following resources are listed in the **Network** tab:
  * A resource with the type `signed-exchange`. This is the SXG.
  * A resource with the type `cert-chain+cbor`. This the SXG certificate. SXG certificates must use the `application/cert-chain+cbor` format.
  * A resource with the type `document`. This is the content that has been delivered via SXG.

  If you don't see these resources, try clearing the browser cache, then
  reloading `http://localhost:8080/priv/doc/https://example.com`.


  Click on the **Preview** tab to see more information about the Signed Exchange
  and its signature.

  {% Img
    src="image/j2RDdG43oidUy6AL6LovThjeX9c2/x9hfT6ZJ8OuB4M1ImWic.png",
    alt="Screenshot of the Preview tab showing a SXG",
    width="800",
    height="541"
  %}


## Serve signed exchanges using a `CanSignHttpExchanges` certificate

  The instructions in this section explain how to serve SXGs using a
  `CanSignHttpExchanges` certificate. Production use of SXGs requires a
  `CanSignHttpExchanges` certificate.

  For the sake of brevity, these instructions are written with the assumption
  that you understand the concepts discussed in the [Setup Signed Exchanges
  using a self-signed
  certificate](/signed-exchanges-webpackager/#serve-sxgs-using-a-self-signed-certificate)
  section.


### Prerequisites

*  You have a `CanSignHttpExchanges` certificate. This
     [page](https://github.com/google/webpackager/wiki/Certificate-Authorities)
     lists the CAs that offer this type of certificate.
   
*  If you don't have a certificate, then you can configure your webpkgserver to
   automatically retrieve certificates from your CA. You can follow the
   directions for what goes in `webpkgserver.toml` in this
   [page](https://github.com/google/webpackager/blob/main/cmd/webpkgserver/README.md#configuration).

*  Although not a requirement, it is strongly recommended that you run
   `webpkgserver` behind an edge server. If you do not use an edge server, you
   will need to configure the `TLS.PEMFile` and `TLS.KeyFile` options in
   `webpkgserver.toml`. By default, `webpkgserver` runs over HTTP. However, SXG
   certificates must be served over HTTPS to be considered valid by the browser.
   Configuring `TLS.PEMFile` and `TLS.KeyFile` allows `webpkgserver` to use
   HTTPS and therefore serve the SXG certificate directly to the browser.





#### Instructions


1. Create a PEM file by concatenating your site's SXG certificate followed by
   your site's CA certificate. More instructions on this can be found
   [here](https://www.digicert.com/kb/ssl-support/pem-ssl-creation.htm).

   [PEM](https://en.wikipedia.org/wiki/X.509#Certificate_filename_extensions) is
   a file format that is commonly used as a "container" for storing multiple
   certificates.

2. Create a fresh `webpkgsever.toml` file by copying the example.

    ```shell
    cp ./webpkgserver.example.toml ./webpkgserver.toml
    ```

3. Open `webpkgserver.toml` with the editor of your choice and make the
   following changes:
    *   Change the line `PEMFile = cert.pem` to reflect the location of the PEM
        file containing your full certificate chain.
    *   Change the line `KeyFile = 'priv.key'` to reflect the location of the
        private key corresponding to your PEM File.
    *   Change the line `Domain = 'example.org'` to reflect your site.
    *   (Optional) To have `webpkgserver` auto-renew the SXG certificate every
        90 days (45 days for Google), configure the options in the `[SXG.ACME]` section of
        `webpkgserver.toml`. This option only applies to sites with a DigiCert
        or Google ACME account setup.

3.  Configure your edge server to forward traffic to the `webpkgserver`
    instance.

    There are two primary types of requests handled by `webpkgserver`: requests
    for SXGs (which are served by the `/priv/doc/` endpoint) and requests for
    the SXG certificate (which are served by the `/webpkg/cert/` endpoint). The
    URL rewriting rules for each of these request types varies slightly. For
    more information, see [Running behind front end edge
    server](https://github.com/google/webpackager/blob/main/cmd/webpkgserver/README.md#running-behind-front-end-edge-server).

    **Note:**

    By default, `webpkgserver` serves the SXG certificate at
    `/webpkg/cert/$CERT_HASH`—for example,
    `/webpkg/cert/-0QmE0gvoedn92gtwI3s7On9zPevJGm5pn2RYhpZxgY`.
    To generate `$CERT_HASH`, run the following command:
    ```shell
    openssl base64 -in cert.pem -d | openssl dgst -sha256 -binary | base64 | tr /+ _- | tr -d =
    ```

# Third-party JavaScript performance

Third-party JavaScript generally refers to scripts embedded in your website that are:
- Not authored by you
- Served from third-party servers

Sites use these scripts for various purposes, including:
- Social sharing buttons
- Video player embeds
- Chat services
- Advertising iframes
- Analytics and metrics scripts
- A/B testing scripts for experiments
- Helper libraries (like date formatting, animation, and functional libraries)

<figure>
  {% Video src="video/tcFciHGuF3MxnTr1y5ue01OGLBn2/uLXJ72jZAlzK56ctPwXd.mp4", autoplay=true, loop=true, muted=true, playsinline=true %}
</figure>

Third-party scripts can provide powerful functionality, but that's not the whole story. They also affect privacy, security, and page behavior⁠—and they can be particularly problematic for performance.

## Performance
Any significant amount of [JavaScript can slow down performance](https://developer.chrome.com/docs/lighthouse/performance/bootup-time/). But because third-party JavaScript is usually outside your control, it can bring additional issues.

### Network
Setting up connections takes time, and sending too many requests to multiple servers causes slowdowns. That time is even longer for secure connections, which may involve DNS lookups, redirects, and several round trips to the final server that handles the user's request.

Third-party scripts often add to network overhead with things such as:
- Firing additional network requests
- Pulling in unoptimized images and videos
- Insufficient [HTTP caching](/http-cache/), which forces frequent fetching of network resources
- Insufficient [server compression](/optimizing-content-efficiency-optimize-encoding-and-transfer/) of resources
- Multiple instances of frameworks and libraries pulled in by different third-party embeds

### Rendering
The way third-party JavaScript is loaded matters a lot. If it's done synchronously in the critical rendering path it delays parsing of the rest of the document.

{% Aside 'key-term' %}
The __critical rendering path__ includes all resources that the browser needs to display the first screen's worth of content.
{% endAside %}

If a third party has server issues and fails to deliver a resource, rendering is blocked until the request times out, which can be anywhere from 10 to 80 seconds. You can test and simulate this problem with [WebPageTest Single-Point-of-Failure tests](https://css-tricks.com/use-webpagetest-api/#single-point-of-failure).

{% Aside %}
[A/B testing scripts](h/optimizing-content-efficiency-loading-third-party-javascript/#ab-test-smaller-samples-of-users) can also often delay rendering. Most of them block content display until they complete processing—which can be true even for asynchronous A/B testing scripts.
{% endAside %}

## What to do about it
Using third-party JavaScript is often unavoidable, but there are things you can do to minimize adverse effects:
- When choosing third-party resources, favor those that send the least amount of code while still giving you the functionality you need.
- Use [performance budgets](/use-lighthouse-for-performance-budgets/) for third-party content to keep their cost in check.
- Don't use the same functionality from two different vendors. You probably don't need two tag managers or two analytics platforms.
- Routinely audit and clean out redundant third-party scripts.

To learn how to audit third-party content and load it efficiently for better performance and user experience, check out the other posts in the [Optimize your third-party resources](/fast/#optimize-your-third-party-resources) section.

# Use Imagemin to compress images

Uncompressed images bloat your pages with unnecessary bytes. Because images can be [candidates for Largest Contentful Paint (LCP)](/lcp/#what-elements-are-considered), those unnecessary bytes can add unnecessary [resource load time](/optimize-lcp/#3-reduce-resource-load-time), which can result in longer LCP times.

The photo on the right is 40% smaller than the one on the left, yet would probably look identical to the average user.

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>
          <p>{% Img src="image/admin/LRE2JJAuShXTjQF5ZSaR.jpg", alt="", width="376", height="250" %}</p>
          20 KB
        </th>
        <th>
          <p>{% Img src="image/admin/u9hncwN4TsT7zw2ObU10.jpg", alt="", width="376", height="250" %}</p>
          12 KB
        </th>
      </tr>
    </thead>
    <tbody></tbody>
  </table>
</div>

## Measure

Run Lighthouse to check for opportunities to improve page load by compressing images.
These opportunities are listed under "Efficiently encode images":

{% Img src="image/admin/LnIukPEZHuVJwBtuJ7mc.png", alt="image", width="800", height="552" %}

{% Aside %}
Lighthouse currently reports on opportunities to compress images in JPEG format
only.
{% endAside %}

## Imagemin

Imagemin is an excellent choice for image compression because it supports a wide
variety of image formats and is easily integrated with build scripts and build
tools. Imagemin is available as both a
[CLI](https://github.com/imagemin/imagemin-cli) and an [npm
module](https://www.npmjs.com/package/imagemin). Generally, the npm module is
the best choice because it offers more configuration options, but the CLI can be
a decent alternative if you want to try Imagemin without touching any code.

### Plugins

Imagemin is built around "plugins." A plugin is an npm package that compresses a
particular image format (e.g. "mozjpeg" compresses JPEGs). Popular image formats
may have multiple plugins to pick from.

The most important thing to consider when choosing a plugin is whether it is
"lossy" or "lossless." In lossless compression, no data is lost. Lossy
compression reduces file size, but at the expense of possibly reducing image
quality. If a plugin doesn't mention whether it is "lossy" or "lossless," you
can tell by its API: if you can specify the image quality of the output, then it
is "lossy."

For most people, lossy plugins are the best choice. They offer significantly
greater filesize savings, and you can customize the compression levels to meet
your needs. The table below lists popular Imagemin plugins. These aren't the only plugins
available, but they'd all be good choices for your project.

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>Image Format</th>
        <th>Lossy Plugin(s)</th>
        <th>Lossless Plugin(s)</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>JPEG</td>
        <td>
          <a href="https://www.npmjs.com/package/imagemin-mozjpeg"
            >imagemin-mozjpeg</a
          >
        </td>
        <td>
          <a href="https://www.npmjs.com/package/imagemin-jpegtran"
            >imagemin-jpegtran</a
          >
        </td>
      </tr>
      <tr>
        <td>PNG</td>
        <td>
          <a href="https://www.npmjs.com/package/imagemin-pngquant"
            >imagemin-pngquant</a
          >
        </td>
        <td>
          <a href="https://www.npmjs.com/package/imagemin-optipng"
            >imagemin-optipng</a
          >
        </td>
      </tr>
      <tr>
        <td>GIF</td>
        <td>
          <a href="https://www.npmjs.com/package/imagemin-giflossy"
            >imagemin-giflossy</a
          >
        </td>
        <td>
          <a href="https://www.npmjs.com/package/imagemin-gifsicle"
            >imagemin-gifsicle</a
          >
        </td>
      </tr>
      <tr>
        <td>SVG</td>
        <td>
          <a href="https://www.npmjs.com/package/imagemin-svgo">imagemin-svgo</a>
        </td>
        <td></td>
      </tr>
      <tr>
        <td>WebP</td>
        <td>
          <a href="https://www.npmjs.com/package/imagemin-webp">imagemin-webp</a>
        </td>
        <td>
          <a href="https://www.npmjs.com/package/imagemin-webp">imagemin-webp</a>
        </td>
      </tr>
    </tbody>
  </table>
</div>

### Imagemin CLI

The Imagemin CLI works with 5 different plugins: imagemin-gifsicle,
imagemin-jpegtran, imagemin-optipng, imagemin-pngquant, and imagemin-svgo.
Imagemin uses the appropriate plugin based on the image format of the
input.

To compress the images in the "images/" directory and save them to the same
directory, run the following command (overwrites the original files):

```bash
$ imagemin images/* --out-dir=images
```

### Imagemin npm module

If you use one of these build tools,
checkout the codelabs for Imagemin with
[webpack](/codelab-imagemin-webpack), [gulp](/codelab-imagemin-gulp),
or [grunt](/codelab-imagemin-grunt).

You can also use Imagemin by itself as a Node script.
This code uses the "imagemin-mozjpeg" plugin to compress JPEG files to a quality
of 50 ('0' being the worst; '100' being the best):

```js
const imagemin = require('imagemin');
const imageminMozjpeg = require('imagemin-mozjpeg');

(async() => {
  const files = await imagemin(
      ['source_dir/*.jpg', 'another_dir/*.jpg'],
      {
        destination: 'destination_dir',
        plugins: [imageminMozjpeg({quality: 50})]
      }
  );
  console.log(files);
})();
```

# Use Lighthouse for performance budgets

[Lighthouse](https://github.com/GoogleChrome/lighthouse) now supports performance budgets. This feature, known as LightWallet, can be set up in under five minutes and provides feedback on performance metrics and the size and quantity of page resources.

{% Aside 'important' %}
While Lighthouse is an excellent tool for identifying performance improvement
opportunities, it can't be stressed enough that Lighthouse is a tool that relies
on [lab data](/lab-and-field-data-differences/#lab-data). While lab data is an
integral part of any performance improvement effort, comparing and contrasting
that data with [field data](/lab-and-field-data-differences/#field-data) collected
from your website's actual users is key. Doing so will help you understand how the
changes you make to your website affect real users.
{% endAside %}

## Install Lighthouse

LightWallet is available in the command line version of Lighthouse v5+.

To get started, install Lighthouse:

```bash
npm install -g lighthouse
```

## Create a Budget

Create a file named `budget.json`. In this file add the following JSON:

```json
[
  {
    "path": "/*",
    "timings": [
      {
        "metric": "interactive",
        "budget": 3000
      },
      {
        "metric": "first-meaningful-paint",
        "budget": 1000
      }
    ],
    "resourceSizes": [
      {
        "resourceType": "script",
        "budget": 125
      },
      {
        "resourceType": "total",
        "budget": 300
      }
    ],
    "resourceCounts": [
      {
        "resourceType": "third-party",
        "budget": 10
      }
    ]
  }
]
```

This example `budget.json` file sets five separate budgets:

* A budget of 3000ms for Time to Interactive.
* A budget of 1000ms for First Meaningful Paint
* A budget of 125 KB for the total amount of JavaScript on the page.
* A budget of 300 KB for the overall size of the page.
* A budget of 10 requests for the number of requests made to third-party origins.

For a complete list of supported performance metrics and resource types, refer to the [Performance Budgets](https://github.com/GoogleChrome/lighthouse/blob/master/docs/performance-budgets.md) section of the Lighthouse docs.

## Run Lighthouse

Run Lighthouse using the `--budget-path` flag. This flag tells Lighthouse the location of your budget file.

```bash
lighthouse https://example.com --budget-path=./budget.json
```

{% Aside %}
**Note**: A budget file does not have to be named `budget.json`.
{% endAside %}

## View the Results

If LightWallet has been configured correctly, the Lighthouse report will contain a **Budgets** section within the **Performance** category.

{% Img src="image/admin/FdUeI8rKZtJB3Ol624S3.png", alt="'Budgets' section of the Lighthouse report", width="800", height="289" %}

In the JSON version of the Lighthouse report, Lightwallet results can be found within the audit findings for the `performance-budget` audit.

# Using bundlesize with Travis CI

Using [bundlesize](https://github.com/siddharthkp/bundlesize) with [Travis
CI](https://travis-ci.org/) lets you define performance budgets with minimal
setup and enforce them as part of your development workflow. Travis CI is a
service that runs tests for your app in the cloud every time you push code to
GitHub. You can [configure your
repository](https://help.github.com/articles/about-required-status-checks/) so
that it won't allow merging pull-requests unless the bundlesize tests have
passed.

Bundlesize's GitHub checks include a size comparison to the main branch and
a warning in case of a big jump in size.

{% Img src="image/admin/8Mm1WPga9dbeIzGrv2fQ.jpg", alt="Bundlesize check on GitHub", width="769", height="316" %}

{% Aside %}
You can also use bundlesize with [Circle CI](https://circleci.com),
[Wrecker](https://app.wercker.com) and [Drone](https://readme.drone.io).
{% endAside %}

To see it in action, here's an app bundled with
[webpack](https://webpack.js.org/) that lets you [vote for your favorite
kitty](https://glitch.com/edit/#!/scarce-pixie).

<a href="https://glitch.com/edit/#!/scarce-pixie">
  {% Img src="image/admin/DGSSFfpAMIaFqX8MwWss.png", alt="Cat voting app", width="800", height="567" %}
</a>

## Set the performance budget

[This Glitch](https://glitch.com/edit/#!/scarce-pixie) already contains
bundlesize.

{% Instruction 'remix' %}

The main bundle of this app is in the public folder. To test its size, add the
following section to the `package.json` file:

```json
"bundlesize": [
  {
    "path": "./public/*.bundle.js",
    "maxSize": "170 kB"
  }
]
```

{% Aside %}
You can also set
[different thresholds for different files](https://github.com/siddharthkp/bundlesize#1-add-the-path-and-maxsize-in-your-packagejson).
This is especially useful if you are
[splitting a bundle](/reduce-javascript-payloads-with-code-splitting)
in your application.
{% endAside %}

To keep the compressed JavaScript bundle size [under the recommended
limit](/your-first-performance-budget#budget-for-quantity-based-metrics),
set the performance budget to 170KB in the `maxSize` field.

Bundlesize supports [glob patterns](https://github.com/isaacs/node-glob) and the \*
wildcard character in the file path will match all bundle names in the public
folder.

{% Aside %}
By default, bundlesize tests gzipped sizes. You can use the [compression option](https://github.com/siddharthkp/bundlesize#1-add-the-path-and-maxsize-in-your-packagejson)
to switch to [brotli](https://en.wikipedia.org/wiki/Brotli)
compression or turn it off completely.
{% endAside %}

### Create a test script

Since Travis needs a test to run, add a test script to `package.json`:

```json
"scripts": {
  "start": "webpack && http-server -c-1",
  "test": "bundlesize"
}
```

## Set up continuous integration

### Integrate GitHub and Travis CI

First, create a new repository for this project on your GitHub account and
initialize it with a `README.md`.

You'll need to [register an account on
Travis](https://docs.travis-ci.com/user/tutorial) and activate GitHub Apps
integration under the Settings section of your profile.

{% Img src="image/admin/kMgVmB5rzRJN3DlqP08R.png", alt="GitHub Apps integration on Travis CI", width="800", height="508" %}

Once you have an account, go to **Settings** under your profile, click the **Sync
account** button, and make sure your new repo is listed on Travis.

{% Img src="image/admin/Zi9Oo5SCfM5P7IxejYd3.png", alt="Travis CI Sync button", width="160", height="54" %}

### Authorize bundlesize to post on pull requests

Bundlesize needs authorization to be able to post on pull requests, so [visit
this link to get the bundlesize
token](https://github.com/login/oauth/authorize?scope=repo%3Astatus&client_id=6756cb03a8d6528aca5a)
that will be stored in the Travis configuration.

{% Img src="image/admin/GkEMv2VCb25oC9lDSARQ.jpg", alt="bundlesize token", width="619", height="330" %}

In your project's Travis dashboard, go to **More options** > **Settings** > **Environment
variables**.

{% Img src="image/admin/gol14FsIsYPyPWwIeYfI.png", alt="Adding environment variables on Travis CI", width="789", height="233" %}

Add a new environment variable with the token as the value field and
BUNDLESIZE_GITHUB_TOKEN as the name.

The last thing you need to kick-off continuous integration is a `.travis.yml`
file, which tells Travis CI what to do. To speed things up, it is already
included in the project and it specifies that the app is using NodeJS.

With this step, you're all set up and bundlesize will warn you if your
JavaScript ever goes over the budget. Even when you start off great, over time,
as you add new features, kilobytes can pile up. With automated performance
budget monitoring, you can rest easy knowing that it won't go unnoticed.

## Try it out

### Trigger your first bundlesize test

To see how the app stacks up against the performance budget, add the code to the
GitHub repo that you created in step 3.

1. On Glitch, click **Tools** > **Git, Import, and Export** > **Export to GitHub**.

2. In the pop-up, enter your GitHub username and the name of the repo as
`username/repo`. Glitch will export your app to a new branch named "glitch".

3. Create a new pull request by clicking the **New pull request** button on
the homepage of the repository.

You'll now see status checks in progress on the pull request page.

{% Img src="image/admin/SrdHGr9z5QY1vEfBwNIY.png", alt="GitHub checks in progress", width="774", height="351" %}

It won't take long until all checks are done. Unfortunately, the cat voting app
is a bit bloated and does not pass the performance budget check. The main bundle
is 266 KB and the budget is 170 KB.

{% Img src="image/admin/Dt31nFkMvJjn1me6cir3.png", alt="Failed bundlesize check", width="774", height="347" %}

### Optimize

Luckily, there are some easy performance wins you can make by
[removing unused code](/remove-unused-code). There are two main imports in
`src/index.js`:

```js
import firebase from "firebase";
import * as moment from 'moment';
```

The app is using [Firebase Realtime
Database](https://firebase.google.com/products/realtime-database/) to store the
data, but it's importing the entire firebase package which consists of a lot
more than just a database (auth, storage, messaging etc.).

Fix this by importing only the package that the app needs in the `src/index.js`
file:

```js
import firebase from "firebase";
import firebase from 'firebase/app';
import 'firebase/database';
```

{% Aside %}
The `firebase/app` import, which sets up the API surface for each of
the different services, is always required.
{% endAside %}

### Re-run test

Since the source file has been updated, you need to run webpack to build the new
bundle file.

1. Click the **Tools** button.

2. Then click the **Console** button. This will open the console in another tab.

3. In the console, type `webpack` and wait for it to finish the build.

4. Export the code to GitHub from **Tools** > **Git, Import, and Export** > **Export to GitHub**.

5. Go to the pull request page on GitHub and wait for all checks to finish.

{% Img src="image/admin/3aKOqNvvavQ32gl15PmU.png", alt="Passed bundlesize check", width="778", height="355" %}

Success! The new size of the bundle is 125.5 KB and all the checks have passed.
🎉

Unlike Firebase, importing parts of the moment library cannot be done as easily,
but it's worth a shot. Check out how you can further optimize the app in the
[Remove unused code codelab](/codelab-remove-unused-code).

### Monitor

The app is now under the budget and all is well. Travis CI and bundlesize will
keep monitoring the performance budget for you, making sure your app stays fast.

# Using Lighthouse Bot to set a performance budget

You've done hard work to get fast, now make sure you stay fast by automating
performance testing with [Lighthouse Bot](https://github.com/ebidel/lighthousebot).

[Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) grades your app
in 5 categories, and one of those is performance. While you could try to remember to
monitor performance changes with [DevTools](https://developer.chrome.com/docs/lighthouse/overview/#devtools)
or [Lighthouse CLI](https://developer.chrome.com/docs/lighthouse/overview/#cli) every
time you edit your code, you don't have to do that. Tools can do the tedious
stuff for you. [Travis CI](https://travis-ci.com/) is a great service that
automatically runs tests for your app in the cloud every time you push new code.

Lighthouse Bot integrates with Travis CI, and its performance budget feature ensures
that you won't accidentally downgrade performance without noticing. You can
[configure your repository](https://help.github.com/articles/about-required-status-checks/)
so that it won't allow merging pull-requests if the Lighthouse scores fall below
the threshold you've set (e.g. < 96/100).

<figure>
  {% Img src="image/admin/LIEdWOuIGubFE0JgBM5Y.png", alt="Failing Lighthouse Bot checks on GitHub", width="800", height="360" %}
  <figcaption>Lighthouse Bot checks on GitHub.</figcaption>
</figure>

{% Aside %}
Lighthouse Bot used to be called Lighthouse CI.
{% endAside %}

Although you can test performance on localhost, your site will often perform
differently on live servers. To get a more realistic picture, it's best to
deploy your site to a staging server. You can use any hosting service; this
guide will take [Firebase hosting](https://firebase.google.com/docs/hosting/)
for a spin.

## 1. Setup

This simple app helps you sort three numbers.

[Clone the example from GitHub](https://github.com/GoogleChromeLabs/lighthouse-bot-starter),
and make sure to add it as a repository on your GitHub account.

## 2. Deploy to Firebase

To get started, you'll need a Firebase account. Once you've taken care of that,
[create a new project in the Firebase console](https://console.firebase.google.com/)
by clicking "Add project":

{% Img src="image/admin/SYTTK9CHxnTt1n7vONKU.png", alt="", width="517", height="552" %}

### Deploying to Firebase

You'll need [Firebase CLI](https://firebase.google.com/docs/cli/) to deploy the
app. Even if you already have it installed, it's good practice to frequently
update the CLI to the latest version with this command:

```bash
npm install -g firebase-tools
```

To authorize the Firebase CLI, run:

```bash
firebase login
```

Now initialize the project:

```bash
firebase init
```

The console will ask you a series of questions during setup:

- When prompted to select features, choose "Hosting."
- For the default Firebase project, select the project that you've created in
  the Firebase console.
- Type in "public" as your public directory.
- Type "N" (no) to configuring as a single-page app.

This process creates a `firebase.json` configuration file in the root of your
project directory.

Congrats, you're ready to deploy! Run:

```bash
firebase deploy
```

In a split second, you'll have a live app.

## 3. Setting up Travis

You'll need to [register an account](https://travis-ci.com) on Travis and then
activate GitHub Apps integration under the Settings section of your profile.

{% Img src="image/admin/fNGAjcYM5Rw3e43PExoD.png", alt="GitHub Apps integration on Travis CI", width="800", height="508" %}

### Once you have an account

Go to Settings under your profile, hit the Sync account button, and make sure
your project repo is listed on Travis.

{% Img src="image/admin/dnXFBpBw4WvWbXQL1DQf.png", alt="", width="160", height="54" %}

To kick-off continuous integration, you need two things:

1. To have a `.travis.yml` file in the root directory
2. To trigger a build by doing a regular old git push

The `lighthouse-bot-starter` repo already has a `.travis.yml` YAML file:

```yaml
language: node_js
node_js:
  - "8.1.3"
install:
  - npm install
before_script:
  - npm install -g firebase-tools
script:
  - webpack
```

The YAML file tells Travis to install all the dependencies and build your app.
Now it's your turn to **push the example app to your own GitHub repository**.
If you haven't already, run the following command:

```bash
git push origin main
```

Click on your repo under Settings in Travis to see your project's Travis
dashboard. If everything is cool, you'll see your build go from yellow to green
in a couple of minutes. 🎉

## 4. Automate Firebase deployment with Travis

In Step 2, you logged into your Firebase account and deployed the app from the
command line with `firebase deploy`. In order for Travis to deploy your app to
Firebase, you have to authorize it. How do you do that? With a Firebase token.
🗝️🔥

### Authorize Firebase

To generate the token run this command:

```bash
firebase login:ci
```

It will open a new tab in a browser window so that Firebase can verify you.
After that, look back at the console, and you'll see your freshly minted token.
Copy it and go back to Travis.

In your project's Travis dashboard, go to **More options** > **Settings** > **Environment variables**.

{% Img src="image/admin/uU7MBc5NdBDZch3ZE3Zd.png", alt="", width="789", height="233" %}

Paste the token in the value field, name the variable `FIREBASE_TOKEN`, and add it.

### Add deployment to your Travis setup

You need the following lines to tell Travis to deploy the app after every successful build.
Add them to the end of the `.travis.yml` file. 🔚

```yaml
after_success:
  - firebase deploy --token $FIREBASE_TOKEN --non-interactive
```

Push this change to GitHub and wait for your first automated deployment.
If you take a look at your Travis log, it should soon say ✔️ Deploy complete!

Now whenever you make changes to your app, they will be automatically deployed to Firebase.

## 5. Setting up Lighthouse Bot

Friendly Lighthouse Bot updates you on your app's Lighthouse scores.
It just needs an invitation to your repo.

On GitHub, go to your project's settings and **add "lighthousebot" as a collaborator** (Settings>Collaborators):

{% Img src="image/admin/H2aLCOr36UDwm5Yk1k9r.png", alt="Lighthouse bot collaborator status", width="800", height="298" %}

Approving these requests is a manual process so they don't always happen
instantly. Before you start testing, make sure lighthousebot has approved its
collaborator status. In the meantime, you also need to add another key to your
project's environment variables on Travis. [Leave your email
here](https://docs.google.com/forms/d/e/1FAIpQLSdIc3QNIMn7bBMgl2cfxmmo6wGBlUpdLGxjB_ml464t9eCg_A/viewform),
and you'll get a Lighthouse Bot key in your inbox. 📬

On Travis, add this key as an environment variable and name it `LIGHTHOUSE_API_KEY`:

{% Img src="image/admin/0XCrRSbUg1Sdca8k9xK9.jpg", alt="", width="800", height="127" %}

{% Aside %}
You can reuse this same key for other projects.
{% endAside %}

### Add Lighthouse Bot to your project

Next, add Lighthouse Bot to your project by running:

```bash
npm i --save-dev https://github.com/ebidel/lighthousebot
```

And add this bit to the `package.json`:

```json
"scripts": {
  "lh": "lighthousebot"
}
```

### Add Lighthouse Bot to your Travis configuration

For the final trick, test the performance of the app after every pull
request!

In `.travis.yml` add another step in after_success:

```yaml
after_success:
  - firebase deploy --token $FIREBASE_TOKEN --non-interactive
  - npm run lh -- https://staging.example.com
```

It will run a Lighthouse audit on the given URL, so replace
`https://staging.example.com` with the URL of your app
(that's your-app-123.firebaseapp.com).

Set your standards high and tweak the setup so you don't accept any changes to
the app that bring the performance score below 95:

```yaml
- npm run lh -- --perf=95 https://staging.example.com
```

### Make a pull request to trigger Lighthouse Bot test on Travis

Lighthouse Bot will only test pull requests, so if you push to the main branch
now, you'll just get "This script can only be run on Travis PR requests" in your
Travis log.

To trigger the Lighthouse Bot test:

1. Checkout a new branch
2. Push it to GitHub
3. Make a pull request

Hang tight on that pull request page and wait for Lighthouse Bot to sing! 🎤

{% Img src="image/admin/SmWHb70YqVfagXI3f03D.png", alt="Passing Lighthouse scores", width="586", height="329" %}

{% Img src="image/admin/ZrPGH5OGEY5Y4e9ntUBK.png", alt="Passing GitHub checks", width="462", height="189" %}

The performance score is great, the app is under budget, and the check has passed!

### More Lighthouse options

Remember how Lighthouse tests 5 different categories? You can enforce scores for
any of those with Lighthouse Bot flags:

```bash
--perf  # performance
--pwa   # progressive web app score
--a11y  # accessibility score
--bp    # best practices score
--seo   # SEO score
```

Example:

```bash
npm run lh -- --perf=93 --seo=100 https://staging.example.com
```

This will fail the PR if the performance score drops below 93 **or** the SEO
score drops below 100.

You can also choose not to get Lighthouse Bot's comments with the `--no-comment`
option.

# The value of speed

After a lot of hard work, you've done it.
You've made your company's site noticeably faster.
Now it's time for the fun part:
showing stakeholders how much extra revenue your work has generated!

In this post we'll walk through how to do that by calculating the _relative mobile conversion_ rate.
This metric is useful because it quantifies the effects of site improvements
while excluding external factors like marketing campaigns,
which can obscure your findings. Let's get started!

## Relative Mobile Conversion Rate (Rel mCvR)

A site's conversion rate can be influenced by site characteristics—
like speed and usability—and by external factors—like marketing campaigns,
seasonal events, and the mix of marketing channels.

{% Aside %}
A _conversion_ happens when a site visitor becomes a customer.
What counts as a conversion will vary depending on the nature of the site.
Buying a product, subscribing to a service, registering as a user,
or even just viewing a particular page could be considered a conversion.
Check with your marketing team if you're not sure how conversions are counted in your company.
{% endAside %}

Since you're interested in how site speed affects conversions,
the mobile site is most relevant—
that's where you're most likely to see the benefits of speed improvements.
Rather than looking at the mobile conversion rate alone, though,
you'll be analyzing the _relative_ mobile conversion rate (Rel mCvR),
which is calculated by dividing the mobile conversion rate by the desktop conversion rate.
This approach reduces the noise from external factors,
which tend to affect both desktop and mobile,
and makes it easier to see whether any increases in the mobile site's effectiveness
were actually caused by the speed improvements.

{% Img
src="image/ZDZVuXt6QqfXtxkpXcPGfnygYjd2/6q2BiNPameuAqeMOghoE.jpg",
alt="Table showing comparison of mobile/desktop conversion rate and relative mobile conversion rate",
width="800", height="450" %}

{% Aside 'caution' %}
Caution: Rel mCvR is influenced not only by speed,
but also by other site characteristics like usability.
If other big changes were made to the site during the period you want to analyze,
you won't be able to measure the effects of speed improvements on their own,
but you can show the benefits of all the improvements as a group.
{% endAside %}

## Doing the analysis

Make sure you have access to your site's Google Analytics,
or collaborate with the analytics team.
If you don't have a Google Analytics account,
you can learn how to set one up at
[Get started with Analytics](https://support.google.com/analytics/answer/1008015?hl=en).

**Step 1:** Go to Google Analytics and click **Admin**. Under **View**, choose **View Settings**. There, copy the View ID.

**Step 2:** Go to
[this spreadsheet](https://docs.google.com/spreadsheets/d/13BnREVWPhIiDYdEvOSYP3ovlMggPbnRQPMTSir6y__I/edit#gid=1619071522)
and click **File**, and Make a copy.

**Step 3:** Insert the View ID from Google Analytics into field **B3**,
**C3** and **D3** in
[the spreadsheet](https://docs.google.com/spreadsheets/d/13BnREVWPhIiDYdEvOSYP3ovlMggPbnRQPMTSir6y__I/edit#gid=1619071522).
If your Google Analytics has goals instead of Ecommerce Conversion Rate,
change field **B6** and **C6** so that you remove `ga:transactionsPerSession` and instead type in
`ga:goalConversionRateAll` in the two fields.

**Step 4:** In the spreadsheet,
click **Add-ons**, **Google Analytics**, and choose **Run reports**.
Then go to the spreadsheet page Rel mCvR and see the results.

You should now have a chart that looks something like this:

{% Img
src="image/ZDZVuXt6QqfXtxkpXcPGfnygYjd2/d7VY9rh0nPtrfyvctkeb.jpg",
alt="Chart showing mobile load time vs relative mobile conversion rate.",
width="800", height="495" %}

**Step 6:** Using the chart,
identify a period before the speed optimization (when load times were high)
and a period after the speed optimization (when load times should be lower)
that you want to analyze.
In this example, you would compare eight weeks in Jan–Feb to eight weeks in Aug–Sept.

**Step 7:** In a new sheet,
calculate the average load time and rel mCvR for the two periods.
Then add the revenue coming from mobile visitors during the period after the speed optimization
(Aug–Sept in the example).
You can find revenue data in Google Analytics under the section Audience > Mobile > Overview.

{% Img
src="image/ZDZVuXt6QqfXtxkpXcPGfnygYjd2/NH6HEdFAmI5IHXOwSPlf.jpg",
alt="Screenshot: Image of table in showing revenue data",
width="800", height="122" %}

**Step 8:** Now calculate what the revenue would have been if Rel mCvR had not improved.
Do this by dividing the revenue (€1,835,962) by the current Rel mCvR (51%)
and multiplying by the Rel mCvR for the period before the speed optimization (42%).

{% Img src="image/ZDZVuXt6QqfXtxkpXcPGfnygYjd2/HNkshbjj8pYGbGlMoZQ1.jpg",
alt="Screenshot: spreadsheet cells showing formula for revenue without Rel mCvR improvements",
width="800", height="202" %}

**Step 9:** Subtract the revenue that the company earned
from what it would have earned if Rel mCvR had not improved.

{% Img src="image/ZDZVuXt6QqfXtxkpXcPGfnygYjd2/I0LBdqLJ7i9gaTZLqgQc.jpg",
alt="Screenshot: spreadsheet cells showing extra revenue formula",
width="800", height="242" %}

In this example, the company earned an additional €323,993 in eight weeks thanks to Rel mCvR improving
—that is, thanks to the mobile site becoming faster.

{% Img src="image/ZDZVuXt6QqfXtxkpXcPGfnygYjd2/DhukJqLa37bBxJbkhmaH.jpg",
alt="Screenshot: spreadsheet cells showing extra revenue due to Rel mCvR improvements",
width="800", height="242" %}

## Things to consider when analyzing Rel mCvR

As noted above, other changes to the site, such as UX improvements,
can influence Rel mCvR.

- Check that speed was the only big change to the site during the period you want to study.
If there were other changes,
Rel mCvR can tell you the effect of the changes as a group, but not of an individual change.
- Watch out for any changes or events that affected the desktop site but not the mobile site
—they can skew your results.
If you discover any desktop-only changes,
omit the affected period from your analysis.
- You might wonder whether an increase in Rel mCvR is caused by a shift in conversions
from desktop to mobile rather than an increase in conversions overall.
While there is likely to be some change
in the mix of desktop and mobile conversions due to speed improvements,
keep in mind that Rel mCvR calculates the mobile conversion rate relative to the desktop conversion rate.
So, you only see an increase in Rel mCvR when mCvR goes up more than dCvR.
In other words, when doing this calculation you're already counting low,
which means you have a safety margin that can compensate for any shifts in the channel mix.

## Summing up

While it has some limitations,
Rel mCvR is a great low-cost way to estimate how much a speed optimization increased revenue without,
for example, having to run server-side or slow-down tests.
And quantifying the relationship between performance and revenue
can help you demonstrate the value of development projects
whose benefits might not be immediately clear to non-technical stakeholders.


## Next steps

- [Discover performance opportunities with Lighthouse](/discover-performance-opportunities-with-lighthouse)
- [Performance budgets 101](/performance-budgets-101)

*Aircraft instrument panel image by
[Arie Wubben](https://unsplash.com/photos/MHIw0nSxCR4) on
[Unsplash](https://unsplash.com/)*

# What is speed?

So, speed matters, but what exactly do we mean by it? What does it mean to have a fast site?

It's common to hear people talk in terms of their website loading in x.xx seconds or similar, but a [load is not a single moment in time](/user-centric-performance-metrics/); it's an experience that no single metric can fully capture. There are multiple moments during the load experience that can affect whether a user perceives it as 'fast', and if you just focus solely on one, you might miss bad experiences that happen during the rest of the time.

Rather than measuring load with just one metric, you should time each moment during the experience that affects the user's perception of load speed. When a user navigates to a web page, they're typically looking for certain types of feedback:

{% Img src="image/admin/NGX9WC2BXTRY6FP5TTGl.png", alt="Image of feedback user is typically looking for", width="800", height="185" %}

Traditional performance metrics like load time or DOMContentLoaded time are unreliable, since their occurrence may or may not correspond with these feedback milestones. So, [additional metrics](https://developer.chrome.com/docs/lighthouse/performance/#metrics) have emerged that could be used to understand when a page delivers this feedback to its users:

{% Img src="image/admin/tz1aubGGvefskjcPfjBQ.png", alt="Image of speed metrics", width="800", height="654" %}

It's important to understand the different insights offered by these metrics, then establish the ones that are important to your user experience. Some brands even define additional custom metrics specific to the expectations people have of their service. In the case of Pinterest, users want to see images, so they defined a custom metric, [Pinner Wait Time](https://www.youtube.com/watch?v=Xryhxi45Q5M), that combines Time to Interactive and Above the Fold Image load times.

Even though the load is more than one moment in time, it can still be useful to have a single metric for the purposes of simplified reporting or comparison: [Speed Index](https://developer.chrome.com/docs/lighthouse/performance/speed-index/) and [Lighthouse score](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring) can both be used in this way.

# What should you measure to improve performance?

The different steps of a purchase funnel are prone to performance issues in
different ways, and therefore need different measurement and optimizations:

<figure>
  {% Img src="image/admin/87cAGdJAnPggf8Yt58ID.png", alt="A conversion funnel going from discover to engage to convert to re-engage.", width="800", height="399" %}
  <figcaption>
    A conversion funnel.
  </figcaption>
</figure>

In this guide we'll address how the different steps can be measured. For this we
recommend you to look at lab as well as field data.

**Lab data** is gathered by running tests locally, for example by using
[Lighthouse](/fast/discover-performance-opportunities-with-lighthouse)
and other tools.  This can make it possible  to compare website performance over
time and with competitors through a controlled, stable environment, but it might
not be representative of the performance users experience in real life.

**Field data** is gathered via analytics from real users, and is therefore
representative of their experience. However, it can't easily be compared over
time or with competitors. Network connections and smartphone hardware evolve
over time, and different target audiences might have different devices, thereby
making comparisons with field data tough. See also _[Measure Performance In The Field](/fast#measure-performance-in-the-field)_.

To get a complete picture, both data sources are needed. The following sections
show how to gather lab and field data for the different relevant performance
marks across the funnel.

## Discovery

Optimizing for discovery means optimizing for the first load, which is what new
users  will get, but also search and social
[crawlers](https://developers.google.com/search/docs/guides/rendering).
Lab data for a first load can be easily acquired through
[Lighthouse](/fast/discover-performance-opportunities-with-lighthouse),
while field data (at least for Chrome) is readily available through [Chrome UX
reports](/fast/chrome-ux-report). A convenient combination of
both can be found in
[PageSpeed Insights](https://pagespeed.web.dev/).
You should also track relevant metrics from the field yourself:
[Measuring these metrics on real users' devices](/user-centric-performance-metrics/#in-the-field)
provides a good overview.

From a user perspective the most important metrics are:

+   **[First Contentful Paint (FCP)](/fcp/):**
    The time the user stares at a blank screen. This is
    when most users bounce, as they don't see progress.
+   **[First Meaningful Paint (FMP)](https://developer.chrome.com/docs/lighthouse/performance/first-meaningful-paint/):**
    When the user begins to see the main content they came for. This
    is often the hero image, but for a landing page it may even be a call to
    action such as a **Buy** button, since the user may have arrived with a clear
    intent (for example, through a targeted ad campaign).
+   **[First Input Delay (FID)](/fid/):**
    The time the website needs to react to the user's first input.
    Excessive JavaScript and other asset loading problems can block this,
    leading to failed taps or clicks, erroneous inputs and page abandonment.

There are more metrics you can look at, but these are a good baseline. Additionally also make sure to capture bounce rates, conversions and user engagement so that you can set these in relation.

## Engagement And Conversion

After the first load of a landing page, a user will proceed through your site,
hopefully towards a successful conversion.


In this phase it is important to have fast and responsive navigations and
interactions. Unfortunately it is not trivial to measure the complete flow of
navigation and interaction events in the field, as every user takes different
paths through the page. We therefore recommend measuring the time needed towards
conversion or conversion subgoals ("_Time-to-Action_") by scripting and
measuring the flow in a lab test, to compare performance over time or with
competitors.

There are two convenient ways of doing this:

### WebPageTest

[WebPageTest](https://www.webpagetest.org/) offers a very flexible [scripting
solution](https://github.com/WPO-Foundation/webpagetest-docs/blob/main/src/scripting.md).
The basic idea is to:

+   Tell WebPageTest to navigate through the pages of the flow with the
    [`navigate`](https://github.com/WPO-Foundation/webpagetest-docs/blob/main/src/scripting.md#navigate)
    command.
+   If needed script the clicking of buttons via
    [`clickAndWait`](https://github.com/WPO-Foundation/webpagetest-docs/blob/main/src/scripting.md#clickandwait)
    commands and fill text fields via
    [`setValue`](https://github.com/WPO-Foundation/webpagetest-docs/blob/main/src/scripting.md#selectvalue).
    For testing of Single Page Applications use `clickAndWait` rather than
    `navigate` commands for all steps after the first, as `navigate` will do a
    full load instead of the lightweight virtual page load.
+   Make sure to combine the different steps of the flow in the analysis via
    [`combineSteps`](https://github.com/WPO-Foundation/webpagetest-docs/blob/main/src/scripting.md#combinesteps)
    to produce a single overall result report for the complete flow.

Such a script could look like this:

```text
combineSteps
navigate	https://www.store.google.com/landingpage
navigate	https://www.store.google.com/productpage
clickAndWait	innerText=Buy Now
navigate	https://www.store.google.com/basket
navigate	https://www.store.google.com/checkout
```

With a script like this in place you can easily measure and compare performance
over time. This can even be automated through the
[WebPageTest API](https://github.com/WPO-Foundation/webpagetest-docs/blob/5337749ae99d0529fc0ae690d402fd4f88766be9/dev/api.md).

### Puppeteer

Another great option to script testing is via headless Chrome, which can be
controlled through the Node API
[Puppeteer](https://github.com/GoogleChrome/puppeteer). The general idea is to
start the browser through Puppeteer, navigate to the landing page through the
[goto](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options)
function,
[inject JavaScript](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pageevaluatepagefunction-args)
to fill fields or
[click](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pageclickselector-options)
buttons and proceed through the funnel through further
[goto](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options)
calls as needed.

As a metric the duration of the flow can be measured directly,
but you could also sum up the FCP, FMP or TTI values of the individual loads of
the flow.
[Test website performance with Puppeteer](https://michaljanaszek.com/blog/test-website-performance-with-puppeteer)
provides an overview of how to get performance metrics via Puppeteer. A very
simplified example Node script could look like this:

```js
const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  const start = performance.now();
  await page.goto('https://www.store.google.com/landingpage');
  await page.goto('https://www.store.google.com/productpage');
  // click the buy button, which triggers overlay basket
  await page.click('#buy_btn');
  // wait until basket overlay is shown
  await page.waitFor('#close_btn');
  await page.goto('https://www.store.google.com/basket');
  await page.goto('https://www.store.google.com/checkout');
  console.log('Flow took ' + parseInt((performance.now() - start)/1000) + ' seconds');
  await browser.close();
})();
```

This script can easily be automated, and even be made part of the build process
or perf budgets, and be monitored regularly.

## Re-Engagement

Users will return to your site in different time intervals. Depending on time
passed, the browser may have less of the website's resources cached, needing
more network requests. This makes it difficult to estimate performance
differences across repeat visits in lab tests. It is still recommended to keep
an eye on this though, and a great lab test tool for repeat visits is
[WebPageTest](https://www.webpagetest.org/), which has a dedicated option for a
direct repeat visit:

<figure>
  {% Img src="image/admin/6eHzqWl6gaKjL39n8Dwh.png", alt="The WebPageTest homepage form for auditing a site. The repeat view option is highlighted.", width="800", height="650" %}
  <figcaption>
    Webpagetest offers options to test first load and repeat load as well
  </figcaption>
</figure>

To get a better feeling for repeat visit performance in the field use your
analytics package of choice to segment your performance metrics by user type.
Here is an example of such a report in Google Analytics:

<figure>
  {% Img src="image/admin/pSxM9xuJKho7RjOn4aDK.png", alt="A Google Analytics dashboard shows a number of fields being added to a custom report.", width="800", height="444" %}
  <figcaption>
    A Google Analytics custom report can be used to report speed metrics for new and returning users.
  </figcaption>
</figure>

A report like this will give you page load times for new and returning users as well.

## Recap
This guide showed you how to measure first load, flow and repeat load via field and lab tests. Make sure to optimize the different steps of the funnel accordingly to maximize discovery (first load), engagement (navigations and flow) and re-engagement (repeat load).

# Why does speed matter?

Consumers increasingly rely on mobile to access digital content and services,
and if you look at your site analytics,
you'll probably see this story playing out in your own data.
Consumers are also more demanding than they've ever been,
and when they weigh the experience on your site, they aren't just comparing you with your competitors,
they're rating you against the best-in-class services they use every day.

This post rounds up some of the research that has been done on the relationship between performance and business success.

## Performance is about retaining users

<figure>
  <blockquote>
    <p>
  	Performance has directly impacted the company's bottom line.
    </p>
    <cite>
      <a href="https://www.youtube.com/watch?v=Xryhxi45Q5M&feature=youtu.be&t=1366">Pinterest</a>
    </cite>
  </blockquote>
</figure>

Performance plays a major role in the success of any online venture.
High-performing sites engage and retain users better than low-performing ones.

Pinterest reduced perceived wait times by 40%
and this [increased search engine traffic and sign-ups by 15%](https://medium.com/@Pinterest_Engineering/driving-user-growth-with-performance-improvements-cfc50dafadd7).

COOK reduced average page load time by 850 milliseconds which
[increased conversions by 7%, decreased bounce rates by 7%, and increased pages per session by 10%](https://www.nccgroup.trust/globalassets/resources/uk/case-studies/web-performance/cook-case-study.pdf).

Studies have also shown the negative impact poor performance can have on business goals.
For example, [the BBC](https://www.creativebloq.com/features/how-the-bbc-builds-websites-that-scale)
found they lost an additional 10% of users for every additional second their site took to load.

## Performance is about improving conversions

Retaining users is crucial to improving conversions.
Slow sites have a negative impact on revenue, and fast sites are shown to increase conversion rates.

For [Mobify](http://resources.mobify.com/2016-Q2-mobile-insights-benchmark-report.html),
every 100ms decrease in homepage load speed worked out to a 1.11% increase in session-based conversion,
yielding an average annual revenue increase of nearly $380,000.
Additionally, a 100ms decrease in checkout page load speed amounted to a 1.55% increase in session-based conversion,
which in turn yielded an average annual revenue increase of nearly $530,000.

When [AutoAnything reduced page load time by half](https://www.digitalcommerce360.com/2010/08/19/web-accelerator-revs-conversion-and-sales-autoanything/),
they saw a boost of 12% to 13% in sales.

Retailer [Furniture Village](https://www.thinkwithgoogle.com/intl/en-gb/success-stories/uk-success-stories/furniture-village-and-greenlight-slash-page-load-times-boosting-user-experience/) audited their site speed and developed a plan to address the problems they found,
leading to a 20% reduction in page load time and a 10% increase in conversion rate.

## Performance is about user experience

When it comes to user experience, speed matters.
A [consumer study](https://www.ericsson.com/en/press-releases/2016/2/streaming-delays-mentally-taxing-for-smartphone-users-ericsson-mobility-report)
shows that the stress response to delays in mobile speed are similar to that of watching a horror movie or solving a mathematical problem,
and greater than waiting in a checkout line at a retail store.

As a site begins to load, there's a period of time where users wait for content to appear.
Until this happens, there's no user experience to speak of.
This lack of an experience is fleeting on fast connections.
On slower connections, however, users are forced to wait.
Users may experience more problems as page resources slowly trickle in.

<figure>
  {% Img src="image/admin/W0ctiX3cMOfWnNF6AQMg.png", alt="A comparison of two filmstrip reels of a page loading. The first shows a page loading on a slow connection, while the second shows the same page loading on a fast connection.", width="800", height="264" %}
  <figcaption>A comparison of page load on a very slow connection
(top) versus a faster connection (bottom).</figcaption>
</figure>

Performance is a foundational aspect of good user experiences.
When sites ship a lot of code, browsers must use megabytes of the user's data plan to download it.
Mobile devices have limited CPU power and memory.
They often get overwhelmed with what we might consider a small amount of unoptimized code.
This creates poor performance which leads to unresponsiveness.
Knowing what we know about human behavior, users will only tolerate low performing applications for so long before abandoning them.

## Performance is about people

Poorly performing sites and applications can also pose real costs for the
people who use them.

[As mobile users continue to make up a larger portion of internet users
worldwide](http://gs.statcounter.com/platform-market-share/desktop-mobile-tablet),
it's important to bear in mind that many of these users access the web through
mobile LTE, 4G, 3G, and even 2G networks.
As Ben Schwarz of Calibre points out in
[this study of real world performance](https://calibreapp.com/blog/beyond-the-bubble),
the cost of prepaid data plans is decreasing,
which in turn is making access to the internet more affordable in places where it once wasn't.
Mobile devices and internet access are no longer luxuries.
They are common tools necessary to navigate and function in an increasingly interconnected world.

[Total page size has been steadily increasing since at least 2011](http://beta.httparchive.org/reports/state-of-the-web#bytesTotal),
and the trend appears to be continuing.
As the typical page sends more data,
users must replenish their metered data plans more often, which costs them money.

In addition to saving your users money,
fast and lightweight user experiences can also prove crucial for users in crisis.
Public resources such as hospitals, clinics,
and crisis centers have online resources that give users important and specific information that they need during a crisis.
[While design is pivotal in presenting important information efficiently in stressful moments](https://aneventapart.com/news/post/eric-meyer-designing-for-crisis),
the importance of delivering this information fast can't be understated.
It's part of our job.

## Get started with improving your website speed {: #get-started }

Read up on the [Core Web Vitals](/vitals/#core-web-vitals) to learn about the metrics
that Google believes all websites should focus on.

<blockquote>
  <p>
	  Today, we’re building on this work and providing an early
	  look at an upcoming Search ranking change that incorporates
	  these page experience metrics. We will introduce a new signal
	  that combines Core Web Vitals with our existing signals for page
	  experience to provide a holistic picture of the quality of a user’s
	  experience on a web page.
  </p>
  <cite>
    <a href="https://webmasters.googleblog.com/2020/05/evaluating-page-experience.html">
      Evaluating page experience for a better web
    </a>, Official Google Webmaster Central Blog
  </cite>
</blockquote>

Then check out [Fast load times](/fast/) for lots of tips and tricks
related to getting fast and staying fast.

# Your first performance budget

When you set a personal, business or family budget, you are setting a limit to your spending and making sure you stay within it. [Performance budgets](/performance-budgets-101) work in the same way, but for metrics that affect website performance.

With a performance budget established and enforced you can be sure that your site will render as quickly as possible. This will provide a better experience for your visitors and positively impact business metrics.

Here's how to define your first performance budget in a few simple steps.

## Preliminary analysis

If you are trying to improve the performance of an existing site, start by identifying the most important pages. For example, these could be pages that have the highest amount of user traffic or a product landing page.

After you identify your key pages, it's time to analyze them. First, we'll focus on the timing milestones that best measure the user experience.

Under the Audits panel in Chrome DevTools, you'll find [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/). Run audits on each page in a [Guest window](https://support.google.com/chrome/answer/6130773?co=GENIE.Platform%3DDesktop&hl=en) to record these two times:

* [First Contentful Paint (FCP)](/fcp/)
* [Time to Interactive (TTI)](/tti/)

{% Aside %}
Using a Guest window gives you a clean testing environment without any Chrome
extensions that could interfere with the audit.
{% endAside %}

{% Img src="image/admin/VUtkCadH9vjnKSzGzd0S.png", alt="Lighthouse panel in Chrome DevTools", width="800", height="637" %}

Let's take a highly specialized search engine, Doggos.com, as an example. Doggos.com aims to index all dog-related things on the internet, and its most important pages are the home and results pages. Here are the FCP and TTI numbers measured for the site on desktop and mobile.

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>Desktop</th>
        <th>FCP</th>
        <th>TTI</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Homepage</td>
        <td>1,680 ms</td>
        <td>5,550 ms</td>
      </tr>
      <tr>
        <td>Results page</td>
        <td>2,060 ms</td>
        <td>6,690 ms</td>
      </tr>
    </tbody>
    <caption>Desktop analysis of Doggos.com</caption>
  </table>
</div>

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>Mobile</th>
        <th>FCP</th>
        <th>TTI</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Homepage</td>
        <td>1,800 ms</td>
        <td>6,150 ms</td>
      </tr>
      <tr>
        <td>Results page</td>
        <td>1,100 ms</td>
        <td>7,870 ms</td>
      </tr>
    </tbody>
    <caption>Mobile analysis of Doggos.com</caption>
  </table>
</div>

## Competitive analysis

Once you've analyzed your own site, it's time to analyze your competitors' sites. Comparing results from websites similar to yours is a great way to figure out a performance budget. Whether you are working on an established project or starting from scratch, this is an important step. You get competitive advantage when you are faster than your competitors.

If you are not sure which sites to look at, here are a few tools to try:

1. Google search's "related:" keyword
2. [Alexa's similar sites](https://www.alexa.com/find-similar-sites) feature
3. [SimilarWeb](https://www.similarweb.com)

{% Img src="image/admin/EzpGvSgVJYC2y3rsnHRk.png", alt="Screenshot of Google search with the related keyword", width="775", height="336" %}


For a realistic picture, try to **find 10 or so competitors**.

### Budget for timing milestones

Our niche search engine in this example has a handful of competitors and we'll focus on optimizing the homepage for mobile devices. Over [half of the internet traffic](https://www.statista.com/statistics/277125/share-of-website-traffic-coming-from-mobile-devices/) today happens on mobile networks and using mobile numbers as default will benefit not only your mobile users, but your desktop users as well.

Create a chart with FCP and TTI times for all the similar websites and highlight the fastest in the bunch. A chart like this one gives you a clearer picture of how your website is doing compared to the competition.

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>Site/Homepage</th>
        <th>FCP</th>
        <th>TTI</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>goggles.com</td>
        <td bgcolor="yellow"><strong>880 ms</strong></td>
        <td bgcolor="yellow"><strong>3,150 ms<strong></td>
      </tr>
      <tr>
        <td>Doggos.com</td>
        <td>1,800 ms</td>
        <td>6,500 ms</td>
      </tr>
      <tr>
        <td>quackquackgo.com</td>
        <td>2,680 ms</td>
        <td>4,740 ms</td>
      </tr>
      <tr>
        <td>ding.xyz</td>
        <td>2,420 ms</td>
        <td>7,040 ms</td>
      </tr>
    </tbody>
    <caption>Competitive analysis of Doggos.com on 3G network</caption>
  </table>
</div>

<figure>
  {% Img src="image/admin/Mfzr0dmMxHij9KrJraHD.jpg", alt="Doggo at a computer", width="800", height="600" %}
  <figcaption>
    Doggos.com seems to be doing okay on the FCP metric but seriously lagging behind in TTI
  </figcaption>
</figure>


There's room for improvement and a good guideline for that is the [20% rule](https://www.smashingmagazine.com/2015/09/why-performance-matters-the-perception-of-time/#the-need-for-performance-optimization-the-20-rule). Research states that users recognize a difference in response times when it's greater than 20%. This means that if you want to be noticeably better than the best comparable site, you have to **be at least 20% faster**.

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>Measure</th>
        <th>Current time</th>
        <th>Budget (20% faster than competition)</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>FCP</td>
        <td>1,800 ms</td>
        <td>704 ms</td>
      </tr>
      <tr>
        <td>TTI</td>
        <td>6,500 ms</td>
        <td>2,520 ms</td>
      </tr>
    </tbody>
    <caption>Performance budget that would get Doggos.com ahead of the competition</caption>
  </table>
</div>

If you are trying to optimize an existing site that goal may seem impossible to reach. This is not a sign for you to give up. Start with small steps and set a budget at 20% faster than your current speed. Keep optimizing from there.

For Doggos.com, a revised budget could look like this.

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>Measure</th>
        <th>Current time</th>
        <th>Initial budget
    (20% faster than the current time)</th>
        <th>Long-term goal
    (20% faster than competition)</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>FCP</td>
        <td>1,800 ms</td>
        <td>1,440 ms</td>
        <td>704 ms</td>
      </tr>
      <tr>
        <td>TTI</td>
        <td>6,500 ms</td>
        <td>5,200 ms</td>
        <td>2,520 ms</td>
      </tr>
    </tbody>
    <caption>Revised Doggos.com performance budget</caption>
  </table>
</div>

## Combine different metrics

A solid performance budget combines different types of metrics. We've already defined the budget for milestone timings and now we'll add two more to the mix:

* quantity-based metrics
* rule-based metrics

### Budget for quantity-based metrics

Whatever total page weight number you come up with, try to **deliver** under 170 KB of **[critical-path](/critical-rendering-path/) resources** (compressed/minified). This guarantees your website will be fast even on [inexpensive devices and slow 3G](https://infrequently.org/2017/10/can-you-afford-it-real-world-web-performance-budgets/).

You can have a bigger budget for the desktop experience, but don't go wild. The median page weight on both desktop and mobile is over 1MB according to the [HTTP Archive](https://httparchive.org/reports/page-weight) data for the last year. To get a performant website you have to aim well below these median numbers.

Here are a few examples based on TTI budgets:

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>Network</th>
        <th>Device</th>
        <th>JS</th>
        <th>Images</th>
        <th>CSS</th>
        <th>HTML</th>
        <th>Fonts</th>
        <th>Total</th>
        <th>Time to Interactive budget</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Slow 3G</td>
        <td>Moto G4</td>
        <td>100</td>
        <td>30</td>
        <td>10</td>
        <td>10</td>
        <td>20</td>
        <td>~170 KB</td>
        <td>5s</td>
      </tr>
      <tr>
        <td>Slow 4G</td>
        <td>Moto G4</td>
        <td>200</td>
        <td>50</td>
        <td>35</td>
        <td>30</td>
        <td>30</td>
        <td>~345 KB</td>
        <td>3s</td>
      </tr>
      <tr>
        <td>WiFi</td>
        <td>Desktop</td>
        <td>300</td>
        <td>250</td>
        <td>50</td>
        <td>50</td>
        <td>100</td>
        <td>~750 KB</td>
        <td>2s</td>
      </tr>
    </tbody>
  </table>
</div>

{% Aside %}
  The recommended sizes are for the critical-path resources.
{% endAside %}

Defining a budget based on quantity metrics is a tricky business. An e-commerce website with loads of product photos is very different from a news portal which is mostly text. If you have ads or analytics on your site, that increases the amount of JavaScript you're shipping.

Use the table above as a starting point and adjust based on the type of content you are working with. Define what your pages will include, review your research and take an educated guess for individual asset sizes. For example, if you are building a website with a lot of images, put stricter limits to JS size.

Once you have a working website, check how you are doing on user-centric performance metrics and adjust your budget.

### Budget for rule-based metrics

Very effective rule-based metrics are [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) scores. Lighthouse grades your app in 5 categories and one of those is performance. Performance scores are calculated based on [5 different metrics](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/#perf-audits), including First Contentful Paint and Time to Interactive.

When you try to build a great site, **set Lighthouse performance score budget to at least 85 (out of 100)**. Use [Lighthouse CI](https://github.com/ebidel/lighthouse-ci) to enforce it on pull-requests.

## Prioritize

Ask yourself what level of interaction you expect on your site. If it's a news website, users' primary goal is to read content so you should focus on rendering quickly and keeping FCP low. Doggos.com visitors want to click on relevant links as soon as possible, so the top priority is low TTI.

Find out exactly what part of your audience browses on desktop vs. on mobile devices and prioritize accordingly. One way to figure this out is to check what your audience is doing on competitors' websites, through the [Chrome User Experience report](https://developer.chrome.com/blog/chrome-ux-report-looker-studio-dashboard/) dashboard.

<figure>
  {% Img src="image/admin/ycZwOrFNzjdjquriM9rJ.png", alt="Device distribution data from Chrome User Experience report", width="800", height="530" %}
  <figcaption>
    Device distribution data from Chrome User Experience report
  </figcaption>
</figure>

## Next steps

Make sure your performance budget is enforced throughout the project and incorporate it into your build process.