Going Offline With Service Workers
The average global internet speed is getting faster year by year, but the number of users with a slow 3G connection is still in the millions. The number of mobile internet users is growing and it’s already surpassed desktop usage worldwide a long time ago. Internet connections for these users can be unreliable and sometimes even non-existent. Often they may want to access your content on the go and if you want to provide a seamless user experience — with these people in mind — you have to consider going offline.
Offline Apps
Offline apps try to tackle these connectivity problems by introducing some clever technical solutions, such as
Detecting offline state
To make your web app work offline, you first need to determine your user’s connection status. This can be done with the online
and offline
events and with the help of navigator.onLine
.
Storing data and assets offline
This is where a service worker can shine. You want to store static assets locally in the cache, so your app can load even when the internet connection is down. It also helps your site load faster as it requests the resources from the cache rather than making a network request.
For storing data locally, there are a couple of options but your best bet is using localStorage
. You can also use it to queue requests while offline and call them once the user is back online.
Sync with server
Once the connection is restored, you can sync with the server and save data back that’s been queued during the offline state.
Prerequisites
When it comes to using service workers, there are some prerequisites that must be met. First, we have to point out that it can still be considered an experimental feature, therefore before you use it, you have to look into browser support. It is growing constantly however and only IE is unsupported at this moment. For full coverage, you can refer to the compatibility table at caniuse.com
The other requirement is that your site must be served through HTTPS. This is because of security reasons as service workers act as a proxy that can alter your responses. Therefore it’s essential to ensure that a service worker hasn’t been tampered with. You can still use them locally however, during development.
Lifecycle of a Service Worker
A service worker has a lifecycle that is separate from your app. It runs on a different thread, therefore it’s important to mention that you won’t be able to access the dom and do things you would normally do in your everyday JavaScript files, but service workers are not meant for that anyway.
Register
To add a service worker for the app, you first need to register it in one of your JavaScript files. Once registered, it will cause the browser to start the next step in the background which is:
Install
During installation, you usually want to cache static assets on your site. If everything goes well and all your files are downloaded successfully, then the service worker becomes installed. If any of those files fail to download for some reason, then this step will fail and the service worker won’t be installed. In that case, it will try again next time.
Activated
After the installation step is completed, the service worker becomes activated. In this step, you usually want to update your worker; remove, or update old cache files.
Fetch
Once activated, your service worker can listen for fetch events that will be fired whenever a network request is made. During this time, you can intercept these requests and return the cached version if there’s any or continue with the network request if there’s none.
Terminated
When not in use anymore, the service worker will be terminated to save memory and only becomes available when it’s next needed.
These are the steps we need to go through during implementation, so let’s start by registering it first.
Registering a Service Worker
Registration can be done in one single line:
navigator.serviceWorker.register('sw.js');
Where sw.js
is the location of the JavaScript file, which holds the functions that the service worker needs to perform. A common convention is to create the service worker’s JavaScript file at the root directory of your project, but it can live anywhere as you can pass the path to the register method.
As discussed previously, they are not yet supported in all browsers so it’s a good practice to check if the serviceWorker
object exists on navigator
. Checking for support can be done with:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
}
Installation
Inside the service worker file, you can refer to the object itself with the self
keyword. Looking back at the lifecycle, we can see the very first step we need to take is installing it. Luckily we have handy events for which we can attach event listeners to. To install a service worker we have to attach a listener to the install
event like so:
self.addEventListener('install', (event) => {
// This is where we will cache our files
});
Everything that goes inside the callback function will get executed whenever the service worker gets installed for the very first time. This is where you want to add files to your cache. To do so, we can extend the install
event with the following:
self.addEventListener('install', event => {
event.waitUntil(
caches.open('sw-cache').then(cache => {
return cache.addAll([
'/',
'/assets/css/main.css',
'/assets/js/app.js',
'/assets/img/favicon.ico',
'/assets/img/bg.jpg',
'/assets/fonts/Raleway-Bold.ttf',
'/assets/fonts/Raleway-Regular.ttf'
]);
})
);
});
Starting from the inside out, cache.addAll
takes in an array of files that we would like to cache. A great rule of thumb is to cache only static assets that rarely change, like fonts and images.
We return that to caches.open
, which takes in a chain of promises, which cache.addAll
returns. The sw-cache
string is the name of the cache to use, it can be anything else.
Then we wrap everything inside event.waitUntil
. It takes in a promise which is used to know how long the installation takes. If any of the files inside cache.addAll
fails to download, the installation step will fail.
Returning The Cache
All that’s left to do is intercepting the requests and returning their cached version. For that, we can listen for the fetch
event.
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
Whenever the user navigates to a page that has a service worker installed, it will start to receive fetch
events from each request. Here we use event.respondWith
and pass in a promise: caches.match
, which looks at the request, and if a match is found, it returns the cache created by the service worker. Otherwise, it uses the fetch
function to request the resource over the network and return the value from the function call. This is equivalent to saying:
if (response) {
return response;
} else {
return fetch(event.request);
}
All that’s left to do is to test it out. You can do so by opening the DevTools in Chrome and changing the Online
state to Offline
, inside the network tab. Refresh the page and appreciate how your site still loads without trouble.
It’s also a good time to verify that in case of slow connection it should still load in a breeze. 🐌
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: