$30 off During Our Annual Pro Sale. View Details »

Building an Offline Page for theguardian.com — London Web Perf, March 2016

Building an Offline Page for theguardian.com — London Web Perf, March 2016

You’re on a train to work and you open up the Guardian app on your phone. A tunnel surrounds you, but the app still works in very much the same way as it usually would—despite your lack of internet connection, you still get the full experience, only the content shown will be stale. If you tried the same for the Guardian website, however, it wouldn’t load at all. Native apps have long had the tools to deal with these situations, in order to deliver rich user experiences whatever the user’s situation may be. With service workers, the web is catching up.

Oliver Joseph Ash

March 02, 2016
Tweet

More Decks by Oliver Joseph Ash

Other Decks in Technology

Transcript

  1. Building an
    Offline Page for
    theguardian.com
    Oliver Joseph Ash – London Web Perf, March 2016

    @OliverJAsh

    View Slide

  2. View Slide

  3. View Slide

  4. web vs native

    View Slide

  5. • Content is cached
    • Experience:
    • offline: stale content remains
    • server down: stale content remains
    • poor connection: stale while
    revalidate
    • good connection: stale while
    revalidate
    Native app

    View Slide

  6. Experience:
    • offline: nothing
    • server down: nothing
    • poor connection: white screen of death
    • good connection: new content
    Website

    View Slide

  7. We can do better!

    View Slide

  8. /developer-blog

    View Slide

  9. How it works

    View Slide

  10. What is a service worker?
    • Script that runs in the background
    • Useful for features with no user interaction, e.g.:
    • listen to push events, useful for pushing notifications
    • intercept and handle network requests
    • future:
    • background sync
    • geofencing
    • Trusted origins only (HTTPS, localhost)
    • Chrome and Firefox stable

    View Slide

  11. www.theguardian.com
    https://

    View Slide

  12. <br/>if (navigator.serviceWorker) {<br/>navigator.serviceWorker.register('/service-worker.js');<br/>}<br/>
    1. Create and register the service worker

    View Slide

  13. 1
    2

    View Slide

  14. View Slide

  15. View Slide

  16. var version = 1;
    var staticCacheName = 'static' + version;
    var updateCache = function () {
    return caches.open(staticCacheName)
    .then(function (cache) {
    return cache.addAll([
    '/offline-page',
    '/assets/css/main.css',
    '/assets/js/main.js'
    ]);
    });
    };
    self.addEventListener('install', function (event) {
    event.waitUntil(updateCache());
    });
    2. Get ready with install
    • install event: get ready!
    • Cache the assets needed later
    • Version the cache

    View Slide

  17. 1
    2

    View Slide

  18. 3. Intercept requests with fetch

    View Slide

  19. const staticCacheName = 'static';
    const version = 1;
    const updateCache = () => (
    caches.open(staticCacheName + version)
    .then(cache =>
    cache.addAll([
    '/offline-page.html',
    '/assets/css/main.css',
    '/assets/js/main.js'
    ]);
    );
    );
    self.addEventListener('install', function (event) {
    event.waitUntil(updateCache());
    });
    self.addEventListener('fetch', function (event) {
    event.respondWith(fetch(event.request));
    });
    • Default: just fetch
    • Override default
    • Intercept network requests to:
    • fetch from the network
    • read from the cache
    • construct your own response
    fetch events

    View Slide

  20. View Slide

  21. • e.g., use templating to construct
    a HTML response from JSON
    Service worker: custom responses
    self.addEventListener('fetch', function (event) {
    var responseBody =
    'Hello, World!';
    var responseOptions = {
    headers: {
    'Content-Type': 'text/html'
    }
    };
    var response = new Response(
    responseBody,
    responseOptions
    );
    event.respondWith(response);
    });

    View Slide

  22. HTML

    View Slide

  23. var doesRequestAcceptHtml = function (request) {
    return request.headers.get('Accept')
    .split(',')
    .some(function (type) { return type === ‘text/html’; });
    };
    self.addEventListener('fetch', function (event) {
    var request = event.request;
    if (doesRequestAcceptHtml(request)) {
    event.respondWith(fetch(request));
    }
    });

    View Slide

  24. Network first

    View Slide

  25. self.addEventListener('fetch', function (event) {
    var request = event.request;
    if (doesRequestAcceptHtml(request)) {
    event.respondWith(
    fetch(request).catch(function () {
    return caches.match('/offline-page');
    })
    );
    }
    });

    View Slide

  26. Assets

    View Slide

  27. View Slide

  28. self.addEventListener('fetch', function (event) {
    var request = event.request;
    if (doesRequestAcceptHtml(request)) {
    event.respondWith(
    fetch(request).catch(function () {
    return caches.match('/offline-page');
    })
    );
    } else {
    event.respondWith(
    caches.match(request)
    );
    }
    });

    View Slide

  29. Cache first

    View Slide

  30. self.addEventListener('fetch', function (event) {
    var request = event.request;
    if (doesRequestAcceptHtml(request)) {
    event.respondWith(
    fetch(request).catch(function () {
    return caches.match('/offline-page');
    })
    );
    } else {
    event.respondWith(
    caches.match(request).then(function (response) {
    response || fetch(request);
    })
    );
    }
    });

    View Slide

  31. 4. Updating the crossword

    View Slide

  32. var version = 1;
    var cacheNameSuffix = 'static' + '-' + version;
    var getISODate = function () { return new Date().toISOString().split('T')[0]; };
    var getCacheName = function () { return getISODate() + '-' + cacheNameSuffix; };
    var isLatestCacheName = function (key) { return key === getCacheName(); };
    var deleteOldCaches = function () {
    var getOldCacheKeys = function () {
    return caches.keys().then(function (keys) {
    return keys.filter(function (key) { return !isLatestCacheName(key); });
    });
    };
    return getOldCacheKeys().then(function (oldCacheKeys) {
    return Promise.all(oldCacheKeys.map(function (key) { return caches.delete(key); }));
    });
    };
    var isCacheUpdated = function () {
    return caches.keys().then(function (keys) { return keys.some(isLatestCacheName); });
    };
    isCacheUpdated().then(function (isUpdated) {
    if (!isUpdated) {
    updateCache().then(deleteOldCaches);
    }
    });
    //

    View Slide

  33. View Slide

  34. Future usages of service worker on
    theguardian.com
    • Offline page will increase in significance
    • Offline page can be extended
    • show stale content, like native apps
    • show personalised content, downloaded ahead of time
    • show content that has been “saved for later”
    • Go fully offline-first

    View Slide

  35. Offline-first
    • Instantly respond to navigations with a “shell”
    • Show stale content while revalidating
    • Improves the experience for users with poor
    connections

    View Slide

  36. View Slide

  37. View Slide

  38. Why? Is this valuable?
    • Fun
    • Insignificant usage due to HTTPS/browser support
    • … but good to plant the seed
    • Iron out browser bugs

    View Slide

  39. “If we only use features that work in IE8, we're
    condemning ourselves to live in an IE8 world.”
    — Nolan Lawson

    View Slide

  40. Problems and caveats
    • Browser bugs in both Chrome and Firefox
    • CDN cache

    View Slide

  41. Conclusion
    • Service workers allow us to:
    • define rich experiences for users who are offline
    • improve performance for users with poor connections
    • It’s easy to build an offline page
    • Offline page is a good place to start

    View Slide

  42. Further reading
    • https://www.theguardian.com/info/developer-blog/2015/nov/04/
    building-an-offline-page-for-theguardiancom
    • https://github.com/slightlyoff/ServiceWorker/blob/master/explainer.md
    • https://www.theguardian.com/service-worker.js

    View Slide

  43. Thank you
    Like what we’re doing?
    developers.theguardian.com
    @OliverJAsh

    View Slide