Mastering Offline Caching with Service Workers: A Deep Dive into PWA Strategies
This article explains how Progressive Web Apps use Service Workers to provide offline access, installability, push notifications, and responsive layouts, and it walks through registration, caching strategies, dynamic cache management, expiration handling, and practical code examples for robust offline experiences.
1. What is PWA
PWA (Progressive Web App) combines web and native app features using modern web technologies such as Service Workers and Web App Manifests to deliver an app‑like experience.
2. Service Worker
A Service Worker runs in the browser background, intercepts network requests, caches HTML, CSS, JavaScript, images and other assets, and can also handle push notifications and background sync.
Compared with a browser extension’s background script, a Service Worker operates independently of any page, cannot access the DOM directly, and is subject to stricter security permissions.
3. Registration
<code><!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Service Worker 示例</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker 注册成功:', registration.scope);
})
.catch(function(error) {
console.log('Service Worker 注册失败:', error);
});
});
}
</script>
</body>
</html>
// service-worker.js
const filesToCache = [
'/',
'/index.html',
'/styles.css',
'/script.js',
'/image.jpg'
];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('my-cache')
.then(function(cache) {
return cache.addAll(filesToCache);
})
);
});
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
return cacheName !== 'my-cache';
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
return response || fetch(event.request);
})
);
});</code>4. Usage
After registration, open Chrome DevTools → Application tab to see the active Service Worker and the cache named
my-cache. When the page is refreshed in offline mode, the cached resources are served.
5. Determining Pre‑Cache Scope
For projects with many resources, list the core files in
filesToCacheand give the cache a versioned name (e.g.,
pre-cache-v1). Dynamic resources are handled by a separate runtime cache.
6. Cache Strategies
Cache First – serve from cache, fall back to network.
Network First – try network, fall back to cache.
Cache Only – serve only from cache.
Network Only – always fetch from network.
Stale‑While‑Revalidate – return cached response immediately and update it in the background.
The article mainly uses the Cache First strategy for the pre‑cache and Stale‑While‑Revalidate for dynamic resources.
7. Dynamic Cache & Runtime Updates
<code>// New runtime cache name
const runtimeCacheName = 'runtime-cache-' + version;
self.addEventListener('fetch', function(event) {
const {request} = event;
if (isStaleWhileRevalidate(request)) {
event.respondWith(handleFetch(request));
return;
}
// default fallback
event.respondWith(fetch(request));
});
function handleFetch(request) {
return caches.match(request).then(function(response) {
const fetchPromise = fetch(request).then(function(networkResponse) {
if (networkResponse && networkResponse.status === 200) {
const clone = networkResponse.clone();
caches.open(runtimeCacheName).then(cache => cache.put(request, clone));
}
return networkResponse;
});
return response || fetchPromise;
});
}</code>8. Cache Expiration
Use Workbox’s
ExpirationPluginor implement a custom expiration check that stores a timestamp header (e.g.,
sw-save-date) and removes entries after a defined max‑age.
<code>function isExpired(response, maxAge) {
const saved = Number(response.headers.get('sw-save-date'));
if (!saved) return false;
return Date.now() > saved + maxAge * 1000;
}
</code>9. Opaque Responses
Cross‑origin requests without CORS headers return opaque responses (status 0, unreadable body). To cache them, create a new
Requestwith
mode: 'cors'and appropriate credentials.
<code>const newRequest = request.url.endsWith('index.html')
? request
: new Request(request, {mode: 'cors', credentials: 'omit'});
</code>10. Cleanup & Fallback
Set a flag (
SW_FALLBACK) to switch between registering the Service Worker and unregistering it when problems occur.
<code>if ('serviceWorker' in navigator) {
if (!SW_FALLBACK) {
navigator.serviceWorker.register('/eemf-service-worker.js')
.then(reg => console.log('Service Worker 注册成功!'))
.catch(err => console.log('Service Worker 注册失败:', err));
} else {
navigator.serviceWorker.getRegistration('/')
.then(reg => {
if (reg) reg.unregister().then(() => window.location.reload());
});
}
}
</code>11. Conclusion
PWA offline caching via Service Workers offers a powerful way to improve load speed and reliability, but it requires careful versioning, cache management, and fallback strategies. A complete demo is available at https://github.com/suilang/PWA-demo .
References
Service Worker specification
Service Worker overview
Workbox documentation
GPT Q&A
JD Cloud Developers
JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.