This content originally appeared on Modern Web Development with Chrome and was authored by Paul Kinlan
<p>Years ago, I did some research into how native applications responded to a lack of network connectivity. Whilst I've lost the link to the analysis (I could swear it was on Google+), the overarching narrative was that many native applications are inextricably tied to the internet that they just straight up refuse to function. Sounds like a lot of web apps, the thing that set them apart from the web though is that the experience was still 'on-brand', Bart Simpson would tell you that you need to be online (for example), and yet for the vast majority of web experiences you get a 'Dino' (see chrome://dino).</p>
<p>We've been working on Service Worker for a long time now, and whilst we are seeing more and more sites have pages controlled by a Service Worker, the vast majority of sites don't even have a basic fallback experience when the network is not available.</p>
<p>I asked my good chum Jake if we have any guindance on how to build a generic fall-back page on the assumption that you don't want to create an entirely offline-first experience, and within 10 minutes he had created it. <a href="https://glitch.com/edit/#!/static-misc?path=sw-fallback-page/sw.js:6:9">Check it out</a>.</p>
<p>For brevity, I have pasted the code in below because it is only about 20 lines long. It caches the offline assets, and then for every fetch that is a 'navigation' fetch it will see if it errors (because of the network) and then render the offline page in place of the original content.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-JavaScript" data-lang="JavaScript"><span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">'install'</span>, (<span style="color:#a6e22e">event</span>) => {
<span style="color:#a6e22e">event</span>.<span style="color:#a6e22e">waitUntil</span>(<span style="color:#a6e22e">async</span> <span style="color:#66d9ef">function</span>() {
<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cache</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">await</span> <span style="color:#a6e22e">caches</span>.<span style="color:#a6e22e">open</span>(<span style="color:#e6db74">'static-v1'</span>);
<span style="color:#a6e22e">await</span> <span style="color:#a6e22e">cache</span>.<span style="color:#a6e22e">addAll</span>([<span style="color:#e6db74">'offline.html'</span>, <span style="color:#e6db74">'styles.css'</span>]);
}());
});
<span style="color:#75715e">// See https://developers.google.com/web/updates/2017/02/navigation-preload#activating_navigation_preload
</span><span style="color:#75715e"></span><span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">'activate'</span>, <span style="color:#a6e22e">event</span> => {
<span style="color:#a6e22e">event</span>.<span style="color:#a6e22e">waitUntil</span>(<span style="color:#a6e22e">async</span> <span style="color:#66d9ef">function</span>() {
<span style="color:#75715e">// Feature-detect
</span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">self</span>.<span style="color:#a6e22e">registration</span>.<span style="color:#a6e22e">navigationPreload</span>) {
<span style="color:#75715e">// Enable navigation preloads!
</span><span style="color:#75715e"></span> <span style="color:#a6e22e">await</span> <span style="color:#a6e22e">self</span>.<span style="color:#a6e22e">registration</span>.<span style="color:#a6e22e">navigationPreload</span>.<span style="color:#a6e22e">enable</span>();
}
}());
});
<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">'fetch'</span>, (<span style="color:#a6e22e">event</span>) => {
<span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">request</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">event</span>;
<span style="color:#75715e">// Always bypass for range requests, due to browser bugs
</span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">headers</span>.<span style="color:#a6e22e">has</span>(<span style="color:#e6db74">'range'</span>)) <span style="color:#66d9ef">return</span>;
<span style="color:#a6e22e">event</span>.<span style="color:#a6e22e">respondWith</span>(<span style="color:#a6e22e">async</span> <span style="color:#66d9ef">function</span>() {
<span style="color:#75715e">// Try to get from the cache:
</span><span style="color:#75715e"></span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cachedResponse</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">await</span> <span style="color:#a6e22e">caches</span>.<span style="color:#a6e22e">match</span>(<span style="color:#a6e22e">request</span>);
<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">cachedResponse</span>) <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">cachedResponse</span>;
<span style="color:#66d9ef">try</span> {
<span style="color:#75715e">// See https://developers.google.com/web/updates/2017/02/navigation-preload#using_the_preloaded_response
</span><span style="color:#75715e"></span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">response</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">await</span> <span style="color:#a6e22e">event</span>.<span style="color:#a6e22e">preloadResponse</span>;
<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">response</span>) <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">response</span>;
<span style="color:#75715e">// Otherwise, get from the network
</span><span style="color:#75715e"></span> <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">request</span>);
} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">err</span>) {
<span style="color:#75715e">// If this was a navigation, show the offline page:
</span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">mode</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">'navigate'</span>) {
<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">caches</span>.<span style="color:#a6e22e">match</span>(<span style="color:#e6db74">'offline.html'</span>);
}
<span style="color:#75715e">// Otherwise throw
</span><span style="color:#75715e"></span> <span style="color:#66d9ef">throw</span> <span style="color:#a6e22e">err</span>;
}
}());
});
</code></pre></div><p>That is all. When the user is online they will see the default experience.</p>
<figure><img src="https://paul.kinlan.me/images/2019-04-05-offline-fallback-page-with-service-worker-0.jpeg"></figure>
<p>And when the user is offline, they will get the fallback page.</p>
<figure><img src="https://paul.kinlan.me/images/2019-04-05-offline-fallback-page-with-service-worker-1.jpeg"></figure>
<p>I find this simple script incredibly powerful, and yes, whilst it can still be improved, I do believe that even just a simple change in the way that we speak to our users when there is an issue with the network has the ability to fundamentally improve the perception of the web for users all across the globe.</p>
<p><strong>Update</strong> Jeffrey Posnick kinldy reminded me about using Navigation Preload to not have to wait on SW boot for all requests, this is especially important if you are only controlling <em>failed</em> network requests.</p>
This content originally appeared on Modern Web Development with Chrome and was authored by Paul Kinlan