Progressive Web Apps (PWA)
Service Workers Fundamentals
What are Service Workers?
Service Workers are JavaScript files that run separately from the main browser thread, acting as a programmable network proxy. They intercept network requests, cache resources, and enable offline functionality, making them the backbone of Progressive Web Apps.
Key Characteristics:
- Runs in background: Separate from web pages, can't access DOM directly
- Event-driven: Responds to events (install, activate, fetch, push, sync)
- Programmable cache: Full control over caching strategies
- HTTPS required: Must be served over secure connection (except localhost)
- Scope-based: Controls pages within its scope path
- Lifecycle-managed: Install, activate, and update phases
Service Worker Capabilities
What Service Workers CAN do:
✓ Intercept and handle network requests
✓ Cache resources (HTML, CSS, JS, images, API responses)
✓ Enable offline functionality
✓ Background sync (retry failed requests)
✓ Push notifications
✓ Manage multiple caches
✓ Update cached content intelligently
What Service Workers CANNOT do:
✗ Access the DOM directly
✗ Use synchronous APIs (localStorage)
✗ Access cookies directly (use fetch with credentials)
✗ Run without being registered from a page
Service Worker Lifecycle
The Complete Lifecycle:
- Registration: Page registers the service worker file
- Installation: Service worker downloads and installs (happens once)
- Activation: Service worker takes control (happens after install)
- Idle: Waits for events (fetch, push, sync)
- Fetch/Event handling: Responds to network requests and events
- Termination: Browser can stop idle service workers to save memory
- Update: New version triggers new install/activate cycle
Service Worker Lifecycle Diagram
┌─────────────────┐
│ Page Loads │
│ Registers SW │
└────────┬────────┘
│
▼
┌─────────────────┐
│ INSTALLING │ ← First time only
│ (install event) │ Cache static assets
└────────┬────────┘
│
▼
┌─────────────────┐
│ INSTALLED │
│ (waiting) │ ← Waits for old SW to stop
└────────┬────────┘
│
▼
┌─────────────────┐
│ ACTIVATING │
│ (activate event)│ ← Clean up old caches
└────────┬────────┘
│
▼
┌─────────────────┐
│ ACTIVATED │
│ (idle) │ ← Controls pages in scope
└────────┬────────┘
│
▼
┌─────────────────┐
│ FETCH/EVENTS │ ← Intercepts requests
│ (fetch event) │ Handles push, sync
└─────────────────┘
Browser Support
| Browser | Support | Since Version |
|---|---|---|
| Chrome | ✓ Full Support | v40 (2015) |
| Firefox | ✓ Full Support | v44 (2016) |
| Safari | ✓ Full Support | v11.1 (2018) |
| Edge | ✓ Full Support | v17 (2018) |
| Opera | ✓ Full Support | v27 (2015) |
| Samsung Internet | ✓ Full Support | v4 (2016) |
Browser Support Check:
if ('serviceWorker' in navigator) {
console.log('Service Worker supported!');
} else {
console.log('Service Worker not supported');
}
HTTPS Requirement
Security Requirement:
Service Workers MUST be served over HTTPS in production because they can intercept network requests and modify responses. This prevents man-in-the-middle attacks.
Exceptions:localhostand127.0.0.1(for local development)- Local network addresses (e.g.,
192.168.x.x) in some browsers
- Use localhost during development
- Use tools like ngrok for HTTPS tunneling
- Use Let's Encrypt for free SSL certificates in production
Service Worker Scope
Understanding Scope:
The scope determines which pages the service worker controls. By default, the scope is the directory where the service worker file is located.
// Service worker at: /sw.js
// Default scope: / (entire site)
navigator.serviceWorker.register('/sw.js');
// Service worker at: /app/sw.js
// Default scope: /app/ (only pages under /app/)
navigator.serviceWorker.register('/app/sw.js');
// Custom scope (must be within service worker directory or higher)
navigator.serviceWorker.register('/sw.js', {
scope: '/app/' // Only controls /app/* pages
});
// INVALID: Can't control pages outside service worker's directory
// Service worker at: /app/sw.js
navigator.serviceWorker.register('/app/sw.js', {
scope: '/' // ✗ Error: Scope outside service worker path
});
Scope Best Practices:
- Place service worker at root (/) for site-wide control
- Use custom scope for controlling specific sections
- Multiple service workers can control different scopes
- More specific scopes take precedence over general ones
Debugging Service Workers
Chrome DevTools
1. Open DevTools (F12)
2. Go to Application tab
3. Click Service Workers in sidebar
You can:
✓ See registered service workers
✓ Unregister service workers
✓ Force update/skip waiting
✓ Simulate offline mode
✓ View service worker source
✓ See active/waiting workers
✓ Check scope and status
Firefox DevTools
1. Open DevTools (F12)
2. Go to Application tab (or about:debugging#/runtime/this-firefox)
3. Click Service Workers
Firefox-specific:
✓ See all service workers across all sites
✓ Debug service workers
✓ Start/stop service workers
✓ View push subscriptions
Safari DevTools
1. Enable Develop menu (Safari → Preferences → Advanced)
2. Develop → Service Workers
3. Select your site
Safari-specific:
✓ List all service workers
✓ Inspect service worker console
✓ View service worker status
Service Worker Events
Core Events:
| install | Fired when service worker is first installed |
| activate | Fired when service worker becomes active |
| fetch | Fired for every network request in scope |
| message | Communication between page and service worker |
| sync | Background sync when connection restored |
| push | Push notification received |
Basic Service Worker Structure
// sw.js - Basic service worker file
// Install event - Cache static assets
self.addEventListener('install', event => {
console.log('Service Worker: Installing...');
// Caching logic goes here
});
// Activate event - Clean up old caches
self.addEventListener('activate', event => {
console.log('Service Worker: Activated');
// Cleanup logic goes here
});
// Fetch event - Intercept network requests
self.addEventListener('fetch', event => {
console.log('Service Worker: Fetching', event.request.url);
// Request handling logic goes here
});
Common Pitfalls:
- Not checking browser support: Always check
if ('serviceWorker' in navigator) - Forgetting HTTPS: Service workers won't register on HTTP (except localhost)
- Wrong scope: Service worker can't control pages outside its directory
- Not updating: Old service workers can stay active indefinitely without update logic
- Blocking activation: New service workers wait until all pages using old one close
- Cache without cleanup: Old caches accumulate and waste storage
Service Worker Lifecycle Example
// Page script (main.js)
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered:', registration.scope);
// Check for updates periodically
registration.update();
// Listen for updates
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// New service worker available
console.log('New content available, please refresh');
}
});
});
})
.catch(error => {
console.error('SW registration failed:', error);
});
}
Exercise:
- Open Chrome DevTools → Application → Service Workers
- Visit a PWA (like twitter.com or pinterest.com) and inspect its service worker
- Check the service worker status, scope, and source code
- Use "Offline" checkbox to simulate offline mode
- Reload the page and observe how it works offline
- Click "Unregister" and reload - see the difference
- Create a simple HTML page and try registering a basic service worker
- Inspect the service worker lifecycle in DevTools