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)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.
Join AiTechWorlds on Telegram and get daily AI tips, prompt engineering templates, coding resources, and exclusive content 100% free!
No spam. Leave anytime.