HTML5 Mastery: The Complete Web Foundation
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 --&gt;
    <link rel="manifest" href="/manifest.json">
    
    <!-- Theme color for browser UI --&gt;
    <meta name="theme-color" content="#4CAF50">
    
    <!-- Apple-specific meta tags --&gt;
    <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 --&gt;
    <meta name="msapplication-TileColor" content="#4CAF50">
    <meta name="msapplication-TileImage" content="/icons/icon-144x144.png">
    
    <title>FileFusion</title>
</head>
<body>
    <!-- Content --&gt;
</body>
</html>

Service Worker

Service workers enable offline functionality, caching, and background sync.

Register Service Worker

HTML
<!-- In your HTML --&gt;
<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 --&gt;
<!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

  1. Open DevTools (F12)
  2. Go to Application tab
  3. Check Manifest (view manifest details)
  4. Check Service Workers (view registered workers)
  5. Check Storage (view cached files)
  6. Test offline: Check "Offline" box in Service Workers

Lighthouse PWA Audit

  1. Open DevTools → Lighthouse tab
  2. Select "Progressive Web App"
  3. Click "Generate report"
  4. Fix any issues
  5. 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)

What's Next?

Explore the future of HTML and emerging web technologies.