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

Building an Offline Page for theguardian.com — Front-Trends, May 2016

Building an Offline Page for theguardian.com — Front-Trends, May 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. In this talk Olly will demonstrate how he built an offline page for http://theguardian.com, and discuss potential future use cases.

There have been lots of talks about service workers in recent times, but rarely have people used them in production and lived to tell the tale. This talk aims to inspire people to start using them in production, and to demonstrate the various caveats they might come across.

Oliver Joseph Ash

May 20, 2016
Tweet

More Decks by Oliver Joseph Ash

Other Decks in Technology

Transcript

  1. Building an
    Offline Page for
    theguardian.com
    Oliver Joseph Ash – Front-Trends, May 2016

    @OliverJAsh

    View Slide

  2. @OliverJAsh

    View Slide

  3. View Slide

  4. View Slide

  5. web vs native

    View Slide

  6. • 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

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

    View Slide

  8. View Slide

  9. We can do better!

    View Slide

  10. /developer-blog

    View Slide

  11. How it works

    View Slide

  12. Prototype built in < 1 day

    View Slide

  13. 1

    View Slide

  14. . Create and register the service worker
    1

    View Slide

  15. What is a service worker?

    View Slide

  16. View Slide

  17. 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
    • alarms (e.g. reminders)
    • geofencing
    • A progressive enhancement
    • Trusted origins only (HTTPS, localhost)
    • Chrome, Opera, and Firefox stable

    View Slide

  18. www.theguardian.com
    https://

    View Slide

  19. https://…/info
    …/science
    …/technology
    …/business

    View Slide

  20. <br/>if (navigator.serviceWorker) {<br/>navigator.serviceWorker.register('/service-worker.js');<br/>}<br/>

    View Slide

  21. 1
    2

    View Slide

  22. View Slide

  23. View Slide

  24. 2

    View Slide

  25. . Prime the cache
    2

    View Slide

  26. 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());
    });
    • install event: get ready!
    • Cache the assets needed later
    • Version the cache

    View Slide

  27. 1
    2

    View Slide

  28. 3

    View Slide

  29. . Handle requests with fetch
    3

    View Slide

  30. 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

  31. View Slide

  32. View Slide

  33. • 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

  34. Mutable (HTML)
    Immutable (assets: CSS, JS)

    View Slide

  35. HTML

    View Slide

  36. Network first, then cache

    View Slide

  37. 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

  38. 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

  39. View Slide

  40. Assets

    View Slide

  41. View Slide

  42. 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

  43. View Slide

  44. Cache first, then network

    View Slide

  45. 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) {
    return response || fetch(request);
    })
    );
    }
    });

    View Slide

  46. 4

    View Slide

  47. . Updating the crossword
    4

    View Slide

  48. 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

  49. View Slide

  50. 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

  51. Offline-first
    • Render as much as possible without the network
    • Instantly respond to navigations with a “shell”
    • Improves the experience for users with poor
    connections
    • No more white screen of death
    • Show stale content whilst fetching new content

    View Slide

  52. View Slide

  53. View Slide

  54. View Slide

  55. View Slide

  56. Problems and caveats
    • Browser bugs in both Chrome and Firefox
    • Interleaving of versions in CDN cache

    View Slide

  57. service-worker.js
    v1.css
    offline-page.html
    caches
    caches
    depends on

    View Slide

  58. 0 60 120
    Seconds
    /v1.css
    TTL: 1 year
    /offline-page.html
    TTL: 60 seconds
    /service-worker.js
    TTL: 60 seconds
    v1 v2
    v1 v2

    View Slide

  59. 0 60 120
    Seconds
    /v1.css
    TTL: 1 year
    /v2.css
    TTL: 1 year
    /offline-page.html
    TTL: 60 seconds
    /service-worker.js
    TTL: 60 seconds
    v1 v2
    v1 v2
    Deploy

    View Slide

  60. service-worker.js
    v2.css
    offline-page.html
    v2
    v1 v2
    v1.css v1
    caches
    caches
    depends on

    View Slide

  61. // /offline-page.json
    {
    "html": "",
    "assets": ["/v1.css"]
    }
    Solution: cache manifest

    View Slide

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

    View Slide

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

    View Slide

  64. Conclusion
    • Service workers allow us to progressively enhance the
    experience for:
    • offline users
    • users with poor connections
    • It’s easy to build an offline page
    • A simple offline page is a good place to start

    View Slide

  65. 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

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

    View Slide

  67. View Slide