The starters guide to service workers

How to make websites work offline

Yannik Schmidt
4 min readDec 22, 2020
Offline websites with service workers

One of the best additions to browser capabilities in recent years was the addition of service workers and offline support in general. We can now preload data, like articles or network intensive data like pictures, style-sheets or JavaScript libraries, from our web service. This is extremely useful for mobile devices in regions with unstable or slow internet connection. (…like this backwater country in the middle of Europe I am living in.)

Basics

The general approach is, to check for new content, whenever there is an internet connection (or access to WLAN) and display it on demand. The service worker is just a fancy piece of JavaScript, which we can install into a clients browser and which will then run independently of the server itself, without needing an active connection.

We generally need two JavaScript snippets for this:

  • the service worker itself, which must be it’s own file
  • the registration of this service worker, which can be inline

The relative position of the registration snippet within the website matters! A register.js in /static/js/ will register a service-worker in this sub-path/scope,
meaning the service worker won’t be doing anything when navigating to for example /something/ or the web root. For now, place register.js in the web root (so it will be available at /register.js).

It’s also important to note, that service workers require HTTPS, even in a test setup, the browser won’t accept any service worker not served via a secure connection. If you don’t have an HTTPS capable web-server, here is a quick tutorial on how to set up one.

Registering the service worker

Assuming the service-worker is deployed at /js/service-worker.js, the registration script should look like this:

register.js

Writing the service worker

Install-event

Contrary to many tutorials out there, we don’t need an ’install’-event listener, but it’s nice to have one, in order to see, if the registration succeeded:

hooking install-event

Fetch-event

If we want subsequent page-loads to work without network, we need to handle fetch-events in our service worker. A fetch event occurs, whenever a new location is queried from outside the service worker, for example from another script or a source in the HTML itself.

In this example we will try to query via network first, then try the cache if the network times out. If no network is available at all, the request is immediately treated as “timed out”.

For this to work, we write one function “fromNetwork” which may load content from the server and another (“fromCache”) to fetch from cache . Then we combine them in the event handler for (network-)‘fetch’.

The network function will try to fetch the resource, and add it to the service-worker’s cache, it will also honour Cache-Control: no-cache — but no other cache control headers.

fromNetwork function

The “fromCache”-function will look for the specific request in our cache and return the matching content or HTTP error 408 (Request Timeout) otherwise.

fromCache function

Now combine those functions into the fetch-event handler, so “fromCache” is called once “fromNetwork” failed.

fetch-event listener

This already works, but it isn’t very useful, because the cache only contains resources, which the user has already accessed. It will also not include resources fetched before the registration and activation of the service worker, meaning: Even if we register the service worker on the starting page of our website, the starting page itself will only be cached on the next load. It therefore makes sense to prefetch resources likely to be used by our user.

Hardcoded prefetching of resources

We will now write a function to prefetch resources, first by hardcoding, then by dynamically asking the server which URLs to fetch.

For this to work we add the “prefetchStuff” function to our ’install’-listener. Be careful with the “cache.addAll function suggested in many tutorials! This function will fail and add NONE of the URLs, if ANY of the requests fail. The prefetch function itself is simple:

static prefetch

Dynamic prefetching of resources

Now that’s great and all, but hardcoding URLs only makes sense for some resources or if there is a build process for the website, which is able to auto generate these URLs.

If, like me, you have a dynamic web-service with new paths being added during runtime, it might be more appropriate, to have a dedicated service on the server, constantly updating the paths which should be prefetched. The following code assumes you have a web service, which returns a JSON encoded list of URLs at /prefetch-info.

dynamic prefetch

If you want to check for updates periodically, you may also set a timer:

setInterval(() => prefetchStuffDynamic, 10000)

Offline indicator

Finally, we probably want a visible indicator, if our page is currently displayed in offline mode. For this we can use the event-listeners “online” and “offline”. They can be added like this, outside of the context of the service worker (e.g. in the registration script):

offline indicator event-hooks

Those listeners call the function “updateOnlineStatus”, in which we can, for example, change the visibility of a certain container:

offline indicator (HTML)

We can check for the current connection status via navigator.onLine:

offline indicator switch

Finally don’t forget to load /register.js from your HTML!

Now please lobby my government, into investing into the abysmal internet infrastructure in my country anyway.

Feel free share your thoughts or ask a question!

--

--

Yannik Schmidt

Python programmer at heart, Linux Expert at work, GameDev on my good days, web developer on the bad days.