Progressive Web Apps (PWAs) combine the best of web and native applications. Let's explore how to build PWAs that truly stand out in 2025. 🚀
Core PWA Components 🎯
Web App Manifest
The manifest.json file defines how your app appears when installed:
{
"name": "SuperApp PWA",
"short_name": "SuperApp",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2563eb",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Service Worker Registration
// register-sw.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('ServiceWorker registered:', registration.scope);
} catch (error) {
console.error('ServiceWorker registration failed:', error);
}
});
}
Advanced Service Worker Features 🛠️
Caching Strategies
// sw.js
const CACHE_NAME = 'superapp-v1';
const ASSETS = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png'
];
// Cache-first strategy
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(event.request)
.then(response => {
if (!response || response.status !== 200) {
return response;
}
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
Background Sync
// background-sync.js
class BackgroundSync {
async registerSync(data) {
if ('serviceWorker' in navigator && 'SyncManager' in window) {
const registration = await navigator.serviceWorker.ready;
try {
await registration.sync.register('sync-data');
localStorage.setItem('pending-sync', JSON.stringify(data));
return true;
} catch (error) {
console.error('Background sync registration failed:', error);
return false;
}
}
return false;
}
}
Push Notifications 📱
Notification Permission
// notifications.js
class NotificationManager {
async requestPermission() {
if (!('Notification' in window)) {
console.log('This browser does not support notifications');
return false;
}
try {
const permission = await Notification.requestPermission();
return permission === 'granted';
} catch (error) {
console.error('Error requesting notification permission:', error);
return false;
}
}
async subscribeToPush() {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: this.urlBase64ToUint8Array(PUBLIC_VAPID_KEY)
});
return subscription;
}
}
App Shell Architecture 🏗️
Shell Component
// app-shell.js
class AppShell extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
min-height: 100vh;
}
header {
position: sticky;
top: 0;
background: var(--primary-color);
color: white;
}
main {
flex: 1;
padding: 1rem;
}
</style>
<header>
<nav><!-- Navigation content --></nav>
</header>
<main>
<slot></slot>
</main>
<footer>
<!-- Footer content -->
</footer>
`;
}
}
customElements.define('app-shell', AppShell);
Offline Experience 🌐
Offline Data Storage
// indexeddb-store.js
class OfflineStore {
constructor() {
this.dbName = 'SuperAppDB';
this.version = 1;
}
async initDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('articles')) {
db.createObjectStore('articles', { keyPath: 'id' });
}
};
});
}
async saveArticle(article) {
const db = await this.initDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(['articles'], 'readwrite');
const store = transaction.objectStore('articles');
const request = store.put(article);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
}
Performance Optimization 🚀
Lazy Loading
// lazy-loading.js
class LazyLoader {
constructor() {
this.observer = new IntersectionObserver(this.handleIntersection);
}
observe(elements) {
elements.forEach(element => {
if (element.dataset.src) {
this.observer.observe(element);
}
});
}
handleIntersection(entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const element = entry.target;
element.src = element.dataset.src;
element.removeAttribute('data-src');
observer.unobserve(element);
}
});
}
}
Installation Experience 📥
Install Prompt
// install-prompt.js
class InstallPrompt {
constructor() {
this.deferredPrompt = null;
this.installButton = document.querySelector('#installButton');
this.init();
}
init() {
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
this.deferredPrompt = e;
this.showInstallButton();
});
this.installButton?.addEventListener('click', () => this.promptInstall());
}
async promptInstall() {
if (!this.deferredPrompt) return;
this.deferredPrompt.prompt();
const { outcome } = await this.deferredPrompt.userChoice;
if (outcome === 'accepted') {
console.log('PWA installed successfully');
}
this.deferredPrompt = null;
this.hideInstallButton();
}
}
Analytics and Monitoring 📊
PWA Analytics
// pwa-analytics.js
class PWAAnalytics {
trackInstallation() {
window.addEventListener('appinstalled', () => {
this.sendAnalytics('PWA', 'install', 'app-installed');
});
}
trackOfflineUsage() {
window.addEventListener('online', () => {
this.sendAnalytics('PWA', 'connectivity', 'online');
});
window.addEventListener('offline', () => {
this.sendAnalytics('PWA', 'connectivity', 'offline');
});
}
sendAnalytics(category, action, label) {
// Implement your analytics logic here
console.log(`Analytics: ${category} - ${action} - ${label}`);
}
}
Conclusion
Building a standout PWA requires attention to detail and implementation of advanced features that enhance user experience. Focus on:
- Reliable offline functionality
- Fast load times and performance
- Engaging push notifications
- Smooth installation process
- Robust caching strategies
Remember that a great PWA should feel natural and seamless, regardless of network conditions or device capabilities. Keep testing and iterating to ensure your PWA provides the best possible experience for your users. 🎉