Modern HTML
Progressive Web Apps (PWA)
Transform websites into app-like experiences with Progressive Web Apps. Learn manifest files, service workers, offline functionality, and installable web applications.
What is a PWA?
Progressive Web Apps are websites that use modern web capabilities to deliver an app-like experience. They work offline, can be installed, and provide native-like features.
📱 Installable
Add to home screen like native apps
📴 Offline
Work without internet connection
🔔 Push Notifications
Engage users with notifications
âš¡ Fast
Instant loading and smooth performance
PWA Requirements
Core Requirements:
- ✅ HTTPS (secure connection)
- ✅ Web App Manifest (manifest.json)
- ✅ Service Worker (for offline support)
- ✅ Responsive design
- ✅ App icons (multiple sizes)
Web App Manifest
manifest.json
JSON
{
"name": "FileFusion - Online File Tools",
"short_name": "FileFusion",
"description": "Free online tools for file conversion and editing",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#4CAF50",
"orientation": "portrait-primary",
"scope": "/",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"categories": ["productivity", "utilities"],
"shortcuts": [
{
"name": "Image Compressor",
"short_name": "Compress",
"description": "Compress images online",
"url": "/tools/image-compressor",
"icons": [{ "src": "/icons/compress.png", "sizes": "96x96" }]
},
{
"name": "PDF Converter",
"short_name": "PDF",
"description": "Convert files to PDF",
"url": "/tools/pdf-converter",
"icons": [{ "src": "/icons/pdf.png", "sizes": "96x96" }]
}
],
"screenshots": [
{
"src": "/screenshots/home.jpg",
"sizes": "1280x720",
"type": "image/jpeg"
}
]
}Link Manifest in HTML
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Link to manifest -->
<link rel="manifest" href="/manifest.json">
<!-- Theme color for browser UI -->
<meta name="theme-color" content="#4CAF50">
<!-- Apple-specific meta tags -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="FileFusion">
<link rel="apple-touch-icon" href="/icons/icon-180x180.png">
<!-- Microsoft-specific -->
<meta name="msapplication-TileColor" content="#4CAF50">
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png">
<title>FileFusion</title>
</head>
<body>
<!-- Content -->
</body>
</html>Service Worker
Service workers enable offline functionality, caching, and background sync.
Register Service Worker
HTML
<!-- In your HTML -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker registered:', registration);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
});
}
</script>Service Worker File (sw.js)
JAVASCRIPT
// sw.js
const CACHE_NAME = 'filefusion-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/icons/icon-192x192.png',
'/offline.html'
];
// Install event - cache files
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// Activate event - clean up 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, fallback to network
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Clone request - can only be used once
const fetchRequest = event.request.clone();
return fetch(fetchRequest).then(response => {
// Check if valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone response - can only be used once
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
}).catch(() => {
// Network failed, show offline page
return caches.match('/offline.html');
});
})
);
});Caching Strategies
1. Cache First (Offline-First)
JAVASCRIPT
// Good for: Static assets, fonts, images
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});2. Network First
JAVASCRIPT
// Good for: API calls, dynamic content
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
// Cache for future
const cache = caches.open(CACHE_NAME);
cache.then(c => c.put(event.request, response.clone()));
return response;
})
.catch(() => caches.match(event.request))
);
});3. Stale-While-Revalidate
JAVASCRIPT
// Good for: Frequently updated content
self.addEventListener('fetch', event => {
event.respondWith(
caches.open(CACHE_NAME).then(cache => {
return cache.match(event.request).then(cached => {
const fetchPromise = fetch(event.request).then(response => {
cache.put(event.request, response.clone());
return response;
});
return cached || fetchPromise;
});
})
);
});Install Prompt
JAVASCRIPT
let deferredPrompt;
// Listen for install prompt
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent default browser install prompt
e.preventDefault();
// Store event for later
deferredPrompt = e;
// Show custom install button
const installBtn = document.getElementById('install-btn');
installBtn.style.display = 'block';
installBtn.addEventListener('click', () => {
// Hide button
installBtn.style.display = 'none';
// Show install prompt
deferredPrompt.prompt();
// Wait for user response
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted install');
} else {
console.log('User dismissed install');
}
deferredPrompt = null;
});
});
});
// Detect if app is installed
window.addEventListener('appinstalled', () => {
console.log('PWA was installed');
// Hide install button
document.getElementById('install-btn').style.display = 'none';
});Push Notifications
Request Notification Permission
JAVASCRIPT
// Request permission
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
console.log('Notification permission granted');
// Show notification
new Notification('Hello!', {
body: 'This is a notification from your PWA',
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png',
vibrate: [200, 100, 200]
});
}
});
// Or with async/await
async function enableNotifications() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
// Subscribe to push notifications
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: 'YOUR_PUBLIC_KEY'
});
// Send subscription to server
await fetch('/api/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: { 'Content-Type': 'application/json' }
});
}
}Handle Push Events in Service Worker
JAVASCRIPT
// sw.js
self.addEventListener('push', event => {
const data = event.data.json();
const options = {
body: data.body,
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png',
vibrate: [200, 100, 200],
data: {
url: data.url
}
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
// Handle notification click
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
});Offline Page
HTML
<!-- offline.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Offline - FileFusion</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-align: center;
padding: 20px;
}
.offline-content {
max-width: 500px;
}
h1 {
font-size: 3rem;
margin: 0 0 20px 0;
}
p {
font-size: 1.2rem;
margin: 0 0 30px 0;
}
button {
padding: 15px 30px;
font-size: 1rem;
background: white;
color: #667eea;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
}
button:hover {
transform: scale(1.05);
}
</style>
</head>
<body>
<div class="offline-content">
<h1>📴</h1>
<h2>You're Offline</h2>
<p>It looks like you've lost your internet connection. Check your connection and try again.</p>
<button onclick="location.reload()">Try Again</button>
</div>
</body>
</html>Testing Your PWA
Chrome DevTools
- Open DevTools (F12)
- Go to Application tab
- Check Manifest (view manifest details)
- Check Service Workers (view registered workers)
- Check Storage (view cached files)
- Test offline: Check "Offline" box in Service Workers
Lighthouse PWA Audit
- Open DevTools → Lighthouse tab
- Select "Progressive Web App"
- Click "Generate report"
- Fix any issues
- Aim for 100/100 score
PWA Checklist
Essential
- ☑ Served over HTTPS
- ☑ manifest.json configured
- ☑ Service worker registered
- ☑ Works offline
- ☑ Responsive on all devices
- ☑ App icons (all sizes)
- ☑ Fast loading (< 3s)
Recommended
- ☑ Install prompt
- ☑ Push notifications
- ☑ Offline page
- ☑ App shortcuts
- ☑ Share target
- ☑ Background sync
Testing
- ☑ Test on real devices
- ☑ Test offline functionality
- ☑ Test install flow
- ☑ Run Lighthouse audit
- ☑ Test push notifications
Browser Support
- ✅ Chrome/Edge (full support)
- ✅ Firefox (full support)
- ✅ Safari 11.1+ (partial support)
- âš ï¸ iOS Safari (limited: no push notifications)
- ✅ Android browsers (full support)