By Oleksii Rudenko April 1, 2015 10:42 PM
Using HTML 5 Application Cache for Single Page Applications

Recently I added the HTML 5 application cache (app cache) to an Ember app. Actually, the app already used the application cache but it didn’t work well. For example, the new releases didn’t always make it to the users and the app broke because the client part of it was stale and the server part was new…

There are many tutorials about the application cache out there: Application Cache is a Douchebag, The Application Cache is no longer a Douchebag, Appcache, not so much a douchebag as a complete pain in the #$%^ etc. I read most of them but still I had some problems while implementing the application cache properly. Therefore, in this post I will describe some of my findings on how to make the application cache actually work [for me].

Let me know if some points of the post are falsy. Although, the testing that I did convinced me that following these points leads to a bug free implementation of the caching for my app [well, I am 99% sure].

About the App

A single page Ember app consisting of static files and user-generated content that would benefit of using the cache due to its size. Gulp is used as a build system. The goal is to cache all of the static resources in the application cache so no HTTP requests to get static content are done unless a new version of the app is published.

The size of the front page (all resources/data - gzipped w/o app cache): ~650 KB. The resulting size of the front page (all resources/data - with enabled app cache, once cached): ~30 KB.

I have not really tried to optimize the page using only HTTP caching.

Some Tips for Working with the Application Cache

Here is what I learned:

  • Never reference the manifest itself in the manifest.
  • Never set cache headers on the manifest file (better set no-cache). Setting no-store on the manifest file will prevent it from being used by Firefox.
  • Setting Cache-Control:no-store on any file referenced in the manifest prevents Firefox from storing that file in the app cache.
  • Initially the browser loads the version from the application cache and then the cache is updated. This means that to refresh the resources the app has to be reloaded.
  • Set no-cache headers on resources defined in the application manifest. If the browser has placed a file in the network cache based on its HTTP cache headers, the file will be taken from the network cache when the application manifest changes and, thus, it will not be updated.
  • Do not reference the app content in the manifest. In our app, we had some templates which could changed by the admin of the app. And it was one of the mistakes to include these templates in the application manifest, because the only way to update resources in the application cache is to change the manifest and it does not normally happen when users change the content.
  • In the network section use * unless your app is made exclusively for offline usage.

Tips for Debugging Application Cache in FF

The FF comes with an application cache validator. To use it, you need to open a page that references an appcache manifest, then open FF console by pressing Shift + F2 and type in:

appcache validate

This would reveal all problems with your application cache manifest.

If the current browser URL is not the one that references the manifest the validator will not find the manifest and will show errors although the manifest may be correct. For example, if www.example.com references the manifest and I open www.example.com/#!/ route the manifest will not be found by the validator.

To clear the app cache in FF:

appcache clear

To see what’s cached:

appcache list

Implementing cache management with Ember

I decided to implement the cache checker as an Ember component. Maybe, it is not the best way to go (!). Anyway, the component needs to be included on every page of an app like this:

  {{cache-updater}}

For me such a place is the footer template. Once inserted the component checks the application cache status and if a new version of the app is available it notifies the user about it and reloads the page (to make sure that the new version is picked up). Additionally, it schedules the application cache re-checks for the running app and if a new version will be published while the app is in use, the app will detect this and update.


import Ember from 'ember';
import log from 'log';

var CacheUpdaterComponent = Ember.Component.extend({
    tagName: 'div',
    isVisible: false, // we don't show this component
    updateFrequency: 1000 * 60 * 3, // 3 minutes

    warnAndReload: function() {
      alert('New version of the app found');
      try {
        window.applicationCache.swapCache(); // make sure new resources are used
        window.location.reload();
      } catch (err) {
        console.log('Swap cache failed', err);
      }
    },

    _setup: function() {
      var ac = window.applicationCache;
      if (ac) {
        if (ac.status === ac.UPDATEREADY) { // if there is a cache update already
          Ember.run.scheduleOnce('afterRender', this, this.warnAndReload);
        }
        // if an update appears later on
        ac.addEventListener('updateready', () => this.warnAndReload());
        setTimeout(() => this.update(), this.get('updateFrequency'));
      }
    }.on('didInsertElement'),

    update: function() {
      var ac = window.applicationCache;
      if (ac) {
        try {
          ac.update(); // check for updates
        } catch (err) {
          console.log('App cache updated failed', err);
        }
        setTimeout(() => this.update(), this.get('updateFrequency'));
      }
    }

  });

Ember.Handlebars.helper('cache-updater', CacheUpdaterComponent);

export default CacheUpdaterComponent;

Generating Manifest With Gulp

I use the following Gulp task to generate the app cache manifest before each deployment:


var manifest = require('gulp-manifest');

gulp.task('manifest', function () {
    return gulp.src(manifestSource)
      .pipe(manifest({
        hash: true,
        preferOnline: true,
        network: ['*'],
        filename: 'app.manifest',
        exclude: 'app.manifest',
        timestamp: true
      }))
      .pipe(gulp.dest('dist'));
  });

It uses the gulp-manifest plugin and it is very similar to the example in the README of the plugin except for the enabled timestamp generation and the network section that define only the *.

I suspect that defining http://* and https://* in the network section is not valid therefore I decided to go with the * only.

That’s it and thanks for reading.