PWA — Progressively Enhanced Apps
Progressive Web Apps (PWAs) combine the best of web and native apps. They work offline, load quickly, can be installed on the home screen, and send push notifications — all without an app store. This guide walks through building a PWA from scratch.
What Makes a PWA
A PWA must meet three core criteria:
- Secure: Served over HTTPS
- Installable: Has a web app manifest and meets browser requirements
- Offline-capable: Uses a service worker to cache resources
These features are progressive, meaning they enhance the experience for supporting browsers while degrading gracefully for older ones.
The Web App Manifest
The manifest is a JSON file that tells the browser how your app should behave when installed:
{
"name": "My PWA App",
"short_name": "MyPWA",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2563eb",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Link it in your HTML:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#2563eb">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
Service Worker Basics
The service worker is a JavaScript file that runs in the background, separate from the web page. It handles caching, push notifications, and background sync.
const CACHE_NAME = 'pwa-v1';
const ASSETS_TO_CACHE = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/icons/icon-192.png',
'/icons/icon-512.png'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS_TO_CACHE))
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys
.filter((key) => key !== CACHE_NAME)
.map((key) => caches.delete(key))
)
)
);
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).then((response) => {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, clone);
});
return response;
});
})
);
});
Register the service worker in your main JavaScript:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('SW registered:', registration.scope);
})
.catch((error) => {
console.log('SW registration failed:', error);
});
});
}
Offline Experience
A great PWA doesn't just work offline — it tells the user when it does:
window.addEventListener('offline', () => {
document.getElementById('offline-banner').style.display = 'block';
});
window.addEventListener('online', () => {
document.getElementById('offline-banner').style.display = 'none';
window.location.reload();
});
<div id="offline-banner" style="display:none; background:#f59e0b; color:#000; text-align:center; padding:0.5rem;">
You are offline. Some features may be limited.
</div>
Push Notifications
Enable real-time engagement with push notifications:
async function requestNotificationPermission() {
if ('Notification' in window && Notification.permission === 'default') {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
const subscription = await navigator.serviceWorker.ready
.then(reg => reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
}));
console.log('Subscription:', subscription);
}
}
}
self.addEventListener('push', (event) => {
const data = event.data?.json() || { title: 'Update', body: 'New content available' };
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192.png',
badge: '/icons/icon-192.png'
})
);
});
Conclusion
PWAs bridge the gap between web and native apps, offering installability, offline support, and push notifications without the friction of app stores. Start with the manifest and a basic service worker, then gradually add features like background sync and push notifications. Test with Lighthouse's PWA audit to ensure you're hitting all the criteria. The return on investment is significant — users love the app-like experience, and you maintain the reach and discoverability of the open web.
The dispatchOne sharp read on the day’s biggest tech story.
Reported analysis for people who build software — free, most days, no spam.