PWA & Service Worker
Manifest, service worker lifecycle, caching strategies, offline, push notifications.
pwaservice-workerfrontendweb
# PWA & Service Worker
## Web App Manifest
```json
{
"name": "My App",
"short_name": "App",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
```
```html
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#000000">
```
## Service Worker lifecycle
Install -> Activate -> Fetch/Sync/Push
```js
// sw.js
self.addEventListener('install', e => {
e.waitUntil(
caches.open('v1').then(c => c.addAll(['/','/','/app.css','/app.js']))
)
self.skipWaiting()
})
self.addEventListener('activate', e => {
e.waitUntil(caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== 'v1').map(k => caches.delete(k)))
))
self.clients.claim()
})
```
## Caching strategies
- **Cache First**: serve from cache, fallback to network. Best for static assets.
- **Network First**: try network, fallback to cache. Best for API data.
- **Stale While Revalidate**: serve cache immediately, update in background.
- **Network Only**: no caching (e.g. analytics).
```js
self.addEventListener('fetch', e => {
if (e.request.url.includes('/api/')) {
// Network first
e.respondWith(
fetch(e.request).then(res => {
const clone = res.clone()
caches.open('api').then(c => c.put(e.request, clone))
return res
}).catch(() => caches.match(e.request))
)
} else {
// Cache first
e.respondWith(caches.match(e.request).then(r => r || fetch(e.request)))
}
})
```
## Register from app
```js
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('SW registered', reg.scope))
})
}
```
## Workbox (recommended for production)
```bash
npm install workbox-webpack-plugin
# or with Vite:
npm install vite-plugin-pwa
```
```js
// vite-plugin-pwa config
VitePWA({
strategies: 'injectManifest',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
runtimeCaching: [{
urlPattern: /\/api\//,
handler: 'NetworkFirst',
options: { cacheName: 'api-cache', expiration: { maxAgeSeconds: 86400 } },
}],
},
})
```
## Push notifications
```js
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
})
await fetch('/api/subscribe', { method: 'POST', body: JSON.stringify(sub) })
```API: /api/skills/pwa-service-worker