Make your existing React app progressive with Webpack

Nikita Sushkov
4 min readJan 3, 2017

--

PWA (Progressive Web Apps) is a hot technology of 2016 and it seems that this trend will continue in 2017. Here I will not delve into what PWA is and why you may decide to build one in the first place. There are plenty of articles out there including excellent blog series by Addy Osmani (1, 2, 3, 4) and less technically-oriented posts by Eric Elliot (1, 2) which cover these topics in a great depth. Instead, I would like to focus on how you actually can make your existing React App progressive with Webpack and what options do you have.

Basically, if we want to setup PWA functionality with Webpack, there are two main kids on the block: OfflinePlugin, which is included in React Boilerplate and sw-precache-webpack-plugin leveraged by sw-precache library. While OfflinePlugin provides more functionality out of the box and can be a solid option for certain use cases, I find it too high level and a little bit opinionated. Especially, when you need to cache dynamic content as well as static. So at this moment we’ll go with sw-precache-webpack-plugin.

1. Update your Webpack config.

The setup for a static cache is pretty straightforward:

module.exports = {

plugins: [
new SWPrecacheWebpackPlugin({
cacheId: ‘my-project-name’,
filename: ‘my-sw-file.js’,
maximumFileSizeToCacheInBytes: 4194304,
staticFileGlobsIgnorePatterns: [/\.json/, /\.map/, /\.xml/]
})
]
}
...
}

Here we set a cache name, which our service worker will use to store static data, a file name for our service worker code (will be stored in the output directory), maximum file size to cache and ignore patterns for static files, that we don’t want to be cached. If you run Webpack build and then open ‘my-sw-file.js’, you’ll see that the generated code is pretty straightforward. All assets names that should be cached, are placed into precacheConfig array and cache name set to something like sw-precache-v2-my-project-name. Then it adds three event listeners for ‘install’, ‘activate’ and ‘fetch’ events of service worker lifecycle. Here we should mention, that you should avoid changing the cache name: the code in ‘activate’ event handler only clears previous values of the same cache.

2. Register service worker for your page

This code does the job:

(function() {
if(‘serviceWorker’ in navigator) {
navigator.serviceWorker.register(‘/my-sw-file.js’);
}
})();

In a case you are using server-side rendering with React Helmet (like I do) the above code can be added in the following way:

<script dangerouslySetInnerHTML={{
__html: `(function() {
if(‘serviceWorker’ in navigator) {
navigator.serviceWorker.register(‘/my-sw-file.js’);
}
})();`
}}></script>}

3. Add manifest file.

You can find some Webpack plugins that help with this task, but in my case simple copying of the manifest file to the output directory worked well. You can name this file whatever you want, for instance ‘app-manifest.json’. It is recommended that you provide “name”, “short_name”, “description”, “start_url”, “display”, “orientation”, “background_color” and “theme” properties as well as at least one value for icons array with a size 144x144.

Caching strategies

So far, so good, but what happens when we change some of our assets files? For example, if we change our js code, css or any of referenced libraries is updated. In this case we still get an old file from our service worker. The same thing can happen with the basic HTTP caching enabled as well. The solution here is to add [hash] or even better [chunkhash] part to your Webpack output file pattern. Therefore our new my-sw-file.js will contain the updated resources and after loading it will remove old URLs from our cache.

But wait, there is a problem here: we need to ensure that our ‘my-sw-file.js’ is not cached by itself (if, again, HTTP caching is enabled). We have two options: we can set Cache-Control max-age to 0 or Cache-Control: no-cache and in this case the application checks the server first and only after that the cached version can be used, (read this article by Jake Archibald to dig deeper) the other option is to update service worker file name each time some of our assets are changed, for instance, we can use GUID as a part of our pattern.

The last thing we need to enable offline-first experience for our application is to implement service-worker caching strategy for HTML content. We can’t use cache-first strategy for static content provided in sw-precache-webpack-plugin by default, especially if we use server-side rendering and our HTML content is dynamic by nature. So what to do? sw-toolbox to the rescue. Moreover, you do not need any other plugin to use dynamic caching: sw-toolbox is used under the hood when you specify runtimeCaching option for sw-precache-webpack-plugin. In our case the following configuration can be used:

new SWPrecacheWebpackPlugin({

runtimeCaching: [
{
handler: ‘fastest’,
urlPattern: /^https:\/\/(www\.)?my-awesome-site.com$/
}
]
}

We use fastest strategy for this type of assets: first, we consult our cache and server and return the fastest response immediately, than, if our network is slower than cache, we update our cache with a newer version in the background.

That’s it! We enabled an offline-first experience for our application. Btw, you can check out how actually ‘progressive’ your app is using the Lighthouse plugin.

--

--