Johannes Siipola

Poor man’s Brotli: serve Brotli compressed files without a nginx module

2018-04-24

Brotli is an awesome new compression algorithm that compresses better than Gzip. You can use it to make your web pages weight less and load faster. Brotli is well supported in in browsers, at the moment 84% of browsers support it globally.

Brotli is named after a Swiss bakery product

But when it comes to servers however, nginx does not have official support for Brotli, you have to either compile nginx yourself with Google’s ngx_brotli or you have to install an unofficial binary version of nginx that has the module baked in.

This is not always possible, for example, if your sysadmin does not allow unofficial versions of nginx. Or maybe you prefer the stability of packages included in your distro.

But we are not completely out of luck! That's because web servers support two kinds of compression: dynamic or static.

Dynamic compression happens on the fly. It’s most effective for dynamic content like HTML pages and JSON API responses. The downside is that you cannot use high compression ratios because that would delay the response. This kind of compression cannot be done without ngx_brotli extension which provides brotli directive to control the compression.

Static compression, however, happens at the build time when you release a new version of your application. You simply run your CSS, JavaScript and SVG files through the brotli command line client to generate the brotli versions from your static assets. For style.css you might create style.css.br. The server will then serve the brotli files to browsers that support brotli encoding. Because files are compressed ahead of the time, this allows you to use higher compression ratios compared to dynamic serving. This functionality is provided by brotli_static directive in brotli nginx extension.

While we can’t do dynamic compression without the nginx module, if we have pre-compressed files, we can serve them using nginx with some configuration trickery.

First, let’s create some brotli files. Because I like gulp, I can use the gulp-brotli plugin to automatically compress the files.

const gulp = require('gulp');
const brotli = require('gulp-brotli');

gulp.task('compress', function () {
  return gulp.src('dist/**/*.{css,js,svg}')
    .pipe(brotli.compress({
      quality: 11,
    }))
    .pipe(gulp.dest('dist/'));
});

You can execute this task by running

npx gulp compress

Now that we have the Brotli files compressed and ready, we can open up the nginx configuration and add the following:

set $extension "";
if ($http_accept_encoding ~ br) {
    set $extension .br;
}

if (-f $request_filename$extension) {
    rewrite (.*) $1$extension break;
}

location ~ /*.css.br$ {
    gzip off;
    types {}
    default_type text/css;
    add_header Content-Encoding br;
    add_header Vary "Accept-Encoding";
}

location ~ /*.js.br$ {
    gzip off;
    types {}
    default_type application/javascript;
    add_header Content-Encoding br;
    add_header Vary "Accept-Encoding";
}

location ~ /*.svg.br$ {
    gzip off;
    types {}
    default_type image/svg+xml;
    add_header Content-Encoding br;
    add_header Vary "Accept-Encoding";
}

Now if the browser supports Brotli encoding and the filesystem contains the requested file with .br extension, the compressed file is served to the client with correct Content-Encoding header.

We can test this in the browser by navigating to the page with the latest version of Chrome. Just keep in mind that will need to have a TLS certificate set up, otherwise, the browser will fall back to Gzip.

No Brotli

Brotli support enabled

As you can see in the Content-Encoding column, Brotli is successfully enabled. Our file sizes have also been shrunk. style.css is down from 20.9 kilobytes to 15.3 kb compared to Gzip, an impressive 27 percent drop!

And just like that, we have enabled Brotli on nginx without installing any additional modules.

Extra tip: if you use Apache instead of nginx, you can use a similar technique as outlined in this article

Comments