logo
Navigate back to the homepage

Service Worker and Server Side Rendering, hmm? 🤔

Kamlesh Chandnani
October 30th, 2020 · 4 min read

Building an offline experience for an app is very important especially when it comes to User Experience. As a user what would you prefer when you’re suddenly disconnected from the network? A Dinosaur screen or a screen which keeps up your context with the application? If you ask me the same question I would always prefer the latter.

Now the next question is how do we achieve that? In this post I’ll be talking about how we can build an offline experience for our web apps using various tools and strategies.

Let’s Begin! 🎢

Different Offline Experiences

There are different approaches and use cases to build an offline experience based on the different behavior of our apps.

When we say experience it’s very subjective and differs use case by use case. It’s also applicable when you think about the offline experience. Every business and product manager wants to define the offline experience of their application based on the nature of their product, the business vertical they operate in and the target customers of that product. Based on the past experience I’ve discovered there are two widely followed offline experience patterns.

1. You want your users to be always connected to the network. If the users are disconnected block the whole application with a CTA to retry.

treebo

2. Allow the users to see all the things on the visited page with a small notification that says that you’re not connected to the network and block all the further interactions with the page.

swiggy

Different Types of Modern Web Applications

Modern web apps today are widely categorized into two:

  1. Statically Generated Applications
  2. Dynamically Genratated Applications

If your application is built using any static site generator like(Gatsby,Next or anything custom) then things are pretty straight forward as you can precache the pages easily since each of them would be individual HTML pages and that information is available at the build time.

But things change once you have a dynamic site(server side rendered applications) whose pages are generated at runtime and now since there are no HTML pages at build time you can’t really precache them since there’s no information available at build time.

In this post I’ll be focussing on how we can build an offline experience when you have dynamic sites or server side rendered applications.

Not familiar about what is SSR applications? Then read my previous post Server Side Rendering, the better way!

First of all, to begin with we need something that can intercept our network request so when we are disconnected from the network, we can take control of what to show to the user in that scenario. Service workers to the rescue!

But, What is a Service Worker?

A service worker is a network proxy, which sits in between our web page and the Internet. This lets us control incoming and outgoing network requests. Service Workers plays an important role in building offline experience since it’s a programmable piece which we can control as per our use case.

Service Worker

Service Worker provides us with different APIs to handle multiple things like controlling the registration of a service worker, notify when new updates are available, manipulate response to a network request etc. Read more about Service workers here.

To give service worker control of our application we need to register it. Here’s how the code for registring looks like

1// service-worker.js
2
3var CACHE_NAME = 'my-site-cache-v1';
4var urlsToCache = [
5 '/',
6 '/styles/main.css',
7 '/script/main.js'
8];
9
10self.addEventListener('install', function(event) {
11 // Perform install steps
12 event.waitUntil(
13 caches.open(CACHE_NAME)
14 .then(function(cache) {
15 console.log('Opened cache');
16 return cache.addAll(urlsToCache);
17 })
18 );
19});
20
21self.addEventListener('fetch', function(event) {
22 event.respondWith(
23 caches.match(event.request)
24 .then(function(response) {
25 // Cache hit - return response
26 if (response) {
27 return response;
28 }
29 return fetch(event.request);
30 }
31 )
32 );
33});
1// app-entry-file.js
2if ('serviceWorker' in navigator) {
3 window.addEventListener('load', function() {
4 navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
5 // Registration was successful
6 console.log('ServiceWorker registration successful with scope: ', registration.scope);
7 }, function(err) {
8 // registration failed :(
9 console.log('ServiceWorker registration failed: ', err);
10 });
11 });
12}

As you can see writing the service worker by hand seems like too much of work? But don’t lose hope we will be using Workbox which will help us ease a lot of these efforts with better DX and decalrative APIs.

But, What is Workbox?

Writing things by hand can be tricky at times and hence the Google Chrome team created Workbox which is a collection of JavaScript libraries that eases our process of setting up Service Worker.

Workbox has different APIs in form of modules for example core, precaching, routing, background sync, google analytics etc.

Read more about Workbox here

Raw Service Worker vs Workbox

The code that we saw above for service-worker can be written in a simplified form with workbox as below

1//service-worker.js
2import { setCacheNameDetails, skipWaiting, clientsClaim } from 'workbox-core';
3import { registerRoute, setCatchHandler } from 'workbox-routing';
4import { precacheAndRoute, getCacheKeyForURL } from 'workbox-precaching';
5import { NetworkOnly } from 'workbox-strategies';
6
7// set the cache name
8setCacheNameDetails({ prefix: 're-ssr' });
9
10// don't wait and install immediately
11skipWaiting();
12
13// take the control of the application in the current tab immediately
14clientsClaim();
15
16// precache all these assets and serve from cache until the new version release
17precacheAndRoute([
18 '/',
19 '/styles/main.css',
20 '/script/main.js'
21]);
1// app-entry-file.js
2if ('serviceWorker' in navigator) {
3 window.addEventListener('load', function() {
4 navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
5 // Registration was successful
6 console.log('ServiceWorker registration successful with scope: ', registration.scope);
7 }, function(err) {
8 // registration failed :(
9 console.log('ServiceWorker registration failed: ', err);
10 });
11 });
12}

Simple, straight forward right and amazing developer experience, isn’t it?

Different ways to use Workbox

Depending on your project, you can use Workbox in three different ways:

  1. command-line interface which lets you integrate workbox into any application you have.
  2. Node.js module which lets you integrate workbox into any Node build tool such as gulp or grunt.
  3. webpack plugin which lets you easily integrate with a project that is built with Webpack(We’ll be using this to setup our service worker).

So now since we have touched upon what is Service Worker and how using Workbox will help us ease our way to create an offline experience for our apps, let’s see things in action 👨🏻‍💻

I’ll be using this GitHub repository for the demonstration purpose.

Code in Action

For the demonstration purpose I’ll be showing an overlay with a Button to retry if the user has disconnected from the internet and makes a new page request.

There are 4 steps in total:

  1. Add routing to service worker to intercept network requests.
  2. Use workbox-webpack-plugin to inject precache files to our service worker.
  3. Add route to express server to serve service worker.
  4. Register service worker when the application loads in the browser.

Step 1. Add routing to service worker to intercept network requests


1import { setCacheNameDetails, skipWaiting, clientsClaim } from 'workbox-core';
2import { registerRoute, setCatchHandler } from 'workbox-routing';
3import { precacheAndRoute, getCacheKeyForURL } from 'workbox-precaching';
4import { NetworkOnly } from 'workbox-strategies';
5
6setCacheNameDetails({ prefix: 're-ssr' });
7
8skipWaiting();
9
10clientsClaim();
11
12precacheAndRoute(self.__WB_MANIFEST);
13
14registerRoute(({ event }) => event.request.destination === 'document', new NetworkOnly());
15
16// when there's no network connectivity and we try to navigate it will be handeled by catch handler
17setCatchHandler(({ event }) => {
18 if (event.request.destination === 'document') {
19 return caches.match(
20 getCacheKeyForURL(`/offline.html`),
21 );
22 }
23 // If we don't have a fallback, just return an error response.
24 return Response.error();
25});

Step 2. Use workbox-webpack-plugin to inject precache files to our service worker


1// webpack.client.js
2const { InjectManifest } = require('workbox-webpack-plugin');
3
4plugins: [
5 // other plugins
6 new InjectManifest({
7 swSrc: path.resolve(__dirname, 'serviceWorker.js'),
8 swDest: 'service-worker.js',
9 exclude: [/\.map$/, /\.json$/],
10 }),
11]

Step 3. Add route to express server to serve service worker


1// server.js
2app.use('/service-worker.js', express.static('build/client'));

Step 4. Register service worker when the applications loads in the browser


1// entry-file.js
2import { Workbox } from 'workbox-window';
3
4if ('serviceWorker' in navigator) {
5 const workbox = new Workbox('/service-worker.js');
6 workbox.addEventListener('activated', (event) => {
7 if (!event.isUpdate) {
8 console.log('ServiceWorker registration successful!');
9 }
10 });
11 workbox.register();
12}

And that’s it we have setup an offline experience for our SSR app using workbox 🚀

demo


If you have run into problems with SSR and service workers and solved them with different ways I would love to hear your stories. You can write it to me or you can DM me on Twitter.

If you like this then don’t forget to
🐤 Share
🔔 Subscribe
➡️ Follow me on Twitter

Join the Newsletter

Subscribe to get the latest write-ups about my learnings from JavaScript, React, Design Systems and Life

No spam, pinky promise!

More articles from Kamlesh Chandnani

Server Side Rendering, the better way!

Set up Server Side rendering from scratch the easier and the scalable way

September 22nd, 2020 · 8 min read

Mentoring - A natural process

You don't need to wear a title for mentoring and giving feedbacks

July 28th, 2020 · 3 min read
Link to https://github.com/kamleshchandnaniLink to https://twitter.com/@_kamlesh_Link to https://www.youtube.com/playlist?list=PLpATFO7gaFGgwZRziAoScNoAUyyR_irFMLink to https://linkedin.com/in/kamleshchandnani