PWA update mechanism is very important for ensuring users always use the latest version of the app. Here's the complete PWA update process and best practices:
Service Worker Update Process
1. Update Detection
Browsers check for Service Worker updates in the following situations:
- When navigating to the app page
- When Service Worker events are triggered (such as push, sync, etc.)
- Automatic check every 24 hours
2. Update Lifecycle
javascript// sw.js const CACHE_VERSION = 'v2'; const CACHE_NAME = `my-pwa-${CACHE_VERSION}`; // Install event self.addEventListener('install', event => { console.log('Installing new Service Worker:', CACHE_VERSION); event.waitUntil( caches.open(CACHE_NAME) .then(cache => { return cache.addAll([ '/', '/index.html', '/styles/main.css', '/scripts/app.js', '/images/logo.png' ]); }) .then(() => { // Skip waiting, activate immediately return self.skipWaiting(); }) ); }); // Activate event self.addEventListener('activate', event => { console.log('Activating new Service Worker:', CACHE_VERSION); event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { // Delete old version caches if (cacheName.startsWith('my-pwa-') && cacheName !== CACHE_NAME) { console.log('Deleting old cache:', cacheName); return caches.delete(cacheName); } }) ); }).then(() => { // Immediately control all clients return self.clients.claim(); }) ); });
3. Notify Users of Updates
javascript// Listen for updates in main thread let newWorker; if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js') .then(registration => { // Check if there's a new Service Worker registration.addEventListener('updatefound', () => { newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { // New Service Worker available showUpdateNotification(); } }); }); }); } // Show update notification function showUpdateNotification() { const notification = document.createElement('div'); notification.innerHTML = ` <div class="update-notification"> <span>New version available</span> <button id="update-btn">Update Now</button> <button id="dismiss-btn">Later</button> </div> `; notification.style.cssText = ` position: fixed; bottom: 20px; right: 20px; background: #007bff; color: white; padding: 15px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 9999; font-family: Arial, sans-serif; `; document.body.appendChild(notification); // Update now button document.getElementById('update-btn').addEventListener('click', () => { newWorker.postMessage({ action: 'skipWaiting' }); window.location.reload(); }); // Later button document.getElementById('dismiss-btn').addEventListener('click', () => { notification.remove(); }); }
Manual Update Trigger
javascript// Manually check for updates async function checkForUpdates() { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.getRegistration(); if (registration) { await registration.update(); console.log('Checked for updates'); } } } // Periodically check for updates (every hour) setInterval(checkForUpdates, 60 * 60 * 1000); // Check for updates when page gets focus window.addEventListener('focus', checkForUpdates);
Cache Update Strategy
1. Versioned Caches
javascript// Use version numbers to manage caches const CACHE_VERSIONS = { static: 'v1', dynamic: 'v1', images: 'v1' }; const CACHE_NAMES = { static: `static-${CACHE_VERSIONS.static}`, dynamic: `dynamic-${CACHE_VERSIONS.dynamic}`, images: `images-${CACHE_VERSIONS.images}` }; // Update specific cache type function updateCacheType(type) { CACHE_VERSIONS[type] = 'v' + (parseInt(CACHE_VERSIONS[type].slice(1)) + 1); CACHE_NAMES[type] = `${type}-${CACHE_VERSIONS[type]}`; }
2. Smart Cache Update
javascriptself.addEventListener('fetch', event => { const url = new URL(event.request.url); // For HTML documents, always fetch latest version from network if (event.request.mode === 'navigate') { event.respondWith( fetch(event.request) .then(response => { const responseClone = response.clone(); caches.open(CACHE_NAMES.dynamic).then(cache => { cache.put(event.request, responseClone); }); return response; }) .catch(() => caches.match(event.request)) ); } // For static resources, use cache first else if (url.pathname.match(/\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2)$/)) { event.respondWith(cacheFirst(event.request)); } // For API requests, use network first else if (url.pathname.startsWith('/api/')) { event.respondWith(networkFirst(event.request)); } });
Pre-cache Update
javascript// Pre-cache critical resources during install self.addEventListener('install', event => { const CRITICAL_ASSETS = [ '/', '/index.html', '/styles/main.css', '/scripts/app.js', '/offline.html' ]; event.waitUntil( caches.open(CACHE_NAMES.static) .then(cache => { return cache.addAll(CRITICAL_ASSETS); }) ); }); // Update pre-cache during activate self.addEventListener('activate', event => { event.waitUntil( caches.open(CACHE_NAMES.static) .then(cache => { return cache.addAll([ '/styles/main.css', '/scripts/app.js' ]); }) ); });
Background Sync Update
javascript// Register background sync self.addEventListener('sync', event => { if (event.tag === 'sync-updates') { event.waitUntil(syncUpdates()); } }); async function syncUpdates() { try { // Get latest resource list const response = await fetch('/api/updates'); const updates = await response.json(); // Update cache const cache = await caches.open(CACHE_NAMES.dynamic); for (const update of updates) { await cache.add(update.url); } console.log('Background sync completed'); } catch (error) { console.error('Background sync failed:', error); } } // Request background sync in main thread async function requestBackgroundSync() { const registration = await navigator.serviceWorker.ready; await registration.sync.register('sync-updates'); }
Update Strategy Selection
1. Immediate Update
javascript// Force immediate update function forceUpdate() { if ('serviceWorker' in navigator) { navigator.serviceWorker.getRegistration().then(registration => { if (registration && registration.waiting) { registration.waiting.postMessage({ action: 'skipWaiting' }); } }); } }
2. Delayed Update
javascript// Update when user is idle function updateWhenIdle() { if ('serviceWorker' in navigator) { navigator.serviceWorker.getRegistration().then(registration => { if (registration) { registration.update(); } }); } } // Use requestIdleCallback window.requestIdleCallback(updateWhenIdle);
3. Smart Update
javascript// Decide update strategy based on network conditions function smartUpdate() { if ('connection' in navigator) { const connection = navigator.connection; // Update on Wi-Fi or fast network if (connection.effectiveType === '4g' || connection.type === 'wifi') { checkForUpdates(); } // Delay update on slow network else { setTimeout(checkForUpdates, 60000); // Update after 1 minute } } }
Update Best Practices
1. Version Management
javascript// Use semantic versioning const VERSION = { major: 1, minor: 2, patch: 3 }; const CACHE_VERSION = `v${VERSION.major}.${VERSION.minor}.${VERSION.patch}`; // Increment version function incrementVersion(type) { if (type === 'major') { VERSION.major++; VERSION.minor = 0; VERSION.patch = 0; } else if (type === 'minor') { VERSION.minor++; VERSION.patch = 0; } else { VERSION.patch++; } }
2. Rollback Mechanism
javascript// Keep old version caches const MAX_CACHE_VERSIONS = 3; self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(cacheNames => { // Get all version numbers const versions = cacheNames .filter(name => name.startsWith('my-pwa-')) .map(name => name.replace('my-pwa-', '')) .sort() .reverse(); // Delete old versions, keep recent versions const versionsToDelete = versions.slice(MAX_CACHE_VERSIONS); return Promise.all( versionsToDelete.map(version => { return caches.delete(`my-pwa-${version}`); }) ); }) ); });
3. Update Notification
javascript// Provide detailed update information function showDetailedUpdateNotification(updateInfo) { const notification = document.createElement('div'); notification.innerHTML = ` <div class="update-notification"> <h3>New Version Available</h3> <p>Version: ${updateInfo.version}</p> <p>Changes:</p> <ul> ${updateInfo.changes.map(change => `<li>${change}</li>`).join('')} </ul> <button id="update-btn">Update Now</button> <button id="dismiss-btn">Later</button> </div> `; document.body.appendChild(notification); document.getElementById('update-btn').addEventListener('click', () => { forceUpdate(); window.location.reload(); }); document.getElementById('dismiss-btn').addEventListener('click', () => { notification.remove(); }); }
Monitoring and Debugging
1. Update Logging
javascript// Log update events function logUpdateEvent(event) { const logData = { timestamp: Date.now(), event: event.type, version: CACHE_VERSION, userAgent: navigator.userAgent }; // Send to server fetch('/api/update-log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(logData) }); } // Listen to Service Worker events self.addEventListener('install', logUpdateEvent); self.addEventListener('activate', logUpdateEvent);
2. Debug Tools
javascript// Add debug information if (location.hostname === 'localhost') { self.addEventListener('install', event => { console.log('[SW] Installing:', CACHE_VERSION); }); self.addEventListener('activate', event => { console.log('[SW] Activating:', CACHE_VERSION); }); self.addEventListener('fetch', event => { console.log('[SW] Fetch:', event.request.url); }); }
Summary
Key points for PWA updates:
- Version Management: Use version numbers to manage caches
- Update Detection: Periodically check for Service Worker updates
- User Notification: Notify users promptly when new version is available
- Smooth Updates: Provide good update experience
- Rollback Mechanism: Keep old versions for rollback
- Smart Strategy: Choose update strategy based on network conditions
- Monitoring Logs: Record update events for debugging
- Testing Validation: Test update process under different conditions