PWA's offline functionality is one of its core features, mainly implemented through Service Worker and caching mechanisms. Here's a complete solution for implementing offline functionality:
Core Components of Offline Functionality
1. Service Worker
Service Worker is the foundation of offline functionality, capable of intercepting network requests and returning resources from cache.
2. Cache API
Used for storing and managing cached resources.
Steps to Implement Offline Functionality
Step 1: Register Service Worker
javascript// Register in main thread if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then(registration => { console.log('SW registered:', registration); }) .catch(error => { console.log('SW registration failed:', error); }); }); }
Step 2: Pre-cache Critical Resources
javascript// sw.js const CACHE_NAME = 'my-pwa-v1'; const ASSETS_TO_CACHE = [ '/', '/index.html', '/styles/main.css', '/scripts/app.js', '/images/logo.png', '/offline.html' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => { return cache.addAll(ASSETS_TO_CACHE); }) .then(() => { return self.skipWaiting(); }) ); });
Step 3: Implement Caching Strategy
javascriptself.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => { // Cache hit, return directly if (response) { return response; } // Cache miss, request network return fetch(event.request) .then(response => { // Check if response is valid if (!response || response.status !== 200 || response.type !== 'basic') { return response; } // Clone response and cache const responseToCache = response.clone(); caches.open(CACHE_NAME) .then(cache => { cache.put(event.request, responseToCache); }); return response; }) .catch(() => { // Network request failed, return offline page if (event.request.mode === 'navigate') { return caches.match('/offline.html'); } }); }) ); });
Step 4: Create Offline Page
html<!-- offline.html --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Offline</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; font-family: Arial, sans-serif; background: #f5f5f5; } .offline-container { text-align: center; padding: 40px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .offline-icon { font-size: 64px; margin-bottom: 20px; } .offline-title { font-size: 24px; margin-bottom: 10px; color: #333; } .offline-message { color: #666; margin-bottom: 20px; } .retry-button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } </style> </head> <body> <div class="offline-container"> <div class="offline-icon">📡</div> <h1 class="offline-title">You are currently offline</h1> <p class="offline-message">Please check your network connection and try again</p> <button class="retry-button" onclick="window.location.reload()">Reload</button> </div> <script> // Listen for network status changes window.addEventListener('online', () => { window.location.reload(); }); </script> </body> </html>
Step 5: Monitor Network Status
javascript// Monitor network status in main thread window.addEventListener('online', () => { console.log('Network connected'); // Can perform operations here, like syncing data }); window.addEventListener('offline', () => { console.log('Network disconnected'); // Show offline notification showOfflineNotification(); }); function showOfflineNotification() { const notification = document.createElement('div'); notification.textContent = 'You are currently offline'; notification.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: #ff9800; color: white; padding: 10px 20px; border-radius: 4px; z-index: 9999; `; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); }
Advanced Offline Features
1. Background Sync
javascript// Register sync event self.addEventListener('sync', event => { if (event.tag === 'sync-data') { event.waitUntil(syncData()); } }); async function syncData() { // Get data stored while offline const offlineData = await getOfflineData(); // Sync to server for (const data of offlineData) { try { await fetch('/api/sync', { method: 'POST', body: JSON.stringify(data) }); // Sync successful, delete local data await removeOfflineData(data.id); } catch (error) { console.error('Sync failed:', error); } } } // Request sync in main thread navigator.serviceWorker.ready.then(registration => { registration.sync.register('sync-data'); });
2. IndexedDB for Storing Offline Data
javascript// Open IndexedDB const dbPromise = idb.open('my-pwa-db', 1, upgradeDB => { upgradeDB.createObjectStore('offline-data', { keyPath: 'id' }); }); // Save offline data async function saveOfflineData(data) { const db = await dbPromise; await db.add('offline-data', data); } // Get offline data async function getOfflineData() { const db = await dbPromise; return await db.getAll('offline-data'); } // Delete offline data async function removeOfflineData(id) { const db = await dbPromise; await db.delete('offline-data', id); }
Best Practices for Offline Functionality
- Pre-cache Critical Resources: Ensure core functionality is available offline
- Provide Friendly Offline Page: Inform users of current status and provide solutions
- Monitor Network Status: Respond to network changes in time
- Implement Data Synchronization: Store data while offline, sync when online
- Set Reasonable Caching Strategy: Balance performance and freshness
- Test Offline Scenarios: Use Chrome DevTools Offline mode for testing
- Provide Network Status Indicator: Let users know current network status
Testing Offline Functionality
Test with Chrome DevTools:
- Open DevTools (F12)
- Switch to Network tab
- Check "Offline" mode
- Refresh page to test offline functionality
- Uncheck "Offline" to test recovery functionality