Topics
v6.0.0
On this page · sections

Progressive Web Apps

A PWA is a web app that is installable, reliable on flaky networks, and engaging — progressive enhancement on the open web, not a separate platform.

PieceRoleRequirement
ManifestInstall metadata, icons, themeLinked from HTML
Service workerIntercepts fetch, enables offlineHTTPS (localhost exempt in dev)
HTTPSSecure originRequired for SW registration
Caching strategyPer-asset freshness rulesWorkbox recommended

PWA Requirements

Definition

{ "name": "Acme Dashboard", "short_name": "Acme", "start_url": "/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#0f172a", "icons": [ { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } ] }
<link rel="manifest" href="/manifest.webmanifest" /> <meta name="theme-color" content="#0f172a" />
if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js', { scope: '/' }); }); }
HTTPS → manifest → service worker registered → install prompt eligible → SW intercepts fetch

Install prompt fires when manifest + SW + icons meet browser criteria plus engagement heuristics (visited twice, minutes apart).

display: standalone — OS status bar, app-like feel. fullscreen — hides browser chrome entirely.

iOS supports Add to Home Screen with limitations — smaller cache quotas, historically weaker push support.

Interview: PWA is manifest + service worker + HTTPS — not a framework feature.

Service Worker Caching Strategies

Definition

import { precacheAndRoute } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; import { CacheFirst, NetworkFirst, StaleWhileRevalidate, NetworkOnly } from 'workbox-strategies'; import { ExpirationPlugin } from 'workbox-expiration'; precacheAndRoute(self.__WB_MANIFEST); registerRoute( ({ request }) => request.destination === 'image', new CacheFirst({ cacheName: 'images', plugins: [new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 3600 })], }) ); registerRoute( ({ url }) => url.pathname.startsWith('/api/'), new NetworkFirst({ cacheName: 'api', networkTimeoutSeconds: 5 }) ); registerRoute( ({ request }) => request.method !== 'GET', new NetworkOnly() );
AssetStrategyWhy
Hashed JS/CSSCache-firstImmutable filename
index.htmlNetwork-firstMust get latest shell
Product imagesSWRFast paint, background refresh
POST /checkoutNetwork-onlyNever cache mutations
  • Cache-first — read cache, fallback network — static hashed assets
  • Network-first — try network, fallback cache — HTML and API GET
  • Stale-while-revalidate — return cache, update in background — fonts, avatars
  • Network-only — auth and mutations

Version cache names (static-v3) and delete old caches on SW activate. Never cache-first index.html — users stuck on old app version.

Interview: app.a1b2c3.js is immutable — cache-first safe. index.html is not.

Offline UX Patterns

Definition

const OFFLINE_URL = '/offline.html'; self.addEventListener('install', (event) => { event.waitUntil(caches.open('offline-v1').then((c) => c.add(OFFLINE_URL))); }); self.addEventListener('fetch', (event) => { if (event.request.mode === 'navigate') { event.respondWith( fetch(event.request).catch(() => caches.match(OFFLINE_URL)) ); } });
'use client'; import { useEffect, useState } from 'react'; export function OfflineBanner() { const [offline, setOffline] = useState(!navigator.onLine); useEffect(() => { const on = () => setOffline(false); const off = () => setOffline(true); window.addEventListener('online', on); window.addEventListener('offline', off); return () => { window.removeEventListener('online', on); window.removeEventListener('offline', off); }; }, []); if (!offline) return null; return ( <div role="status">You are offline. Changes will sync when reconnected.</div> ); }

Patterns:

  • Offline fallback page — precached HTML for navigate requests
  • Read-only mode — show cached data, disable mutations
  • Queued mutations — Background Sync API or IndexedDB queue, replay on online
  • Clear messaging — banner, not silent failure

Don’t cache API responses with auth headers in shared cache — private data risk. Align SW TTL with CDN Cache-Control philosophy.

Interview: Offline UX needs explicit fallback + user messaging — not just caching assets.

Service Worker Update Lifecycle

Definition

// sw.js — new version waiting self.addEventListener('install', (event) => { self.skipWaiting(); // activate immediately (use with care) }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== 'static-v3').map((k) => caches.delete(k))) ).then(() => clients.claim()) ); });
'use client'; useEffect(() => { if (!('serviceWorker' in navigator)) return; navigator.serviceWorker.addEventListener('controllerchange', () => { window.location.reload(); // new SW took control }); navigator.serviceWorker.ready.then((reg) => { reg.addEventListener('updatefound', () => { const newWorker = reg.installing; newWorker?.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { // Show "Update available" prompt — don't force skipWaiting silently } }); }); }); }, []);

Default: new SW waits until all tabs close. skipWaiting() + clients.claim() activates immediately — can cause version mismatch mid-session.

Staff UX: prompt user to refresh when update available. Coordinate with lazy chunk hashes — stale SW serving old remoteEntry causes chunk 404 after deploy.

Workbox generateSW vs injectManifest — precache manifest from build output.

Interview: Forced skipWaiting without user prompt can break in-flight sessions — show update banner instead.

Production Trade-offs

Definition

PWAs add complexity — evaluate ROI per product:

BenefitCost
Install to home screenSW update lifecycle bugs
Offline read accessCache invalidation complexity
Push notifications (where supported)iOS limitations
Faster repeat visitsStorage quota management

When PWA makes sense: field apps (retail, logistics), content readers, tools used daily on mobile with flaky connectivity.

When to skip: SEO-critical marketing sites needing instant freshness, apps with heavy real-time data where stale cache is dangerous, teams without SW ops experience.

Lighthouse PWA score checks baseline; Chrome adds engagement heuristics beyond Lighthouse.

Storage quota: navigator.storage.estimate() — prune caches with ExpirationPlugin. Test on low-end Android devices.

Staff: monitor SW registration rate, cache hit ratio, offline error rate, update prompt acceptance. Feature flag SW registration — disable if post-deploy incident.

Interview: PWA is progressive enhancement — measure whether install + offline justify SW operational cost.

Senior Interview Summary

Requirements

HTTPS + manifest + service worker. Icons 192/512. Install heuristics beyond Lighthouse.

Caching

Cache-first hashed assets. Network-first HTML/API. Network-only mutations. Workbox in production.

Offline

Fallback page, read-only mode, mutation queue, user-visible banner.

Updates

New SW waits by default. Prompt before skipWaiting. Purge old caches on activate.

Trade-offs

ROI for field/daily-use apps. Skip when freshness critical or team lacks SW ops.

Best mental model

SW = programmable proxy between app and network Manifest = install metadata Strategy = per-asset freshness contract

Practical rules

index.html → network-first always Hashed bundles → cache-first + immutable CDN headers Auth responses → never cache in SW Deploy → version cache names + user update prompt

What to say in interviews

Explain manifest + SW + HTTPS triangle, compare three caching strategies with asset examples, describe offline UX layers, and discuss update lifecycle without forcing silent skipWaiting.

Last updated on