Cache Busting with SSR and Webpack 3

January 27th, 2018

Cache busting is the process of getting the browser of a previous visitor to your site to not used cached resources when they return, and to re-request the new resources. This is especially important if you've made breaking back-end changes that require a change in front-end code, or an updated bundle contains a critical fix. Probably the best way of accomplishing this is to just change the name of the file, which is easy to do with Webpack 3 for sites that only serve a client bundle and do not use server-side rendering (SSR).

The Problem

With Webpack, you can add a hash to generated bundle names that changes either when the chunk content changes, or on each build. That's great, but if you're using SSR, the HTML you serve probably has quite a bit of content added by the server, and can't be static or generated at build time.

For example, this site is injecting Glamourous IDs for CSS, pre-loaded state for the Redux store, metadata from React Helmet, and grabbing additional code split bundles for React Loadable.

Solution

The workaround is pretty simple. We can use the Webpack Stats Plugins for Webpack to output the names of the bundles we need with their hashes to a JSON file, and grab them at runtime.

First, let's make sure our Webpack build is generating bundles with hashes in our Webpack config.

Partial Webpack Config (Entry/Output)

module.exports = {
  entry: {
    app: './client/src/index.jsx'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].[chunkhash].js'
  }
};

By specifying [name].[chunkhash].js, the resulting bundle will be named something like app.MyGeneratedChunkhashHere.js. It uses the entry point name, which is app, but if the entry name isn't provided, it'll be main.

After that, we'll want to install the webpack-stats-plugin, and add the plugin to our config.

Partial Webpack Config (Plugins)

const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
module.exports = {
  plugins: [
    new StatsWriterPlugin({
      filename: 'hashed-names.json',
      transform: function(data) {
        return JSON.stringify({
          appJsBundleName: data.assetsByChunkName.app[0]
        });
      }
    })
  ]
};

The StatsWriterPlugin provides access to the Webpack build stats object, and allows you to transform it and write the results out to a file. We named the file hashed-names.json above, and it will be placed in the same directory as the final bundle. We use the assetsByChunkName property to find the full name of the bundle, and we then return a JSON object from the transform hook with the name saved to the appJsBundleName property.

Now we can properly reference the bundle for our server render by importing the JSON at runtime and grabbing the appJsBundleName to insert into our HTML.

let bundleName = require('pathToFile/hashed-names.json').appJsBundleName;

After a redeploy, any returning visitors with a cached bundle will now get the new one!

Next
Apollo and GraphQL Make a Great Impression
Written by Joshua Pohl

Software Engineer in San Antonio, Texas. Find me on Twitter.