PWA - Progressive Web Apps — Build App-Like Experiences on the Web
Introduction: The Future of Web Applications
Progressive Web Apps (PWAs) bridge the gap between web applications and native apps, delivering the best of both worlds. PWAs work offline, can be installed on home screens, send push notifications, and run at native-like speeds. This comprehensive guide covers everything you need to build modern PWAs.
Table of Contents
- Introduction
- What is a PWA?
- PWA Core Requirements
- Service Workers
- Web Manifest
- Offline Functionality
- Push Notifications
- Installation and Distribution
- Performance Optimization
- Popular PWA Examples
- Debugging PWAs
- Best Practices
What is a PWA?
A Progressive Web App combines web and app features to deliver an enhanced user experience. PWAs are:
- Progressive: Work on all devices and browsers
- Responsive: Perfect fit for screen size
- Connectivity Independent: Work offline via service workers
- App-like: Navigation and interactions feel native
- Fresh: Always up-to-date thanks to service workers
- Safe: Served via HTTPS
- Discoverable: Identifiable as “application”
- Installable: Home screen installation without app store
- Linkable: Easy sharing via URL
Why PWAs Matter
PWAs solve critical challenges:
- Offline Support: No internet? No problem
- Reduced Data Usage: Cached content means less bandwidth
- Faster Loading: Cached resources load instantly
- Native-like Feel: Full-screen, home screen icon
- Push Notifications: Re-engagement capabilities
- No App Store: Direct distribution via web
PWA Core Requirements
1. HTTPS Security
All PWAs must be served over HTTPS. This ensures secure communication and enables service workers.
PWAs require HTTPS
Exceptions: localhost for development
Certificate: Can use Let's Encrypt (free)
Validation: Check browser console for HTTPS warnings
2. Service Worker Registration
Service workers act as a proxy between your app and the network, enabling offline functionality and caching.
// Register service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then(registration => {
console.log('Service Worker registered:', registration);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
}
3. Web App Manifest
The manifest file tells the browser how to display your app when installed.
{
"name": "My Progressive Web App",
"short_name": "MyApp",
"description": "An amazing PWA example",
"start_url": "/",
"scope": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/images/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/images/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/images/icon-maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
}
]
}
Adding Manifest to HTML
Link the manifest file in your HTML head section.
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#000000">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon" href="/images/apple-touch-icon.png">
Service Workers
Service workers are the backbone of PWAs, running in the background and handling caching, offline support, and background sync.
Service Worker Lifecycle
Installation → Activation → Fetch/Message Handling → Termination
(cache setup) (cleanup old) (serve from cache) (if unused)
Implementing Service Worker Caching
// sw.js - Service Worker
const CACHE_NAME = 'app-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/css/styles.css',
'/js/app.js',
'/images/logo.png'
];
// Install event - cache assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// Activate event - cleanup old caches
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
// Fetch event - serve from cache, fall back to network
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request).then(response => {
// Check if valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone the response
const responseToCache = response.clone();
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
}).catch(() => {
// Network request failed, return offline page
return caches.match('/offline.html');
})
);
});
Strategies for Caching
Cache First (Cache, falling back to network)
- Best for: Assets that don’t change often
- Speed: Fast offline experience
- Use case: Images, CSS, JavaScript
Network First (Network, falling back to cache)
- Best for: Frequently updated content
- Speed: Always gets fresh data when available
- Use case: API responses, dynamic content
Stale While Revalidate
- Best for: Content that can be slightly out of date
- Speed: Fast initial load, background update
- Use case: Blog posts, news feeds
Web Manifest
The web manifest defines how your app appears when installed and how it should behave.
Display Modes
"display": "standalone" // Like a native app
"display": "fullscreen" // Full screen, no browser chrome
"display": "minimal-ui" // Minimal UI
"display": "browser" // Normal browser
Theme Customization
{
"theme_color": "#000000", // Browser UI color
"background_color": "#ffffff", // Background during load
"scope": "/app/", // PWA scope
"start_url": "/app/" // Launch URL
}
Offline Functionality
Offline Detection
// Detect online/offline status
window.addEventListener('online', () => {
console.log('User is online');
syncPendingData();
});
window.addEventListener('offline', () => {
console.log('User is offline');
showOfflineMessage();
});
// Check current status
if (navigator.onLine) {
console.log('Currently online');
} else {
console.log('Currently offline');
}
Local Storage and IndexedDB
Store data locally for offline access.
// IndexedDB for complex data
const dbRequest = indexedDB.open('myapp', 1);
dbRequest.onsuccess = event => {
const db = event.target.result;
const transaction = db.transaction(['items'], 'readwrite');
const store = transaction.objectStore('items');
store.put({ id: 1, title: 'Item 1' });
};
Push Notifications
Requesting Permission
// Request notification permission
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
console.log('Notification permission granted');
}
});
Sending Notifications
// Service Worker push handler
self.addEventListener('push', event => {
const options = {
body: event.data.text(),
icon: '/images/icon-192x192.png',
badge: '/images/badge-72x72.png',
tag: 'notification',
requireInteraction: false
};
event.waitUntil(
self.registration.showNotification('New Message', options)
);
});
// Handle notification clicks
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(
clients.matchAll({ type: 'window' }).then(clientList => {
for (let client of clientList) {
if (client.url === '/' && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow('/');
}
})
);
});
Installation and Distribution
Add to Home Screen
Users can install your PWA directly:
- Visit your PWA in browser
- Tap the install prompt (or menu → “Add to Home Screen”)
- App appears like native app
Web App Install Banners
Show custom install prompts.
let deferredPrompt;
window.addEventListener('beforeinstallprompt', event => {
event.preventDefault();
deferredPrompt = event;
// Show custom install button
document.getElementById('install-btn').style.display = 'block';
document.getElementById('install-btn').addEventListener('click', () => {
deferredPrompt.prompt();
deferredPrompt.userChoice.then(choice => {
if (choice.outcome === 'accepted') {
console.log('User installed PWA');
}
deferredPrompt = null;
});
});
});
window.addEventListener('appinstalled', () => {
console.log('PWA was installed');
});
Performance Optimization
Key Metrics for PWAs
- First Contentful Paint (FCP): < 1.8s
- Largest Contentful Paint (LCP): < 2.5s
- Cumulative Layout Shift (CLS): < 0.1
- Time to Interactive (TTI): < 3.8s
Performance Checklist
✓ Service worker precaches critical assets ✓ Images optimized and lazy-loaded ✓ Code splitting and bundling optimized ✓ Network requests minimized ✓ Offline experience tested ✓ Performance monitored in production
Popular PWA Examples
- Twitter Lite: 30% data reduction, fast loading
- Spotify: Full offline support, home screen
- Pinterest: 40% faster on 3G, home screen install
- Starbucks: Order ahead offline, sync on reconnect
- Washington Post: 1MB total size, fast load times
Debugging PWAs
Chrome DevTools
- Application Tab: View manifest, service workers, storage
- Service Workers: Check status, unregister, skip waiting
- Storage: Inspect cache, local storage, IndexedDB
- Offline Simulation: Test offline behavior
Testing Offline
// Network throttling test
// DevTools → Network → Throttling
// Check your app still works
Best Practices
✓ Always serve over HTTPS ✓ Provide offline fallback pages ✓ Optimize manifest icons for all sizes ✓ Test on real devices ✓ Monitor performance metrics ✓ Keep service workers updated ✓ Handle service worker errors gracefully ✓ Provide clear uninstall options
PWAs represent the future of web delivery!
Last updated: January 8, 2026
What is a PWA?
A Progressive Web App is a web application that:
- Works offline seamlessly
- Is installable on home screen
- Provides app-like experience
- Is fast and reliable
Requirements
1. HTTPS
All PWAs must be served over HTTPS
2. Service Worker
// Register service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
3. Web Manifest
{
"name": "My App",
"short_name": "App",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "/",
"display": "standalone"
}
Service Worker Caching
self.addEventListener('install', event => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/styles.css',
'/app.js'
]);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});
Features
✓ Offline support without network ✓ Home screen installation ✓ Push notifications ✓ Background sync ✓ App shell architecture
Popular PWAs
- Twitter Lite
- Spotify
- Starbucks
PWAs are the future of web development!