Nowadays, most web apps run without offline capabilities. They depend almost totally on a server. We can observe this when, from our browser in a web app, we get disconnected and the dinosaur game appears onscreen. This is how Google Chrome tells us we are offline (Figure 4-1).
This is not the only scenario we want to avoid. There is also a common scenario in which LiFi shows neither our app nor the dinosaur, but leaves users waiting with a white browser window.
Some solutions work well to handle this nonconnection status, such as PouchDB or Firebase. However, with the use of web technologies such as service workers, IndexedDB, and the cache API, we can plan our offline strategies for our PWAs.
When to Store Information
If you remember, a service worker has stages in its life cycle (Figure 4-2), beginning with when it is installed until it is terminated in a web app. These moments are perfect to start mechanisms that save, update, return, or delete data from cache storage. Let’s look at some well-known strategies that can be applied to our PWAs.
The install event is the best moment to store files for the first time. If you remember the service worker life cycle, this stage is reached after it is detected in a web app. To reach it, we do something like this:
As you can see, we have our files to store in the files array in our app shell. Next, we go in the install event, which brings the app shell elements from the network and stores them in cache storage, which then gives way to the activate event of the service worker (Figure 4-3).
When to Update Files in the Cache
Although it is supposed that the files in our app shell do not change frequently, they could do so, for example, with a user experience improvement, a branding change, and so on. We have to prepare for this moment and avoid saddling our users with version 1 of our app forever.
To handle a cache storage update, we use a version number in the name of our cache or the activated event of the service worker life cycle to eliminate older files and replace them with the new ones. Updating our service worker, looks something like this:
If you look at the previous code, you’ll see that we added a series of operations when our service worker gets to our activate event. We verify whether the name that it brings in cacheNames has the same version number that we have in version. If it doesn’t, we delete the other cache that is detected (Figure 4-4). This ensures that previous versions, such as cache-v0.0.0 or cache-v0.0.1, get deleted from the client’s cache storage.
These two moments—when we store files in the cache and when we update cache storage—are usually common in every app.
Responding to Requests
Although there are many strategy mixes to respond to user requests, there are some identified patterns that work great with most apps.
The service worker triggers a fetch event when we make a request. It contains information about the fetch, including the request and how the receiver will treat the response:
sw.js
self.addEventListener("fetch", (event) => {
});
cacheFirst
The cacheFirst pattern responds to requests with files from cache storage. If the response from cache storage fails, it tries to respond with files from the network (Figure 4-5).
Use case: When your content changes infrequently, such as a corporate site. The content is always the same. You can save the files in the first use, provide a fast charge in the next uses, and update unfrequented changes in the content if this happens.
The networkFirst pattern responds to all requests with network content. If it fails, it tries to respond with content from cache storage (Figure 4-7).
Its implementation should look like this:
sw.js
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request).catch(function() {
return caches.match(event.request);
})
);
});
Use case: When your content is updated frequently, such as a blog that can be read offline. The content changes all the time and users want to read the latest content. However, if the user is offline, you can at least show the last stored content.
The stale-while-revalidate pattern responds to all requests with cache content, if available, from cache storage; but, it also fetches an update from the network for next time (Figure 4-8).
Use case: When content is frequently updated but the latest version is not critical, such as a news feed app. The content changes often, but you can show updated content in the next visit without providing a lousy experience for users.
At this point, we’ve looked at the manifest, service workers, and cache strategies. Now, we can simplify our life with a package that we can install using Vue CLI:
$vue add @vue/pwa
With this package, we add Workbox along with the official plug-in. When you run the command, you’ll see something like what is shown in Figure 4-9.
And the important thing is that, now when we run
$npm run build
we’ll see the new service worker in our dist/ folder. Because of the previous changes, we need to keep our changes in the icons and manifest files.
With service workers, we can save assets and add offline support in our web apps. We looked at some patterns and good practices that help us deliver a better experience to our users. We also studied the events of the service worker life cycle to determine when to use storage capabilities effectively and efficiently.