Service Worker Routing β€” Part 2: The Three-Strategy Router πŸ—ΊοΈ

March 5, 20264 min read
service workerPWAofflinecachingweb workersprogressive web appsbackground sync

Service Worker Routing β€” Part 2: Auth-Aware Routing πŸ”

My notes from Kyle Simpson's "Exploring Service Workers" β€” Section 6, Lectures 6–9

Covers: Three-strategy router, auth-aware routing, login/logout handling, full demo


Lecture 6: Logged Out Routing Walkthrough πŸ—ΊοΈ

This is the most important lecture in the section. Kyle pulls in a complete evolved router and walks through every decision. Three distinct resource types, three distinct strategies.


Resource Type 1: API Calls

if (reqURL.startsWith('/api')) { if (isOnline) { let fetchOptions = { method: request.method, headers: request.headers, credentials: 'same-origin', // API calls need session cookies cache: 'no-store' }; let res = await fetch(request.url, fetchOptions); if (res.ok) { if (request.method === 'GET') { // never cache POST responses await cache.put(reqURL, res.clone()); } return res; } } // Fallback: try cache let res = await cache.match(reqURL); if (res) return res.clone(); // Both failed β€” synthetic 404 return notFoundResponse(); }

Key decisions:

  1. credentials: 'same-origin' β€” unlike logged-out static file caching, API calls need session cookies. At least one endpoint (/api/add-post) requires authentication.
  2. Only cache GET responses β€” POST responses should never be cached. The one GET API call fetches the list of blog post IDs for the homepage β€” worth caching for offline.
  3. Why cache API responses at all? The homepage won't show any posts if the API call fails. An old cached list is better than nothing.
  4. Synthetic notFoundResponse() β€” fabricate a 404 so the page knows the AJAX failed cleanly:
function notFoundResponse() { return new Response('', { status: 404, statusText: 'Not Found' }); }

You can fabricate any response β€” status, headers, even a body. You're programming a proxy, you have complete control.


Resource Type 2: HTML Pages (Navigation Requests)

Detected via the Accept header β€” if a request includes text/html, it's a page navigation:

if (request.headers.get('Accept').includes('text/html')) { // TODO: handle /login, /logout, /add-post separately (auth-sensitive) if (isOnline) { let res = await fetch(request.url, { method: request.method, headers: request.headers // no credentials needed for public pages }); if (res.ok) { // Don't cache if server flagged it as a friendly 404 if (!res.headers.get('X-Not-Found')) { await cache.put(reqURL, res.clone()); } return res; } } // Fallback: try cache let res = await cache.match(reqURL); if (res) return res.clone(); // Both failed β€” serve the offline page return cache.match('/offline'); }

Key decisions:

  1. Always try network first for HTML β€” page content changes. A typo fix on /about shouldn't require bumping the SW version. Unlike CSS and JS where atomic updates make sense, HTML gets fresh-served whenever possible.
  2. Detect HTML via Accept header β€” more flexible than hard-coding a list of page URLs. Any request with text/html in Accept is a page navigation.
  3. X-Not-Found custom header β€” the server returns a friendly 404 page with a 200 status (so the browser renders it nicely). But the SW must not cache it under the requested URL β€” that would pollute the cache with 404 content. The server appends X-Not-Found: true so the SW knows to show it but skip caching it.
  4. Offline page as final fallback β€” if both network and cache fail, serve the pre-cached /offline page. The message: "I can't tell if this page exists, I just can't reach it right now."

Resource Type 3: Static Assets (CSS, JS, Images)

// Cache first let res = await cache.match(reqURL); if (res) return res.clone(); // Not in cache β€” try network if online if (isOnline) { let fetchOptions = { method: request.method, headers: request.headers, credentials: 'omit', cache: 'no-store' }; res = await fetch(request.url, fetchOptions); if (res.ok) { await cache.put(reqURL, res.clone()); return res; } } // Both failed β€” 404 (no need for offline page for a missing stylesheet) return notFoundResponse();

Key decisions:

  1. Cache first β€” CSS, JS, images rarely change. If it's in cache, serve immediately with no network request.
  2. Only fetch if online β€” no point attempting a network request we know will fail.
  3. 404 not offline page β€” if CSS can't be loaded, the browser just gets a 404. No need to serve the offline HTML page for a missing stylesheet.
  4. Update cache on cache miss β€” if the asset wasn't cached and we're online, fetch it and store it for next time.

The Three-Strategy Summary

ResourceStrategyFinal fallback
API callsNetwork first (online only) β†’ cacheSynthetic 404
HTML pagesNetwork first (online only) β†’ cacheOffline page
Static assetsCache first β†’ network (online only)Synthetic 404

Workbox vs. Raw β€” The Trade-off

"Workbox makes a lot of these decisions for you. You make high-level declarative statements, like which strategy you want. But checking for a specific header from the server β€” that's not really a thing."

The X-Not-Found header check, selective caching of GET-only API responses, the different credentials handling per resource type β€” none of this is expressible in a high-level declarative framework. Raw service workers give you 100% control at the cost of 100% responsibility.


Next up: Authentication Aware Routing β€” handling the login, logout, and add-post routes that were left as TODOs.