![Michael DiBlasio](https://web.dev/images/authors/mdiblasio.jpg) Michael DiBlasio [GitHub](https://github.com/mdiblasio)

<br />

> [!IMPORTANT]
> **Tip:** To follow this codelab, open [this Glitch demo](https://glitch.com/edit/#!/fav-kitties-compress-starter) in a second tab.

> [!NOTE]
> **Note:** This codelab uses Chrome DevTools. [Download Chrome](https://www.google.com/chrome) if you don't already have it.

This codelab is an extension of the [Minify and compress network payloads
codelab](https://web.dev/articles/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 (`br`) can further reduce compression ratios and your app's
overall size.

![App screenshot](https://web.dev/static/articles/codelab-text-compression-brotli/image/app-screenshot-9ab00b5c0f965.png)

## Measure

> [!NOTE]
> **Note:** Since [webpack](https://webpack.js.org) 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.

Before diving in to add optimizations, it's always a good idea to first analyze
the current state of the application.

1. Click **Remix to Edit** to make the project editable.
2. To preview the site, press **View App** . Then press **Fullscreen** ![fullscreen](https://web.dev/static/images/glitch/fullscreen.svg).

In the previous [Minify and compress network payloads
codelab](https://web.dev/articles/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

> [!WARNING]
> **Warning:** Many hosting platforms, CDNs and reverse proxy servers either encode assets with compression by default or allow you to configure them with less effort. 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.

[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 [all
modern browsers](https://caniuse.com/#feat=brotli). Browsers that support Brotli
will include `br` in `Accept-Encoding` headers:

```
Accept-Encoding: gzip, deflate, br
```

You can determine which compression algorithm is used using the
`Content-Encoding` field in the Chrome Developer Tools Network tab
(`Command+Option+I` or `Ctrl+Alt+I`):
![Network panel. The Content-encoding column displays encodings used for various assets, encluding gzip and brotli (br).](https://web.dev/static/articles/codelab-text-compression-brotli/image/network-panel-9ae4f0c414193.png)

## How to enable Brotli

How you set up a web server to send Brotli-encoded resources depends on *how*
you plan to encode them. Your options are to dynamically compress resources with
Brotli at the time of the request (dynamic), or encode them ahead of time so
they're already compressed by the time the user requests them (static).

### Dynamic compression

**Dynamic compression** involves compressing assets on-the-fly as they get
requested by the browser.

#### Advantages

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

#### Disadvantages

- 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 and Express

The `server.js` file is responsible for setting up the Node server that hosts
the application.

    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 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`:

    "devDependencies": {
      // ...
      "shrink-ray": "^0.1.3"
    },

And import it into the server file, `server.js`:

    const express = require('express');
    const shrinkRay = require('shrink-ray');

And add it as a middleware before `express.static` is mounted:

    // ...
    const 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:
![Bundle size with dynamic Brotli compression.](https://web.dev/static/articles/codelab-text-compression-brotli/image/bundle-size-dynamic-brot-1b3deb34e927b.png)

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

> [!NOTE]
> **Note:** 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])`.

### Static compression

The idea behind static compression is to have assets compressed and saved ahead
of time.

#### Advantages

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

#### Disadvantages

- Assets need to compressed with every build. Build times can increase significantly if high compression levels are used.

#### Static compression with Node and Express with 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`:

    "devDependencies": {
      // ...
     "brotli-webpack-plugin": "^1.1.0"
    },

Like any other webpack plugin, import it in the configurations file,
`webpack.config.js`:

    var path = require("path");

    //...
    var BrotliPlugin = require('brotli-webpack-plugin');

And include it within the plugins array:

    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.
2. Click the **Console** button.
3. In the console, run the following commands to change into the `public` directory and see all of its files:

    cd public
    ls -lh

![Bundle size with static Brotli compression](https://web.dev/static/articles/codelab-text-compression-brotli/image/bundle-size-static-brotl-d7b6cec6c6ef7.png)

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

    const express = require('express');
    const 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`:

    const express = require('express');
    const 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.
![Bundle size of 53.1 KB (from 225KB)](https://web.dev/static/articles/codelab-text-compression-brotli/image/bundle-size-531-kb-22-4ffeb537cb8ee.png)

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

> [!WARNING]
> **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.