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

PWAs: the Application Shell & the well of surprises

PWAs: the Application Shell & the well of surprises

When it comes to performance, we’ll usually take all the help we can get. The app shell model – an architecture for building PWAs – can make a huge difference… but better keep in mind it may hold a few surprises for you.

Stratos Pavlakis

November 30, 2018
Tweet

More Decks by Stratos Pavlakis

Other Decks in Programming

Transcript

  1. the Application Shell
    …and the well of surprises

    View Slide

  2. Stratos Pavlakis
    Software guy @Blueground
    th3hunt

    View Slide

  3. the Application Shell
    Why?

    View Slide

  4. Web Performance
    a short primer

    View Slide

  5. domContentLoaded
    navigationStart
    loadEventEnd
    time to first paint
    time to first paint
    time to interactive
    time to first byte
    frames per second
    page load time
    speed Index
    time to first ad
    first contentful paint
    first meaningful paint
    hero elements
    domainLookupStart

    View Slide

  6. critical rendering path

    View Slide

  7. Network
    Rendering

    View Slide

  8. Request
    Page
    GET

    /page
    Network
    Rendering

    View Slide

  9. Request
    Page
    Start building
    DOM
    wait
    GET

    /page
    Network
    Rendering
    HMTL

    View Slide

  10. Request
    Page
    Start building
    DOM
    wait
    GET

    /page
    Network
    Rendering
    GET

    /style
    GET

    /js
    HMTL

    View Slide

  11. Request
    Page
    Start building
    DOM
    Build
    CSSOM
    wait block
    GET

    /page
    Network
    Rendering
    GET

    /style
    GET

    /js
    CSS
    JS
    HMTL

    View Slide

  12. Request
    Page
    Start building
    DOM
    Build
    CSSOM
    Run JS
    wait Continue
    DOM
    block
    GET

    /page
    Network
    Rendering
    GET

    /style
    GET

    /js
    CSS
    JS
    HMTL

    View Slide

  13. Request
    Page
    Start building
    DOM
    Build
    CSSOM
    Run JS
    wait Continue
    DOM
    block style / layout / paint /
    comp
    GET

    /page
    Network
    Rendering
    GET

    /style
    GET

    /js
    CSS
    JS
    HMTL

    View Slide

  14. Request
    Page
    Start building
    DOM
    Build
    CSSOM
    Run JS
    wait Continue
    DOM
    block styl
    GET

    /page
    Network
    Rendering
    GET

    /style
    GET

    /js
    CSS
    JS
    HMTL

    View Slide

  15. Network
    Rendering

    View Slide

  16. Network
    Rendering
    CPU / GPU

    View Slide

  17. Network
    Rendering
    CPU / GPU Speed of Light

    View Slide

  18. speed of light places a hard limit

    on the network latency!

    View Slide

  19. imagine…

    View Slide

  20. a single byte
    1 1 0 1 1 0 1 0

    View Slide

  21. in New York

    View Slide

  22. distance light in vacuum light in fiber RTT
    London 5,585km 19ms 28ms 56ms
    Sydney 15,993km 53ms 80ms 160ms
    Thess 7,695km 26ms 38ms 76ms
    - Browser Networking, Ilya Grigorik
    Getting a byte from New York

    View Slide

  23. 56 ms ?

    View Slide

  24. you wish…

    View Slide

  25. Client (London) Server (New York)
    TCP
    TLS

    View Slide

  26. Client (London) Server (New York)
    0 ms
    SYN
    28 ms
    TCP
    TLS

    View Slide

  27. Client (London) Server (New York)
    0 ms
    SYN
    28 ms SYN ACK
    ACK 56 ms
    TCP
    TLS

    View Slide

  28. Client (London) Server (New York)
    0 ms
    SYN
    28 ms SYN ACK
    ACK 56 ms
    140 ms ChangeCipherSpec

    Finished
    ClientHello 84 ms
    ClientKeyExchange

    ChangeCipherSpec 

    Finished

    ServerHello Cert

    ServerHelloDone

    112 ms
    168 ms
    TCP
    TLS

    View Slide

  29. Client (London) Server (New York)
    0 ms
    SYN
    28 ms SYN ACK
    ACK 56 ms
    Application
    196 ms
    140 ms ChangeCipherSpec

    Finished
    ClientHello 84 ms
    ClientKeyExchange

    ChangeCipherSpec 

    Finished

    ServerHello Cert

    ServerHelloDone

    112 ms
    168 ms
    TCP
    TLS

    View Slide

  30. Client (London) Server (New York)
    0 ms
    SYN
    28 ms SYN ACK
    ACK 56 ms
    Application
    196 ms
    140 ms ChangeCipherSpec

    Finished
    ClientHello 84 ms
    ClientKeyExchange

    ChangeCipherSpec 

    Finished

    ServerHello Cert

    ServerHelloDone

    112 ms
    168 ms
    Application
    224 ms
    TCP
    TLS

    View Slide

  31. Client (London) Server (New York)
    0 ms
    SYN
    28 ms SYN ACK
    ACK 56 ms
    Application
    196 ms
    140 ms ChangeCipherSpec

    Finished
    ClientHello 84 ms
    ClientKeyExchange

    ChangeCipherSpec 

    Finished

    ServerHello Cert

    ServerHelloDone

    112 ms
    168 ms
    Application
    224 ms
    11011010
    TCP
    TLS

    View Slide

  32. 224 ms

    View Slide

  33. What if I send
    1MB in 224ms?

    View Slide

  34. well…

    View Slide

  35. meet TCP Slow Start

    View Slide

  36. Client (London) Server (New York)
    TCP Slow Start

    View Slide

  37. Client (London) Server (New York)
    TCP Slow Start

    View Slide

  38. Client (London) Server (New York)
    TCP Slow Start

    View Slide

  39. Client (London) Server (New York)
    TCP Slow Start

    View Slide

  40. Client (London) Server (New York)
    TCP Slow Start

    View Slide

  41. 224ms + 2*56ms
    3 * RTT for 64KB with init_cwnd = 10

    View Slide

  42. “Even for small payloads
    network places a hard limit on speed”

    View Slide

  43. Ok ok…
    but how bad can
    a few 100s of
    milliseconds be?

    View Slide

  44. Delay User Perception
    0 - 100ms Instant
    100 - 300ms Perceptible delay
    300 - 1000ms Machine is working
    1000ms+ Likely mental context switch
    time and user perception
    Browser Networking - Ilya Grigorik

    View Slide

  45. So… how do we
    deal with
    Mr. Network?

    View Slide

  46. dealing with
    network latency

    View Slide

  47. 1

    View Slide

  48. Edge Caching
    1

    View Slide

  49. 1 Edge Caching

    View Slide

  50. 1 Edge Caching

    View Slide

  51. Html
    Css Js
    Img
    Html
    Css Js
    Img
    Html
    Css Js
    Img
    Html
    Css Js
    Img
    Html
    Css Js
    Img
    1 Edge Caching
    Html
    Css Js
    Img

    View Slide

  52. Html
    Css Js
    Img
    Html
    Css Js
    Img
    Html
    Css Js
    Img
    Html
    Css Js
    Img
    Html
    Css Js
    Img
    1 Edge Caching
    Html
    Css Js
    Img

    View Slide

  53. Html
    Css Js
    Img
    Html
    Css Js
    Img
    Html
    Css Js
    Img
    Html
    Css Js
    Img
    Html
    Css Js
    Img
    1 Edge Caching
    Html
    Css Js
    Img
    Static / JAMStack

    View Slide

  54. 2

    View Slide

  55. HTTP2 Push
    2

    View Slide

  56. HTTP2 Push
    2

    View Slide

  57. HTTP2 Push
    2
    GET /index.html

    View Slide

  58. HTTP2 Push
    2
    GET /index.html
    styles.css + app.js
    index.html

    View Slide

  59. 3

    View Slide

  60. Service Workers
    3

    View Slide

  61. 3 Service Workers

    View Slide

  62. 3 Service Workers

    View Slide

  63. 3 Service Workers
    •Proxies of our own

    View Slide

  64. 3 Service Workers
    •Proxies of our own
    •Live in the browser

    View Slide

  65. 3 Service Workers
    •Proxies of our own
    •Live in the browser
    •Domain & path scoped

    View Slide

  66. 3 Service Workers
    •Proxies of our own
    •Live in the browser
    •Domain & path scoped
    •Async by spec

    View Slide

  67. 3 Service Workers
    •Proxies of our own
    •Live in the browser
    •Domain & path scoped
    •Async by spec
    •Utilize Cache API

    View Slide

  68. 3 Service Workers
    •Proxies of our own
    •Live in the browser
    •Domain & path scoped
    •Async by spec
    •Utilize Cache API
    •Run on separate thread

    View Slide

  69. the app shell

    View Slide

  70. server browser

    View Slide

  71. server browser
    cache

    View Slide

  72. server browser
    GET /index.html
    cache

    View Slide

  73. server browser
    index.html
    GET /index.html
    cache

    View Slide

  74. service worker
    server browser
    cache

    View Slide

  75. service worker
    server browser
    GET /index.html
    cache

    View Slide

  76. service worker
    server browser
    GET /index.html
    index.html
    cache

    View Slide

  77. service worker
    server browser
    GET /index.html
    index.html
    index.html
    cache

    View Slide

  78. service worker
    server browser
    cache

    View Slide

  79. service worker
    server browser
    GET /app.js, /style.css
    app.js, style.css
    cache

    View Slide

  80. service worker
    server browser
    GET /app.js, /style.css
    app.js, style.css
    app.js
    style.js
    cache

    View Slide

  81. service worker
    cache
    server browser

    View Slide

  82. service worker
    cache
    server browser
    GET /index.html
    /style.css
    /app.js
    /logo.png
    /background.jpg

    View Slide

  83. service worker
    cache
    server browser
    XHR XHR

    View Slide

  84. Lifecycle

    View Slide

  85. Lifecycle

    View Slide

  86. Lifecycle

    View Slide

  87. Lifecycle

    View Slide

  88. Lifecycle
    pre-cache the shell
    (html, js, css, fonts)

    View Slide

  89. Lifecycle
    pre-cache the shell
    (html, js, css, fonts)
    Cache runtime assets
    as they come (e.g. posted images)

    View Slide

  90. View Slide

  91. Robert, a software engineer

    View Slide

  92. Request
    Page
    Start building
    DOM
    Build
    CSSOM
    Run JS
    wait Continue
    DOM
    block style / layout / paint /
    comp
    GET

    /page
    GET

    /style
    GET

    /js
    css
    js
    html
    with a critical path to improve…

    View Slide

  93. Improve the rendering
    Process?
    (Refactor code)

    View Slide

  94. Improve the rendering
    Process?
    (Refactor code)
    That would involve…

    View Slide

  95. Improve the rendering
    Process?
    (Refactor code)
    Designers
    That would involve…

    View Slide

  96. Improve the rendering
    Process?
    (Refactor code)
    Developers
    Designers
    That would involve…

    View Slide

  97. Improve the rendering
    Process?
    (Refactor code)
    Developers
    Designers
    That would involve…
    Product Owners

    View Slide

  98. Better jump off
    a cliff

    View Slide

  99. What about the
    network?

    View Slide

  100. Edge Caching
    maybe?

    View Slide

  101. Edge Caching
    maybe?
    DevOps
    Developers
    Test Engineers
    That would involve…
    Product Owners

    View Slide

  102. HTTP2 Push?

    View Slide

  103. HTTP2 Push?
    DevOps
    Developers
    Test Engineers
    That would involve…

    View Slide

  104. What can service
    workers do?

    View Slide

  105. What can service
    workers do?
    Cache stuff

    View Slide

  106. What can service
    workers do?
    Cache stuff
    Even… cache
    the HTML
    document

    View Slide

  107. What can service
    workers do?
    a few front end Devs
    That would only involve…
    Cache stuff
    Even… cache
    the HTML
    document

    View Slide

  108. What can service
    workers do?
    a few front end Devs
    That would only involve…
    It would be just cross cut change
    that would eliminate the network
    Cache stuff
    Even… cache
    the HTML
    document

    View Slide

  109. What can service
    workers do?
    a few front end Devs
    That would only involve…
    It would be just cross cut change
    that would eliminate the network
    once and for all !!
    Cache stuff
    Even… cache
    the HTML
    document

    View Slide

  110. Perfecto…

    View Slide

  111. let’s do it

    View Slide

  112. the plan
    • Create a proof of concept
    • Deploy on staging
    • Work on the details while feedback is coming
    • Tweak a few things
    • Go Live!

    View Slide

  113. the mentality
    • Go fast
    • Perform minimum changes
    • Involve as few as possible

    View Slide

  114. proof of concept

    View Slide


  115. View Slide

  116. SSR

    SSR
    <br/>Tag Manager<br/>/index.html<br/>PoC<br/>

    View Slide

  117. SSR

    SSR
    <br/>Tag Manager<br/>/index.html<br/>PoC<br/>

    View Slide

  118. Tag Manager
    /index.html
    PoC

    View Slide

  119. /index.html
    PoC

    View Slide

  120. /index.html
    but that is slower
    for older browsers
    that don’t support SWs
    PoC

    View Slide

  121. /index.html
    PoC

    View Slide

  122. /index.html
    Browser: /index.html
    Worker: /index-shell.html
    PoC

    View Slide

  123. more Developers involved

    View Slide

  124. sw.js
    var cacheFiles = [
    “./index-shell.html”,
    "./js/app.js",
    "./css/style.css"
    ];
    self.addEventListener("install", e "=> {
    e.waitUntil(
    caches.open(cacheName).then(cache "=> {
    return cache.addAll(cacheFiles);
    })
    );
    });

    View Slide

  125. "./js/app.js",
    "./css/style.css"
    ];
    self.addEventListener("install", e "=> {
    e.waitUntil(
    caches.open(cacheName).then(cache "=> {
    return cache.addAll(cacheFiles);
    })
    );
    });
    self.addEventListener("fetch", function(e) {
    if (e.request.url.match(/\/index.html/)) {
    e.respondWith(caches.match('/index-shell.html'));
    }
    });

    View Slide

  126. navigator.serviceWorker
    .register(“/sw.js”, { scope: "./" })
    .then(() "=> {
    console.log(“[ServiceWorker] Registered");
    })
    .catch(err "=> {
    console.log(“[ServiceWorker] Failed to Register", err);
    });
    index.html

    View Slide

  127. to staging!

    View Slide

  128. View Slide

  129. …from staging

    View Slide

  130. Surprise #1 /index.html
    Cannot submit any form

    View Slide

  131. Surprise #1 /index.html

    View Slide

  132. Surprise #1 /index.html
    name=“csrf-token”
    content=“…” />

    View Slide

  133. Surprise #1 /index.html
    Keep CSRF token in a

    View Slide

  134. all Developers involved

    View Slide

  135. to staging!

    View Slide

  136. View Slide

  137. …from staging

    View Slide

  138. Surprise #2 /index.html
    CSS is not loading

    View Slide

  139. Surprise #2 /index.html

    View Slide

  140. Surprise #2 /index.html
    meet Opaque responses

    View Slide

  141. Surprise #2 /index.html
    meet Opaque responses

    View Slide

  142. Surprise #2 /index.html
    meet Opaque responses
    • Responses to requests made to a remote origin 

    when CORS is not enabled

    View Slide

  143. Surprise #2 /index.html
    meet Opaque responses
    • Responses to requests made to a remote origin 

    when CORS is not enabled
    • status == 0

    View Slide

  144. Surprise #2 /index.html
    meet Opaque responses
    • Responses to requests made to a remote origin 

    when CORS is not enabled
    • status == 0
    • no access to headers

    View Slide

  145. Surprise #2 /index.html
    meet Opaque responses
    • Responses to requests made to a remote origin 

    when CORS is not enabled
    • status == 0
    • no access to headers
    • no access to body

    View Slide

  146. Surprise #2 /index.html
    meet Opaque responses
    • Responses to requests made to a remote origin 

    when CORS is not enabled
    • status == 0
    • no access to headers
    • no access to body
    • Using a CDN? => your assets on a remote origin

    View Slide

  147. Surprise #2 /index.html
    meet Opaque responses
    const request = new Request(
    'https:"//no-cors-cdn.com/style.css',
    { mode: 'no-cors' }
    );
    fetch(request).then(response "=>
    cache.put(request, response)
    );

    View Slide

  148. Surprise #2 /index.html
    meet Opaque responses
    Configure CORS for all your cached assets

    Your JS should already be so for error monitoring

    View Slide

  149. DevOps involved

    View Slide

  150. to staging!

    View Slide

  151. no news,

    maybe it’s time for those details…

    View Slide

  152. Detail #1 /index.html
    App has been updated. Click here to reload
    •When?
    •Won’t it become annoying?
    •Backwards compatibility?

    View Slide

  153. That is a BIG FAT
    detail, right there!

    View Slide

  154. Detail #1 /index.html
    App has been updated. Click here to reload
    Treat your web app as a mobile app

    View Slide

  155. Detail #1 /index.html
    App has been updated. Click here to reload
    •Set ShellVersion in index.html
    •Add ShellVersion HTTP Header 

    to all XHR requests
    •Bump it on every release/deploy

    based on watched files

    View Slide

  156. Detail #1 /index.html
    App has been updated. Click here to reload
    •Set minimum supported version
    •Return client error HTTP status 

    (e.g. 430) if below minimum

    View Slide

  157. Detail #1 /index.html
    App has been updated. Click here to reload
    Show update prompt up to 2 times/
    day for + on 430s.

    View Slide

  158. PMs definitely involved

    View Slide

  159. View Slide

  160. …from staging

    View Slide

  161. Surprise #3 /index.html
    Safari not working - at all

    View Slide

  162. Detail #2 /index.html
    How to unregister a buggy 

    service worker?

    View Slide

  163. Detail #2 /index.html
    Be prepared!
    No deployment should be required

    View Slide

  164. Detail #2 /index.html
    no big deal bug?
    ENV.SW_TYPE=“NOOP”
    self.addEventListener('install', function () {
    self.skipWaiting();
    console.log('NOOP service worker installed');
    });
    self.addEventListener('activate', function (event) {
    event.waitUntil(clients.claim());
    console.log('NOOP service worker activated');
    });

    View Slide

  165. Detail #2 /index.html
    HUGE deal bug?
    ENV.SW_TYPE=“DESTROYER”
    self.addEventListener('install', function (e) {
    self.skipWaiting();
    });
    self.addEventListener('activate', function (e) {
    self.registration.unregister()
    .then(function () {
    return self.clients.matchAll();
    })
    .then(function (clients) {
    clients.forEach(client "=>
    client.navigate(client.url));
    });

    View Slide

  166. Detail #2 /index.html
    /purge-caches
    •Caches
    •Storage
    •IDB
    •Service Workers
    Clear-Site HTTP Header
    (W3C working draft)

    View Slide

  167. DevOps involved. Again.

    View Slide

  168. to staging!

    View Slide

  169. View Slide

  170. …from staging

    View Slide

  171. Surprise #3 /index.html
    it’s not thaaaat fast…

    View Slide

  172. What did you
    just say to me!??

    View Slide

  173. Surprise #3 /index.html
    We removed SSR.
    To go beyond user perceived performance
    and ACTUALLY load faster,
    you need to approach the idea of…
    offline first

    View Slide

  174. Surprise #3 /index.html
    Offline first
    (simple case)
    Serve cached content from IDB or
    localStorage, while fetching fresh
    data from the server.

    View Slide

  175. authenticated users ==
    user specific content

    View Slide

  176. Surprise #3 /index.html
    1.Sign in as Bob
    2.Sign out
    3.Sign in as Alice
    4.What does Alice see?
    caching the user specific content,
    poses this problem

    View Slide

  177. Surprise #3 /index.html
    clear all caches on /signout?

    View Slide

  178. Surprise #3 /index.html
    clear all caches on /signout?
    won’t work :(

    View Slide

  179. Surprise #3 /index.html
    clear all caches on /signin?

    View Slide

  180. Surprise #3 /index.html
    clear all caches on /signin?
    won’t work :(

    View Slide

  181. need to create a
    personal service worker

    View Slide

  182. Surprise #3 /index.html
    /app_shell
    /index.html?uid=4w321r
    /signin

    View Slide

  183. Surprise #3 /index.html
    /app_shell
    /index.html?uid=4w321r
    /signin
    match with UID

    View Slide

  184. Surprise #3 /index.html
    /app_shell
    /index.html?uid=4w321r
    /signin
    + Scope IDB, Storage keys under this UID
    e.g. “/uid/42321/avatarUrl”
    match with UID

    View Slide

  185. self.addEventListener("fetch", function(e) {
    if (e.request.url.match(/\/index.html?uid=4w221r/)) {
    e.respondWith(caches.match('/index-shell.html'));
    }
    });

    View Slide

  186. View Slide

  187. Offline first… would involve EVERYONE

    View Slide

  188. epilogue

    View Slide

  189. Robert case takeaways

    View Slide

  190. PWAs should be treated as…
    Apps

    View Slide

  191. PWAs will remind you…

    View Slide

  192. Cache invalidation is a
    hard thing :P

    View Slide

  193. Great performance is

    a team thing!

    View Slide

  194. Thank you!
    Questions?
    Stratos Pavlakis
    Software guy @Blueground
    th3hunt

    View Slide