Offline First: An Intro to Service Workers
Let’s assume our users will always have a fast, reliable internet connection.
– No One Ever
It seems ridiculous to say it out loud, but that’s how we’ve built websites and applications in the past. The goal of an “offline first” strategy is to assume the opposite: our users have a slow, unreliable internet connection. Why? Because network awareness is a good user experience in any context.
Unfortunately there is no magic bullet – if your application needs content from the server, at some point it will require a connection. But what happens when your user’s connection drops?
Enter the service worker…
What’s a Service Worker?
A generic entry point for persistent event-driven background processing in the Web Platform.
– Paraphrased from the W3C Spec
A service worker is essentially a background script for controlling web pages. This provides application authors the low-level control over the request lifecycle required to deliver a great user experience with low connectivity.
Service Worker Features
- Intercept network requests
- Requests caching
- Push notifications
- Background sync (proposed)
- Geo-fencing (proposed)
Of which, intercepting and caching network requests are the essential building blocks an offline first application. Let’s look at how it works:
Service Worker Demos
These examples assume familiarity with:
Example 1: Service Worker Lifecycle
Registering a service worker loads the script in the background and executes the initial lifecycle events. Notice the register method returns a Promise, and we can access a registration object for unregistering the worker.
A basic service worker script is mainly comprised of event handlers. We’ll load this in Chrome and inspect the console:
First impressions:
- self is an instance of ServiceWorkerGlobalScope. There is no window object.
- event is an instance of ExtendableEvent, which contain new APIs for controlling the service worker lifecycle.
Notice this time we see the fetch events, including URLs from another domain. The request object on fetch events is an instance of the Fetch Request class, with contains useful properties and methods for inspecting the outgoing request.
We also see stale logs from the previous load & an error, which can be very confusing. At this point, debugging service workers is difficult, and I think this is actually a bug in Chrome.
Example 2: Fetching
Using the fetch event you can intercept the request and return a custom response. To do this, we pass a Response object to the respondWith method of the event.
When omitting a custom response, the default behavior is to fetch and return the original response. It’s exactly the same as explicitly calling event.respondWith(fetch(event.request)).
Example 3: Prefetching & Caching
In this example we will prefetch our assets and cache them for later requests.
First off, there’s a global caches object which contains many Cache instances, keyed by unique cache names. The addAll convenience method handles fetching and storing the responses in the cache. We use this to prefetch the assets from CACHE_URLS.
In the network panel, we’ll see the requests for the assets occur in the service worker, which is indicated by the gear icon. This happens in the background and does not affect initial page load time.
Next, in the fetch event we use a cache first strategy of:
- Attempt to get response from the cache, by matching the request.
- If there is no response, fallback to the default behavior of fetching.
When we load the next page, we’ll see the assets are loaded from the cache, indicated by the “(from ServiceWorker)” message.
And finally we’ll use the activate event to delete stale caches. We use the convention of incrementing the CACHE_VERSION for cache invalidation. This deletes any caches that do not match the current cache name.
Example 4: Offline Page
Now, let’s combine these techniques to create an offline page, which will indicate to users we’ve lost the connection.
We’ll use the same prefetching technique from Example 3 to cache the offline page on initial load.
This time, in the fetch event, we’ll use a fetch first strategy and fallback to a cached offline page. To be on the safe side, we check the request to ensure it’s a GET request for HTML content.
It may not seem like much, but this is a great foundation for custom handling of the offline user experience. Here’s what we’ve accomplished:
- Prefetched static assets, for faster subsequent page loads.
- Added a fallback offline page when the user loses their connection.