LEARNING OBJECTIVES โต
- Configure an installable Web App Manifest (
manifest.json) with e-commerce display modes, adaptive maskable icons, and theme colors. - Implement dual Service Worker caching strategies: Cache-First for static assets (fonts, icons, critical CSS) and Stale-While-Revalidate for catalog data.
- Build an offline fallback page (
offline.html) served automatically when network connectivity drops during non-cached navigation. - Persist offline cart mutations in
IndexedDBand trigger automatic background reconciliation when network connectivity is restored.
๐ The Mental Model & Story (Intuitive Foundation)
Imagine boarding a high-speed commuter train. You enter a subterranean tunnel where mobile cellular reception drops to zero bars.
- In a fragile web application, clicking "Timepieces" displays the dreaded Google Chrome "No Internet Dinosaur" error screen. Your shopping cart disappears, and when the train emerges from the tunnel 10 minutes later, you have to start your entire session over.
- In a resilient Progressive Web App (PWA), the storefront continues running seamlessly. You browse previously viewed timepieces, inspect high-res photos stored in local cache, adjust quantities in your slideout shopping drawer, and tap "Proceed to Checkout".
The application queues your checkout intent locally in an IndexedDB background mutation mailbox. The moment your phone reconnects to a 5G tower, the Service Worker awakens, syncs your cart payload with the payment API in the background, and displays a subtle notification: "Your order was securely transmitted!"
Offline resilience transforms a website from a fragile remote document into an installed, native-grade desktop and mobile retail experience.
Technical Deep Dive & Specifications
Service Worker Caching Strategies for E-Commerce
Not all web assets should be cached using the same strategy:
+----------------------------------------------------------------------------------------------------+
| SERVICE WORKER ROUTING ARCHITECTURE |
+----------------------------------------------------------------------------------------------------+
| |
| [ Incoming Request (fetch) ] |
| โ |
| โโโ 1. Static Assets (CSS, JS, Fonts, App Shell) |
| โ โโโ Strategy: CACHE-FIRST (Fallback to Network) |
| โ - Instant sub-50ms local disk delivery |
| โ |
| โโโ 2. Catalog Products & PDP HTML |
| โ โโโ Strategy: STALE-WHILE-REVALIDATE |
| โ - Returns cached catalog instantly, then updates cache with fresh price/stock in BG |
| โ |
| โโโ 3. Dynamic Mutations (Checkout / Payment / Cart API) |
| โ โโโ Strategy: NETWORK-ONLY (Fallback to IndexedDB Queue) |
| โ - Bypasses HTTP cache to prevent duplicate financial transactions |
| โ |
| โโโ 4. Offline Fallback Catch |
| โโโ Serves offline.html if HTML navigation request fails while offline |
+----------------------------------------------------------------------------------------------------+
Strategy Comparison Matrix
| Asset Class | Service Worker Strategy | Cache Invalidation Trigger | Failure Mode |
|---|---|---|---|
| App Shell (HTML/CSS/JS) | Cache-First |
Service Worker version bump (CACHE_VERSION = 'v2') |
Loads previous shell |
| Product Media (Images) | Cache-First with LRU eviction |
Cache size quota / TTL (e.g. 50 items max) | Uses placeholder |
| Catalog Listings | Stale-While-Revalidate |
Background network fetch on each navigation | Serves cached snapshot |
| Checkout API POST | Network-Only + IndexedDB Queue |
Network online event (window.addEventListener('online')) |
Queued locally |
๐ป Interactive Code Playground
Starter Code: Production Web App Manifest (manifest.json)
Production Service Worker (sw.js)
Production Offline Fallback Page (offline.html)
Line-by-Line Code Breakdown
- Lines 7โ16 (
manifest.json): Configures standalone PWA installation modes ("display": "standalone"), matching browser theme bars with the luxury dark aesthetic ("theme_color": "#0a0a0c"). - Lines 17โ28 (
"purpose": "maskable"): Supplies adaptive maskable icons that Android and iOS automatically crop into circular or squircle launcher badges. - Lines 31โ38 (
sw.jsPre-caching): Pre-populates the cache during theinstallphase, guaranteeing instant availability of the application shell before first use. - Lines 41โ52 (
caches.delete()): Removes outdated cache versions during theactivateevent lifecycle, preventing device storage bloat. - Lines 63โ76 (
request.mode === 'navigate'): Detects full HTML page navigations. If the network drops, it serves the cached page or automatically rendersoffline.html.
Expected Browser Render Output
{
"name": "Aura Luxe Haute Horlogerie",
"short_name": "Aura Luxe",
"description": "High-performance handcrafted luxury timepieces and bespoke accessories.",
"start_url": "/index.html?utm_source=pwa",
"display": "standalone",
"background_color": "#0a0a0c",
"theme_color": "#0a0a0c",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"categories": ["shopping", "lifestyle"],
"shortcuts": [
{
"name": "View Timepieces",
"url": "/catalog.html?category=timepieces",
"icons": [{ "src": "/icons/shortcut-watch.png", "sizes": "96x96" }]
},
{
"name": "Shopping Bag",
"url": "/catalog.html?open=cart",
"icons": [{ "src": "/icons/shortcut-cart.png", "sizes": "96x96" }]
}
]
}const CACHE_VERSION = 'aura-luxe-v1';
const STATIC_CACHE = `${CACHE_VERSION}-static`;
const DYNAMIC_CACHE = `${CACHE_VERSION}-dynamic`;
const STATIC_PRECACHE = [
'/',
'/index.html',
'/catalog.html',
'/checkout.html',
'/offline.html',
'/css/critical.css',
'/js/store.js'
];
// 1. Install Event: Pre-cache Application Shell
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(STATIC_CACHE).then((cache) => {
return cache.addAll(STATIC_PRECACHE);
}).then(() => self.skipWaiting())
);
});
// 2. Activate Event: Clean up legacy caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) => {
return Promise.all(
keys.map((key) => {
if (key !== STATIC_CACHE && key !== DYNAMIC_CACHE) {
return caches.delete(key);
}
})
);
}).then(() => self.clients.claim())
);
});
// 3. Fetch Event: Multi-tier routing strategies
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Ignore cross-origin API and chrome extensions
if (request.method !== 'GET' || !url.origin.includes(self.location.origin)) {
return;
}
// Strategy A: HTML Navigation (Stale-While-Revalidate with Offline Fallback)
if (request.mode === 'navigate') {
event.respondWith(
fetch(request)
.then((networkResponse) => {
const clone = networkResponse.clone();
caches.open(DYNAMIC_CACHE).then((cache) => cache.put(request, clone));
return networkResponse;
})
.catch(() => {
return caches.match(request).then((cachedResponse) => {
return cachedResponse || caches.match('/offline.html');
});
})
);
return;
}
// Strategy B: Static Assets & Media (Cache-First)
event.respondWith(
caches.match(request).then((cachedResponse) => {
if (cachedResponse) return cachedResponse;
return fetch(request).then((networkResponse) => {
if (!networkResponse || networkResponse.status !== 200) {
return networkResponse;
}
const clone = networkResponse.clone();
caches.open(DYNAMIC_CACHE).then((cache) => cache.put(request, clone));
return networkResponse;
});
})
);
});+---------------------------------------------------------------------------------------------------------+
| [STANDALONE PWA WINDOW - NO BROWSER CHROME] |
+---------------------------------------------------------------------------------------------------------+
| |
| ๐ก |
| YOU'RE CURRENTLY OFFLINE |
| |
| Your internet connection is temporarily unavailable. Previously viewed collections |
| and your shopping bag are saved locally. |
| |
| [ Check Connection & Retry ] |
| |
+---------------------------------------------------------------------------------------------------------+๐๏ธ Hands-On Exercise
๐ฏ The Challenge: Build an Offline Add-to-Cart Sync Queue using IndexedDB
Instructions:
- Initialize an IndexedDB object store named
offline_cart_mutations. - When the user clicks "Add to Bag" while
navigator.onLine === false, store the product mutation object with a timestamp. - Listen for
window.addEventListener('online', syncOfflineQueue). - When connectivity returns, read the pending mutations from IndexedDB, transmit them to the backend API, clear the store, and notify the user via
#live-announcer.
๐ Starter Code Sandbox
โ ๏ธ Common Pitfalls
- Caching POST/PUT Financial Transactions in Service Workers: Caching API responses for payment authorizations. Financial transactions must always be
network-onlyto prevent duplicate billing charges. - Forgetting to Bump
CACHE_VERSIONDuring Deployments: Updating your HTML/CSS on the server but leavingconst CACHE_VERSION = 'v1'insw.js. Clients will be trapped on stale cached assets indefinitely. - Omitting
purpose: "maskable"Icons: Supplying only raw square PNG icons. Modern mobile OS launchers will render unsightly white borders around the app icon.
๐ก Pro Tips
- Call
self.skipWaiting()andself.clients.claim(): During Service Worker installation, invokingskipWaiting()activates the new worker immediately, preventing users from getting stuck on legacy cached code until all tabs are closed. - Use Background Sync API (
registration.sync.register): On supported browsers, the Background Sync API executes offline sync jobs even if the user closes the browser before the network reconnects.
๐ Key Takeaways
- A valid
manifest.jsonenables installability, standalone display, and home-screen presence. - Apply
Cache-Firstfor static assets andStale-While-Revalidatefor catalog pages. - Always serve an
offline.htmlfallback when navigation requests fail without network connectivity. - Store offline mutations (cart adds/updates) in
IndexedDBand synchronize whenonlineevents fire. - Never cache non-idempotent payment or checkout API calls.
- --