A
AiTechWorlds
AiTechWorlds
Web App Manifest, service worker lifecycle, cache strategies (cache-first/network-first), push notifications, and Workbox.
A Progressive Web App (PWA) is a web application that uses modern browser APIs to deliver app-like capabilities: offline support, home screen install, push notifications, and fast loading.
| Requirement | Purpose |
|---|---|
| HTTPS | Security prerequisite |
| Web App Manifest | Enables install prompt |
| Service Worker | Offline support, caching |
| Responsive design | Works on all screen sizes |
| Fast load (LCP < 2.5s) | Good user experience |
// public/manifest.json
{
"name": "My App",
"short_name": "MyApp",
"description": "A progressive web app",
"start_url": "/",
"display": "standalone",
"orientation": "portrait-primary",
"theme_color": "#4f46e5",
"background_color": "#ffffff",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
],
"screenshots": [
{ "src": "/screenshots/screen1.png", "sizes": "1280x720", "type": "image/png", "form_factor": "wide" }
]
}<!-- Link in <head> -->
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#4f46e5">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="apple-touch-icon" href="/icons/icon-180.png">| Mode | Behavior |
|---|---|
standalone | Runs like a native app (no browser UI) |
fullscreen | No browser chrome, no OS bar |
minimal-ui | Minimal browser controls |
browser | Regular browser tab |
1. Register β browser downloads SW file
2. Install β SW activates, `install` event fires, cache pre-populated
3. Activate β old SW controlled pages until they reload, `activate` event fires
4. Fetch β SW intercepts all network requests
5. Update β browser checks for new SW in background// main.js β register the service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('SW registered:', registration.scope);
// Listen for updates
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
showUpdateBanner(); // prompt user to refresh
}
});
});
} catch (error) {
console.error('SW registration failed:', error);
}
});
}// sw.js
const CACHE_NAME = 'app-v1';
const STATIC_ASSETS = ['/', '/index.html', '/app.css', '/app.js'];
// Install β pre-cache static assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS))
);
self.skipWaiting(); // activate immediately
});
// Activate β clean up old caches
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)))
)
);
self.clients.claim();
});self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cached =>
cached ?? fetch(event.request).then(response => {
const clone = response.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
return response;
})
)
);
});self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
const clone = response.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
return response;
})
.catch(() => caches.match(event.request)) // fallback to cache offline
);
});| Strategy | Network | Cache | Best For |
|---|---|---|---|
| Cache First | Fallback | Primary | Static assets (CSS, JS, images) |
| Network First | Primary | Fallback | API data, dynamic content |
| Stale While Revalidate | Background update | Serve immediately | Frequently updated resources |
| Cache Only | Never | Always | Offline-only resources |
| Network Only | Always | Never | Real-time data (payments) |
// Request permission
const permission = await Notification.requestPermission();
if (permission === 'granted') {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
});
// Send subscription to your server
}
// sw.js β handle push event
self.addEventListener('push', event => {
const data = event.data?.json() ?? {};
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192.png',
data: { url: data.url }
})
);
});
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification.data.url));
});let deferredPrompt;
window.addEventListener('beforeinstallprompt', event => {
event.preventDefault();
deferredPrompt = event;
showInstallButton(); // show your custom install button
});
async function promptInstall() {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(`User ${outcome === 'accepted' ? 'installed' : 'dismissed'}`);
deferredPrompt = null;
}// Simplifies service worker patterns significantly
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
// Images: cache first, expire after 30 days
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [new ExpirationPlugin({ maxAgeSeconds: 30 * 24 * 60 * 60 })]
})
);
// API: network first
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({ cacheName: 'api-cache' })
);/sw.js)Advertisement
Get more notes like this daily on Telegram!
Free study notes, cheat sheets & AI tips
Last reviewed on June 13, 2026 by the AiTechWorlds Notes Team. Free cheat sheet β no signup required.
Advertisement
Join AiTechWorlds on Telegram and get daily AI tips, prompt engineering templates, coding resources, and exclusive content β 100% free!
No spam. Leave anytime.