Introduction
Progressive Web Apps (PWAs) combine the best of web and native apps, offering offline support, push notifications, and home screen installation.
This guide covers PWA development for modern web applications.
PWA Essentials
Web App Manifest
{
"name": "My PWA",
"short_name": "MyApp",
"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"
}
]
}
<link rel="manifest" href="/manifest.json">
Service Workers
Registration
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('SW registered:', registration.scope);
});
}
Service Worker Basics
// sw.js
const CACHE_NAME = 'my-cache-v1';
const ASSETS = [
'/',
'/index.html',
'/styles.css',
'/app.js'
];
// Install - cache assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(ASSETS))
);
});
// Fetch - serve from cache, fallback to network
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});
// Activate - clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
)
)
);
});
Offline Strategies
Cache-First
self.addEventListener('fetch', (event) => {
if (event.request.destination === 'image') {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request).then(fetchResponse => {
return caches.open('images').then(cache => {
cache.put(event.request, fetchResponse.clone());
return fetchResponse;
});
});
})
);
}
});
Network-First
async function networkFirst(request) {
try {
const response = await fetch(request);
return response;
} catch {
return caches.match(request);
}
}
Push Notifications
Notification Permission
async function requestNotificationPermission() {
if (!('Notification' in window)) {
console.log('Notifications not supported');
return;
}
const permission = await Notification.requestPermission();
console.log('Permission:', permission);
}
Push Subscription
async function subscribeToPush() {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
});
await fetch('/api/push/subscribe', {
method: 'POST',
body: JSON.stringify(subscription)
});
}
Install Prompt
Custom Install Banner
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault();
deferredPrompt = event;
});
async function installApp() {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
deferredPrompt = null;
}
}
Conclusion
PWAs provide native-like experiences on the web. Use service workers for offline support, manifests for installation, and push for engagement.
Comments