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)