Matthew Smith

web developer and designer

matt@matthewsmith.io

How to Set Up Cache Busting in Express

by Matthew Smith on September 30, 2017

To increase the loading speed of your website you should make sure to cache your static assets (CSS, images, JavaScripts, etc.) so visitors don’t have to re-download those files every time they visit your website. But if you make changes to those assets you’ll have to ‘bust’ the cache otherwise visitors will see old versions of your static assets. Here’s how you can set up cache busting in your Node.js/Express app.

This tutorial assumes basic knowledge of Node.js and Express. If you’ve never used Express before, MDN has a really great Express tutorial that I recommend.

Overview

Here’s the basic rundown:

  1. Set your static assets to expire in 30 days (or longer).
  2. Inject version numbers into the filenames of your static assets in your view templates.
  3. Remove version numbers from request URLs before your assets are served.

Cache Assets for a Looooooong Time

Cache-Control is an HTTP header field that lets servers determine how browsers should cache resources. One property of Cache-Control is max-age which tells the browser how long to cache the file before re-downloading it.

We’ll set the max-age property to something far into the future. Like 30 days. You may want to set it even longer.

If you’re using express.static to serve your static assets, you can pass in an options object as the second argument which allows you to set the max-age directive. That should look something like this:

app.use(express.static(path.join(__dirname, 'public'), { maxAge: '30 days' }));

Great, now your static assets in your public directory will be cached for 30 days! Visitors won’t have to re-download your CSS, images, or any other static assets in /public.

That sounds great, except what if you update your CSS? If it’s been less than 30 days, your visitors will still get the cached version and won’t see your changes. This is obviously bad.

Now Bust That Cache

If you want visitors to see updated static assets, you’ll have to bust the cache whenever those assets change. This is done in two steps:

  1. Inject version numbers into the filenames of your static assets in your view templates. Browsers will see it as a request to a new file.
  2. In your application logic, before your static assets are served, rewrite the request URL, removing the version number. Express will see it as a request to the non-versioned filename and serve that.

Inject Version Numbers

Version numbers might be a bit of a misnomer because what we’re actually doing is calculating an MD5 hash of the file’s contents and injecting that into the filename. I’m using an npm module called staticify to handle this.

First you’ll need to install staticify:

npm install staticify -save

Then require it in your app:

var staticify = require('staticify')(path.join(__dirname, 'public'));

Use the middleware:

app.use(staticify.middleware);

Create a local variable so you can use staticify in your view templates:

app.locals = {
  getVersionedPath: staticify.getVersionedPath
};

Now in your view templates (I’m using pug), you can use getVersionedPath to inject the MD5 hash into the filename like this:

link(rel='stylesheet', href=getVersionedPath('/stylesheets/styles.css'))

Which renders to HTML:

<link rel="stylesheet" href="/stylesheets/styles.8b7c4cea606b882c691c3f785814e259.css">

Rewrite the Request URL

This is all good and wonderful, except now the browser will send a request to styles.<MD5 hash>.css and it won’t find it because it doesn’t exist. In our application logic, we have to write some code to rewrite the request URL and remove the MD5 hash. Essentially we’re going to say, “Hey Browser, I know you asked for styles.<MD5 hash>.css, but instead you get styles.css.”

When I was trying to set this up for the first time myself, I found this really helpful bit of code from Bryan Burgers:

app.use(function(req, res, next) {
  req.url = req.url.replace(/\/([^\/]+)\.[0-9a-f]+\.(css|js|jpg|png|gif|svg)$/, '/$1.$2');
  next();
});

Basically, it uses a regular expression to replace the MD5 hash in the request URL with nothing, then passes control to the next middleware function with next(). Brilliant! Place that right before your express.static middleware and you should be good to go:

app.use(function(req, res, next) {
  req.url = req.url.replace(/\/([^\/]+)\.[0-9a-f]+\.(css|js|jpg|png|gif|svg)$/, '/$1.$2');
  next();
});
app.use(express.static(path.join(__dirname, 'public'), { maxAge: '30 days' }));

This is the strategy I’m currently using on this website and it’s worked out well so far.

Thanks for reading! Questions? Shoot me an email: matt@matthewsmith.io