Progressive Web Apps (PWAs) machen aus deiner Website eine installierbare App, mit Offline-Support, Push Notifications und ohne App Store. Alles, was du brauchst, sind ein Web App Manifest, ein Service Worker und HTTPS.
Stell dir vor, du baust eine Website und die verhält sich plötzlich wie eine native App. Deine User können sie auf dem Homescreen installieren, sie funktioniert offline und kann sogar Push Notifications schicken. Klingt nach viel Aufwand? Ist es nicht. Willkommen in der Welt der Progressive Web Apps.
Was sind Progressive Web Apps eigentlich? 🤔
Eine PWA ist im Grunde eine ganz normale Website, die ein paar Extra-Fähigkeiten mitbringt. Google hat den Begriff 2015 geprägt, aber die Idee dahinter ist simpel: Webseiten sollen sich so anfühlen wie native Apps.
Die drei Grundpfeiler einer PWA sind:
- Reliable, Sie lädt auch bei schlechter oder fehlender Netzwerkverbindung
- Fast, Sie reagiert schnell auf User-Interaktionen
- Engaging, Sie fühlt sich an wie eine native App (installierbar, Vollbild, Push Notifications)
Das Beste daran: Du brauchst keinen App Store, keine separaten Codebases für iOS und Android und kein Swift oder Kotlin zu lernen. Dein vorhandenes Web-Know-how reicht völlig aus.

Das Web App Manifest 📋
Das Manifest ist eine JSON-Datei, die dem Browser sagt, wie sich deine App verhalten soll, wenn sie installiert wird. Hier ein vollständiges Beispiel:
{
"name": "Meine Awesome PWA",
"short_name": "AwesomePWA",
"description": "Eine richtig coole Progressive Web App",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2196F3",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}Binde das Manifest in deinem HTML ein:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#2196F3">Kurz zu den wichtigsten Feldern:
display: "standalone", Die App läuft ohne Browser-UI, fühlt sich also wie eine echte App anstart_url, Welche Seite beim App-Start geladen wirdicons, Brauchst du mindestens in 192x192 und 512x512. Dasmaskable-Icon sorgt dafür, dass es auf Android korrekt zugeschnitten wirdtheme_color, Färbt die Statusbar auf mobilen Geräten ein
Service Worker, das Herzstück 💙
Der Service Worker ist ein JavaScript-File, das im Hintergrund läuft, unabhängig von deiner Website. Er sitzt quasi als Proxy zwischen Browser und Netzwerk und kann Requests abfangen, cachen und sogar beantworten, wenn du offline bist.
Zuerst musst du den Service Worker registrieren:
// In deiner Haupt-JS-Datei
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('SW registriert:', registration.scope);
} catch (error) {
console.log('SW Registrierung fehlgeschlagen:', error);
}
});
}Und hier der Service Worker selbst mit einer Cache-First-Strategie:
// sw.js
const CACHE_NAME = 'pwa-cache-v1';
const ASSETS_TO_CACHE = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/icons/icon-192x192.png',
'/offline.html'
];
// Install-Event: Assets vorab cachen
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(ASSETS_TO_CACHE))
.then(() => self.skipWaiting())
);
});
// Activate-Event: Alte Caches aufräumen
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
}).then(() => self.clients.claim())
);
});
// Fetch-Event: Cache-First-Strategie
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(event.request).then((response) => {
// Nur erfolgreiche Responses cachen
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then((cache) => cache.put(event.request, responseToCache));
return response;
});
})
.catch(() => {
// Offline-Fallback für Navigations-Requests
if (event.request.mode === 'navigate') {
return caches.match('/offline.html');
}
})
);
});Offline-Fähigkeit, das Killer-Feature 🔌
Offline-Fähigkeit ist das, was eine PWA wirklich von einer normalen Website unterscheidet. Mit dem Service Worker oben hast du schon die Basics. Aber es gibt verschiedene Caching-Strategien, je nachdem was du brauchst:
| Strategie | Beschreibung | Gut für |
|---|---|---|
| Cache First | Schau zuerst in den Cache, dann ins Netz | Statische Assets (CSS, JS, Bilder) |
| Network First | Versuch's zuerst übers Netz, Fallback auf Cache | API-Daten, dynamische Inhalte |
| Stale While Revalidate | Liefere aus dem Cache, aktualisiere im Hintergrund | Häufig aktualisierte Inhalte |
| Cache Only | Nur aus dem Cache | Rein statische Inhalte |
| Network Only | Nur übers Netzwerk | Analytics, nicht-cachbare Requests |
Eine nette Offline-Seite ist auch schnell gebaut. Erstell einfach eine offline.html, die im Install-Event gecacht wird, und zeig sie als Fallback an, wenn die Navigation fehlschlägt.
Der Install Prompt 📲
Wenn deine PWA die Kriterien erfüllt (Manifest, Service Worker, HTTPS), zeigt der Browser automatisch einen Install-Prompt an. Du kannst das Event aber auch abfangen und zu einem besseren Zeitpunkt auslösen:
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
// Verhindere den automatischen Prompt
e.preventDefault();
deferredPrompt = e;
// Zeige deinen eigenen Install-Button
const installBtn = document.getElementById('install-button');
installBtn.style.display = 'block';
installBtn.addEventListener('click', async () => {
installBtn.style.display = 'none';
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(outcome === 'accepted' ? 'App installiert!' : 'Installation abgelehnt');
deferredPrompt = null;
});
});
// Erkennen, ob die App bereits installiert ist
window.addEventListener('appinstalled', () => {
console.log('PWA wurde installiert');
deferredPrompt = null;
});Push Notifications 🔔
Push Notifications sind das Feature, das PWAs wirklich "app-like" macht. Der Flow sieht so aus:
- User erlaubt Notifications
- Browser gibt dir ein Push-Subscription-Objekt
- Dein Server schickt Push-Nachrichten über den Push-Service
- Der Service Worker empfängt und zeigt sie an
// Permission anfragen und Subscription erstellen
async function subscribeToPush() {
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
console.log('Notification permission denied');
return;
}
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('DEIN_VAPID_PUBLIC_KEY')
});
// Subscription an deinen Server schicken
await fetch('/api/push/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: { 'Content-Type': 'application/json' }
});
}
// Helper: Base64 zu Uint8Array
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = atob(base64);
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}Und im Service Worker empfängst du die Push-Nachricht:
// In sw.js
self.addEventListener('push', (event) => {
const data = event.data ? event.data.json() : {};
const options = {
body: data.body || 'Neue Benachrichtigung',
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png',
vibrate: [100, 50, 100],
data: { url: data.url || '/' }
};
event.waitUntil(
self.registration.showNotification(data.title || 'PWA Update', options)
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
});PWA vs. Native App, wann was? ⚖️
Die ehrliche Antwort: Es kommt drauf an. Hier eine Orientierung:
PWA ist die richtige Wahl, wenn:
- Du eine breite Reichweite willst (jeder mit einem Browser kann sie nutzen)
- Dein Budget begrenzt ist (eine Codebase für alle Plattformen)
- Schnelle Iteration wichtig ist (kein App-Store-Review-Prozess)
- Deine App hauptsächlich Content darstellt
- Du SEO brauchst (PWAs sind indexierbar)
Native ist besser, wenn:
- Du tiefe Hardware-Integration brauchst (Bluetooth, NFC, AR)
- Maximale Performance nötig ist (Spiele, Video-Editing)
- Du auf bestimmte native APIs angewiesen bist, die im Web nicht verfügbar sind
- App Store Presence ein Muss ist (wobei PWAs mittlerweile auch dort gelistet werden können)
Fun Fact: Twitter Lite als PWA hat die Engagement-Rate um 65% gesteigert und den Datenverbrauch um 70% gesenkt. Starbucks' PWA ist 99,84% kleiner als ihre iOS-App. Das sind keine Spielereien.
Fazit 🎯
PWAs sind kein Hype mehr, sie sind eine ernsthafte Alternative zu nativen Apps für viele Use Cases. Mit ein paar Zeilen Code machst du aus jeder Website eine installierbare, offline-fähige App. Die Web-Plattform wird immer mächtiger, und die Lücke zu nativen Apps wird jedes Jahr kleiner.
Mein Tipp: Fang klein an. Füg ein Manifest und einen simplen Service Worker zu deinem nächsten Projekt hinzu. Du wirst überrascht sein, wie wenig Aufwand es braucht und wie viel es bringt.
Happy Coding! 🚀
