Learn how to customize Google Workbox’s default behavior for a service worker in your React app. This articles explains how to modify the behavior of the service worker life cycle, enabling fully automated updates of your app in the background. Includes a bonus section with a React Bootstrap component alert for app update announcements.

Setting Up A Service Worker

The smart way to get started with service workers is to use create-react-app, which provides a simple way to scaffold a basic service worker for a React app:

[code lang=”bash” classname=”codeboxes” gutter=”false”]npx create-react-app hello-world –template cra-template-pwa[/code]

This quick start approach gives your site basic offline capabilities by adding two boilerplate js modules to the root of your project, service-worker.js and serviceWorkerRegistration.js. It also installs Google’s npm package workbox-sw that vastly simplifies everything about managing a service worker.

By the way, a couple of reference sources that I came across bear mentioning. First, the web site create-react-app.dev contains documentation that explains how to customize these two boiler plate files to optimize your site’s offline behavior. Additionally, if you’re just getting started with service workers then I’d recommend this blog post from Danielly Costa, “Showing ‘new version available’ notification on create-react-app PWAs“. It’s well-written, easy to follow, it’s relatively current as of this publication, and it’s what I used when I was just getting started.

Moving along, the boiler plate code adds the following to your React app:

  • Registers a service worker for your React app

  • Automatically detects and installs updates to your service worker and offline content

  • Sets up basic caching to enable performant offline viewing of your site’s static content

The only thing that I dislike is that this boiler plate does several things to bring attention to your site’s offline capabilities. I don’t want that. I want my site to be resilient with regard to Internet connectivity but I’d rather that my users don’t even notice when they’re offline. The less they notice, the better. Summarizing what you get versus what I actually wanted:

The default service worker life cycle

By default Workbox registers a service worker and then checks for and downloads any updates to your code base. At that point, it pauses the update process until all browser tabs and windows containing your app have been closed. Then, only upon the user’s next visit to your site does it actually update the locally cached content to activate these changes in the user’s browser. Additionally, just a slightly annoying side effect is that the boiler plate code sends messages to the javascript console any time the app is being accessed offline, and also when code is updated.

Versus what I wanted instead

By contrast, I not only want code updates downloaded and seamlessly activated but I also want the service worker to periodically check for updates say, at least once a day to ensure that users are never at risk of running dangerously outdated code.

I prototyped my desired behavior in my personal web site, lawrencemcdaniel.com (see the screen cast below). The source code is located in this Github repository in case you prefer to see the entire code base as opposed to the abbreviated code snippets that follow. I use the same code base for several other blog posts about ReactJS, Redux, REST api’s and front-end coding in general.

Some Editorial Comments On Hacking Google Workbox

I only had to modify a few files to achieve my desired behavior. The hard part, for me at least, was developing a technical understanding of what a service worker does, and then developing a commensurate understanding of how Google Workbox is trying to simplify how you work with them. With regard to the former, I found this documentation from Mozilla the most informative, “MDN Web Docs: Service Worker API“. Learning about Workbox on the other hand is more of a challenge. I ended up tracing the source code.

Service Workers are are powerful. The scaffolding that you get from create-react-app really only scratches the surface. Having said that, there are weird gaps in the Service Worker API. For example, there are events for ‘fetch’ and ‘install’ and ‘activate’ but not for ‘fetched’ and ‘installed’ and ‘activated’, even though there are defined states for these.

The service worker life cycle model is simple. A service worker has four possible states, which migrate as follows: installing, then installed, then activating, then activated. There’s also a general purpose exception state called redundant. The built-in wait period between ‘installed’ and ‘activating’ can be overriden by calling skipwaiting() (see an example of this in the default service-worker.js. You should note a couple of things however. First, it is recommended that your React app communicate with the service worker via a formal postMessage() api. And second, that regardless of how you call skipwaiting(), it will only have an effect if there is actually a installed worker in an ‘installed’ state.

Debugging service workers is a challenge. For one thing, they only run on your “production” build, which obviously complicates testing. But additionally, the very nature of service workers is that they directly control what version of your code you’re running, and that can get confusing. Lastly, its all event-driven which itself is challenging to trace and debug. My advise is to make copious use of console logging, as per the video screen cast above, so that execution threads are abundantly clear.

Summary of file modifications

Source File Summary of changes
index.js Remove boiler plate service worker registration
service-worker.js No modifications are necessary
serviceWorkerRegistration.js Add a hook for serviceWorkerRegistrationEnhancements
serviceWorkerRegistrationEnhancements.js New js module to implement onActivate event, plus, periodic update checks
App.js Refactor to class component. Add event handlers (callbacks) for Workbox service worker life cycle events. Add appUpdate components for announcements.
appUpdate React component to render Bootstrap announcements.

index.js

Remove the reference to serviceWorkerRegistration. We’re going to migrate this to App.js in the next step so that we can leverage the App component life cycle for rendering announcements about changes to the service worker lifecycle.

[code lang=”js” classname=”codeboxes” gutter=”false”] // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://cra.link/PWA

serviceWorkerRegistration.register();[/code]

service-worker.js

I did not need to make any changes to this file as part of these modifications. Note however that I did make extensive modifications to service-worker.js when setting up the cache behavior of my React app, though this is out of scope of this blog post.

serviceWorkerRegistration.js

I added a hook for my own behavioral enhancements, as follows:

[code lang="js" classname="codeboxes" gutter="false"] import from "./serviceWorkerRegistrationEnhancements";

// approximately row 57
function registerValidSW(swUrl, config)
// more boiler plate code follows …..[/code]

serviceWorkerRegistrationEnhancements.js

This new additional js module implements an `onActivate` event which App.js listens for in order to raise a Bootstrap alert after updates are downloaded, installed, and activated. It also implements periodic daily checks for updates to the service worker.

[code lang="js" classname="codeboxes" gutter="false"] export function serviceWorkerRegistrationEnhancements(config, registration) [/code]

Note: At this point we’ve fully implemented the modifications that necessary to create automated updates with periodic checks in the background. The remaining code samples are only necessary for rendering announcements of these activities to the browser.

App.js

BEFORE

[code lang="js" classname="codeboxes" gutter="false"] import React from 'react';

function App()

export default App;[/code]

AFTER. Note that I refactored the default App from a functional to a class component. This is necessary so that we can make use of the class component life cycle methods.

[code lang="js" classname="codeboxes"] import React, from 'react';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';

//
// misc app imports …
//

// UI stuff for service worker notifications
import AppUpdateAlert from './components/appUpdate/Component';

const UPDATE_AVAILABLE_MESSAGE = "New content is available and will be automatically installed momentarily.";
const SUCCESSFUL_INSTALL_MESSAGE = "This app has successfully updated itself in the background. Content is cached for offline use.";

class App extends Component

export default App;[/code]

appUpdate React Component

This React component is used in App.js. It renders a Bootstrap alert to the top of the screen which automatically disappears after 5 seconds and then fires an optional callback.

[code lang="js" classname="codeboxes" gutter="true"] import React from 'react';
import from 'reactstrap';

import './styles.css';

const ALERT_VISIBILITY_SECONDS = 5.0;

class AppUpdateAlert extends React.Component

export default AppUpdateAlert;

[/code]

I hope you found this helpful. Contributors are welcome. My contact information is on my web site. Please help me improve this article by leaving a comment below. Thank you!