What do you think of when you hear “offline web application”? It doesn’t even sound right when you first hear it. One could assume that the main requirement for using web applications is an internet connection. But that’s not entirely true. In this article, I’ll show you how to create web applications that work even when you are offline.

First, let’s think for a minute of what a typical web app needs. Very often, it consists of a variety of resource files like HTML for markup, CSS for styling, Javascript for application logic, and others like images, fonts, etc. On the other hand, the application is worth nothing without data that it either displays or stores (or both).

Static content

Let’s talk about the static content first. We need resource files to be available offline. For this purpose, we can use a mechanism called Application Cache. We can enable it by adding a manifest attribute to our HTML element:

<html manifest=”manifest.appcache”>

...

</html>

This tells the browser that we want to use the Application Cache. The attribute value points to the manifest file which lists all the files we want the browser to cache. The manifest file has a very simple format:

CACHE MANIFEST

# comment

index.html

script.js NETWORK:

other-file.html FALLBACK:

/ fallback.html

You can immediately see 3 sections here:

CACHE:

This section is the most important for us, as it lists all the files we want to cache. Usually, we want to cache all static elements (html, scripts, styles, images, fonts, etc.). This section does not require CACHE: header if it immediately follows the CACHE MANIFEST line.

NETWORK:

This section is for listing resources that require a network connection, even when there is none. We can use wildcard * here, and in most cases, this is what we need.

FALLBACK:

This section is for listing fallback responses when given resources are not available. That’s why each line consists of URIs: the first one is the resource, the second is the fallback.

These sections can appear in any order and as many times as you need. Also, the first line of the file must be CACHE MANIFEST, and you can put any comments in the file (and they are useful, as you will see in a second).

Now, how does the browser use the manifest file? This part is tricky, so read this diagram carefully:

If diagrams are not your thing, simply speaking:

If there is no application cache, the browser will create it when visiting the page

When there is an application cache, the browser will use cached files to load the page

It will also try to load the manifest from the network and check if it has changed

If there is a change, it will create a new temporary cache, with new files

When it’s done, the browser will replace the old application cache with the new one

There are two important things to note about this flow.

The first is that the browser updates the application cache only when the manifest changes. This is where comments become useful. We need to ensure that the manifest file changes whenever we build a new application version, so that browsers know that they need to update the application cache. Common practice is to put a timestamp as a comment immediately after the CACHE MANIFEST line like this:

CACHE MANIFEST

# 1498421309

index.html

script.js

The second thing is that if the application cache exists, the browser will always use it to display the page, and will update the cache only in the background. This means that users will not see the new version immediately. This might not be a problem, but there is a way to work around it if required. There are a few events that the browser sends to the window.applicationCache object that you can listen to, and react accordingly. In particular, the "updateready" event can be used to either refresh the page, or call the swapCache to load all subsequent scripts from a new cache, or even give the user a choice.

Here is a full example of a given manifest file:

CACHE MANIFEST

# 1498421309

index.html

js/script1.js

js/script2.js

css/style.css

assets/image.png

assets/font.ttf NETWORK:

*

For more details, read the documentation.

Data

We covered how we can have an application accessible without the network, but it’s quite rare to have an application that doesn’t require any data store to be functional. We need something for that as well. There are quite a few choices depending on your needs. You could save your data in cookies, but it’s not quite the purpose they were created for, and it would be pretty cumbersome to use them in this way. A better alternative is localStorage, which is very good for key-value data in small amounts.

We’re focusing here on the third option, which are database solutions. These are either WebSQL or IndexedDB, depending on the browser. Since they differ quite a lot, in our project we have decided to use PouchDB library, which unifies them under an easy-to-use CouchDB-like API. This is a document database, which stores data mainly as JSONs. We can also store attachments to the documents, which we can use, say, for data-related images (e.g. avatars).

As always in the web applications world, there are differences in behavior among browsers, especially in terms of size limits, which you can read about here.

PouchDB usage is very simple. First, you need to create a database (there are a few configuration options), and you’re ready to go:

let db = new PouchDB(“cats”);

db.post({name: “Garfield”})

.then(response => db.get(response._id))

.then(garfield => …);

There is one gotcha when using PouchDB. Let’s take a look at deleting documents:

db.delete(doc);

It’s a trap! As noted earlier, size limits for a browser’s database are noticeable, despite being better than localStorage. But why would it matter when removing documents? Seems insane, but it isn’t. When we tell PouchDB to remove a document, it doesn’t actually do it. Instead, it removes all its fields, and marks it as deleted, so there is some (very little) data left over. Without getting into details, this happens because PouchDB is designed to be replicable. While that might seem like a negligible amount of data, it’s worth taking a second and asking yourself a question — how many objects, and how often, are users going to save/remove?

In the case of our application, we did the maths and discovered that if we allow for such ghost objects, after a year our users will have half the original amount of space available (in extreme cases with the Safari browser). We decided to work around this problem by compiling a database for every object we create, and then removing the entire database when they have finished working on it. This is working quite well for us, as our users create only a few objects a day. In your case, this problem might be safe to ignore, or you might consider some other approach.

For more information read PouchDB documentation.

Summary

Combining Application Cache with PouchDB allowed us to create a fully functional web application that users can harness even in areas where the connection is poor or non-existent.

Disclaimer: Application Cache is already marked as a “deprecated” feature, and Firefox advises using Service Workers instead. On the other hand, Service Workers are still marked as an “experimental” feature, and not yet operational in all browsers. You can find more details here.