Making open web apps functional while offline is an important problem to solve: users may lose network while on the move. In addition, having your files available locally will make your app require less server access and therefore be more responsive.
This article provides recommendations for getting apps working offline as quickly and as easily as possible, along with FAQs, examples and further reading at the bottom for anyone needing more detailed information on the inner workings of the technologies involved including IndexedDB, localStorage, Application Cache, XMLHttpRequest and more.
Offline workflow
The following diagram illustrates the typical workflow of an offline app. The entry point is when the app is downloaded/installed and then launched — navigated to in the case of an online web app, and installed and opened in the case of an installable app (e.g. on Firefox OS.) At this point the standard behaviour should be to store the assets and initial dataset for the app on the device, if possible, after which the app can be used, storing its dataset offline and periodically syncing with the dataset on the server.
For future running of the app, the app should try to detect whether it is online or not. If it is, it should sync with the server and download new data and assets. If not, it should continue to use what it has offline.
Recommendations
The following is a set of recommendations and best practices for getting up and running with offline web apps quickly.
Detecting offline state
There are a few APIs available to determine the user's connection status. These are useful because you can skip HTTP requests if you know the user is offline. You can also display less generic failure messages. For example: if you were writing a Twitter client, a message like “New tweets cannot be loaded while offline” is more informative than “Could not make HTTP connection.”
Try to focus on loading data from your/others' API only when needed, and keep data cached so you don't have to request the same data twice. It can also be helpful to prefetch some data so the user can use parts of your app offline that they have yet to open.
The Network information API is designed to detect online status, but this is not very reliable. There are a number of other offline detection mechanisms, but we would recommend using an offline detection library, such as offline.js.
If you are using XMLHttpRequest to update data dynamically, you can check its response to determine if the network connection has dropped during the use of your app. If your app has systemXHR privileges, you can check for a connection to a site unlikely to be down to determine if you have an active connection (iOS does this with Apple.com).
// We'll assume we aren't online. var online = false; // Assume we're a packaged app with systemXHR permissions. // https://developer.mozilla.org/docs/Web/Apps/App_permissions var request = new window.XMLHttpRequest({mozSystem: true}); request.open('HEAD', 'https://www.mozilla.org/robots.txt', true); request.timeout = 5750; request.addEventListener('load', function(event) { console.log('We seem to be online!', event); online = true; }); var offlineAlert = function(event) { console.log('We are likely offline:', event); } request.addEventListener('error', offlineAlert); request.addEventListener('timeout', offlineAlert); request.send(null);
When your app is first loaded/installed, it should store its assets and data offline, as indicated below. On subsequent loads, it should take a progressive enhancement approach — assume the app is offline and work with the dataset it has available in offline storage. If online, it should update the assets and data as available.
Storing assets offline
If you are distributing your app as a packaged app on the app store (for example a Firefox OS app, distributed via the Firefox Marketplace), then you get this part for free — once the app is installed on the device, the assets are saved locally (see Packaged apps).
If your app is hosted and you want it to be available as an open web app as well as installed on Firefox OS, your options are currently somewhat limited. You could use Application Cache.
The AppCache manifest will typically list of all the CSS, fonts, JavaScript, and image files you use. You can use tools to generate your manifest or create one by hand. This is an example of the format, from Face Value’s manifest:
CACHE MANIFEST # v 0.1 / 12 CACHE: css/app.css img/coins/[email protected] js/main.js js/lib/require.js NETWORK: * https://* https://*
Note: If you're using JavaScript templates like mustache, be sure to include your templates in your AppCache manifest or precompile them into your built JavaScript files.
Important: In your AppCache manifest you cannot specify any resources that are on a different domain (even redirects). Chrome lets you do this as long as the resources are served over SSL, but this contravenes the spec, and other browsers don't. Bear this in mind if, for example, you are serving content from a CDN with a different origin.
AppCache problems, and the future
However, for apps of any significant size and complexity, AppCache is a problematic solution and we would actually go so far as to recommend you don't use it. If you really want assets available offline, a better solution may be to grab your assets via XMLHttpRequest and then store them inside an offline datastore such as IndexedDB (see below) .
In the near future, Service Workers will be ready for use, and present a much better solution to the problem of offline asset storage and use.
Storing data offline
To get up and running quickly with offline data we'd recommend that you use localForage, a handy library that abstracts away the differences in cross browser support for offline data technologies. It provides asynchronous data storage via IndexedDB for browsers that support it; it then falls back to WebSQL for browsers that support that (such as Safari), and localStorage for browsers with even more basic support. (see more detailed support info).
localForage uses simple, localStorage-like syntax for getting and setting data items:
localforage.getItem('key', function(value) { alert('My value is: ' + value); }); } localforage.setItem('key', 'value', doSomethingElse);
Note: For more information, read how to use localForage.
Note: The Device Storage API is an option available on Firefox OS for storing large data objects, but IndexedDB (localForage) should also be ok. The Podcasts reference app uses IndexedDB to store episodes in its episode model code.
Note: Another good option for offline data storage is dexie.js, a wrapper for IndexedDB that allows fast code development via a nice, simple syntax.
Saving state
During app usage, you should ensure that its state is saved periodically to the offline data store, and save state locally when user closes the app (though IndexedDB does not allow this).
Syncing with server side
Your app should also save its data back to the server periodically. If your app is offline you should consider building a queueing solution that can handle a lack of connectivity, aiming to save the updates once the network is available again. We have developed Mozilla Kinto a self-hostable service to let you do that.
Third party code that may help:
Examples
- To-do Notifications (view example live): The reference application for the examples in the IndexedDB reference.
Tutorials
If you want more information on how localForage, etc. works behind the scenes, these tutorials should help.