Advanced PWA Cheat Sheet

Last Updated: November 21, 2025

Service Worker Lifecycle

navigator.serviceWorker.register('/sw.js')
Register service worker
self.addEventListener('install', event => {...})
Install event - caching assets
self.addEventListener('activate', event => {...})
Activate event - cleanup old caches
self.addEventListener('fetch', event => {...})
Fetch event - intercept requests
self.skipWaiting()
Skip waiting phase, activate immediately
clients.claim()
Take control of all clients immediately
event.waitUntil(promise)
Extend event lifetime until promise resolves
registration.update()
Manually check for service worker updates
registration.unregister()
Unregister service worker
registration.waiting
Access waiting service worker
registration.active
Access active service worker
registration.installing
Access installing service worker

Caching Strategies

Cache First (Cache Falling back to Network)
Best for static assets, offline-first
Network First (Network Falling back to Cache)
Best for API requests, fresh data priority
Cache Only
Only serve from cache, fail if not cached
Network Only
Always fetch from network, no caching
Stale While Revalidate
Serve cache, update in background
caches.open('v1').then(cache => cache.addAll([...]))
Pre-cache assets during install
caches.match(request)
Check if request is in cache
cache.put(request, response)
Add response to cache
cache.add(url)
Fetch and cache URL
cache.delete(request)
Remove from cache
caches.delete(cacheName)
Delete entire cache
caches.keys()
Get all cache names

Cache First Implementation

self.addEventListener('fetch', event => { event.respondWith(caches.match(event.request).then(response => response || fetch(event.request))) })
Return cached response or fetch from network
Cache assets on install
Pre-cache critical resources
Good for: CSS, JS, images, fonts
Static assets that rarely change
response.clone()
Clone response before caching (response can only be used once)
Cache versioning
Use cache names with versions: 'v1', 'v2'
Delete old caches on activate
Clean up previous versions

Network First Implementation

event.respondWith(fetch(event.request).then(response => { cache.put(event.request, response.clone()); return response; }).catch(() => caches.match(event.request)))
Fetch from network, cache, fallback to cache on error
Good for: API requests
Data that should be fresh
Update cache in background
Keep cache updated with latest data
Timeout option
Fall back to cache after network timeout
Promise.race([fetch, timeout])
Race network against timeout

Stale While Revalidate

event.respondWith(caches.open(CACHE).then(cache => cache.match(event.request).then(cached => { const fetched = fetch(event.request).then(response => { cache.put(event.request, response.clone()); return response; }); return cached || fetched; })))
Return cache immediately, update in background
Best user experience
Instant response + fresh data next time
Good for: Content that updates regularly
News, social feeds, product listings
Background sync
Updates happen in background
Always fresh on second visit
First visit may be stale, next is fresh

Background Sync

registration.sync.register('sync-tag')
Register background sync
self.addEventListener('sync', event => {...})
Handle sync event in service worker
event.waitUntil(doSyncStuff())
Perform sync operation
IndexedDB for queuing
Store offline actions to sync later
Retry failed requests
Automatically retry when online
event.tag
Identify which sync to perform
Browser retries automatically
Will retry on failure with exponential backoff
Use case: Offline form submissions
Submit when connection restored
Use case: Analytics tracking
Send analytics when online
Use case: Chat messages
Send messages when connection available

Push Notifications

Notification.requestPermission()
Request notification permission
registration.pushManager.subscribe({userVisibleOnly: true, applicationServerKey})
Subscribe to push notifications
self.addEventListener('push', event => {...})
Handle push event in service worker
self.registration.showNotification(title, options)
Show notification
self.addEventListener('notificationclick', event => {...})
Handle notification click
event.notification.close()
Close notification
clients.openWindow(url)
Open URL when notification clicked
event.notification.data
Access custom notification data
options: {body, icon, badge, actions, data}
Notification options
VAPID keys for push
Voluntary Application Server Identification
subscription.toJSON()
Get subscription details to send to server
subscription.unsubscribe()
Unsubscribe from push

Install Prompts

window.addEventListener('beforeinstallprompt', event => {...})
Capture install prompt event
event.preventDefault()
Prevent automatic prompt
deferredPrompt = event
Save event to trigger later
deferredPrompt.prompt()
Show install prompt on demand
deferredPrompt.userChoice.then(choice => {...})
Check if user accepted/dismissed
window.addEventListener('appinstalled', event => {...})
Detect successful install
Display custom install button
Show UI when installable
Hide button after install
Remove install UI when installed
matchMedia('(display-mode: standalone)').matches
Detect if running as installed app
window.navigator.standalone
iOS standalone detection

Web App Manifest

"name": "My App"
Full application name
"short_name": "App"
Short name for home screen
"start_url": "/"
URL to open when launched
"display": "standalone"
Display mode (standalone, fullscreen, minimal-ui, browser)
"background_color": "#ffffff"
Splash screen background
"theme_color": "#000000"
Browser UI color
"icons": [{src, sizes, type}]
App icons for various sizes
"orientation": "portrait"
Default orientation
"scope": "/"
Navigation scope
"description": "App description"
App description for stores
"categories": ["productivity"]
App categories
"screenshots": [{src, sizes, type}]
Screenshots for app stores
<link rel="manifest" href="/manifest.json">
Link manifest in HTML

Advanced Service Worker Patterns

clients.matchAll()
Get all controlled clients
client.postMessage(data)
Send message to client page
navigator.serviceWorker.addEventListener('message', event => {...})
Receive messages in page
self.addEventListener('message', event => {...})
Receive messages in service worker
event.source.postMessage(response)
Reply to message sender
importScripts('script.js')
Import external scripts in SW
Cache navigation requests
Cache HTML pages for offline
request.mode === 'navigate'
Detect navigation requests
Precache app shell
Cache core UI for instant loading
Runtime caching
Cache resources as they're requested

Offline Strategies

App shell architecture
Separate static shell from dynamic content
Offline page fallback
Show custom offline page
IndexedDB for data
Store structured data offline
LocalStorage for simple data
5MB limit, synchronous
Cache API for resources
Store request/response pairs
window.addEventListener('online', ...)
Detect when connection restored
window.addEventListener('offline', ...)
Detect when connection lost
navigator.onLine
Check connection status
Show offline indicator
UI feedback for offline state
Queue actions for sync
Store actions to perform when online

Performance Optimization

Lazy load service worker
Register after page load
Selective caching
Don't cache everything
Cache expiration
Remove old cached items
Cache size limits
Prevent unlimited cache growth
Opaque responses
Cross-origin responses count as 7MB
Skip caching large files
Don't cache videos, large downloads
Update on user interaction
Prompt to update when new SW available
Streaming responses
Use ReadableStream for large responses
Compression
Serve compressed assets
Resource hints
Use preload, prefetch, preconnect
Pro Tip: Use Stale While Revalidate for the best user experience - instant loads with background updates! Always implement skipWaiting() carefully to avoid breaking active sessions. Test offline scenarios thoroughly, and use Background Sync for critical user actions. Remember to version your caches and clean up old ones!
← Back to Data Science & ML | Browse all categories | View all cheat sheets