Progressive Web Apps (PWAs)

Eine Progressive Web App (PWA) ist eine Webanwendung, die moderne Browserfunktionen nutzt, um ein App-ähnliches Erlebnis zu bieten. PWAs sind zuverlässig, schnell und ansprechend – sie kombinieren die Reichweite des Webs mit den Funktionen nativer mobiler Anwendungen.
Einführung
Eine Progressive Web App (PWA) ist eine Webanwendung, die moderne Browserfunktionen nutzt, um ein App-ähnliches Erlebnis zu bieten. PWAs sind zuverlässig, schnell und ansprechend – sie kombinieren die Reichweite des Webs mit den Funktionen nativer mobiler Anwendungen.
PWAs lösen ein grundlegendes Problem: Benutzer möchten die sofortige Verfügbarkeit des Webs (keine Installation, verlinkbar, immer aktuell), erwarten aber die Leistung und Funktionen nativer Apps (Offline-Unterstützung, Push-Benachrichtigungen, Präsenz auf dem Startbildschirm). PWAs bieten beides.
Kernmerkmale
Der Begriff „Progressive Web App“ wurde 2015 von Alex Russell und Frances Berriman von Google geprägt. Eine PWA muss diese 10 Merkmale erfüllen:
| # | Prinzip | Warum es wichtig ist |
|---|---|---|
| 1 | Progressiv – Funktioniert für jeden | Anmutige Verschlechterung bei älteren Browsern |
| 2 | Responsiv – Passt auf jeden Bildschirm | Desktop, Tablet, Mobil |
| 3 | Konnektivitätsunabhängig – Funktioniert offline | Caching von Servicemitarbeitern |
| 4 | App-ähnlich – Natives App-Feeling | Shell-Architektur, reibungslose Navigation |
| 5 | Frisch – Immer aktuell | Service-Worker-Update-Lebenszyklus |
| 6 | Sicher – Wird über HTTPS bereitgestellt | Verhindert Manipulationen, erforderlich für Servicemitarbeiter |
| 7 | Auffindbar – SEO-freundlich | Suchmaschinen indizieren PWAs |
| 8 | Wieder aktivierbar – Push-Benachrichtigungen | Binden Sie Benutzer wie native Apps erneut ein |
| 9 | Installierbar – Zum Startbildschirm hinzufügen | Web-App-Manifest |
| 10 | Verlinkbar – Über URL teilen | Kein App-Store erforderlich |
Die drei Säulen von PWA
1. Servicemitarbeiter – Der Motor
Ein Service Worker ist eine JavaScript-Datei, die getrennt von der Webseite im Hintergrund ausgeführt wird. Es fungiert als programmierbarer Netzwerk-Proxy, der Anfragen abfängt und Offline-Funktionalität, Push-Benachrichtigungen und Hintergrundsynchronisierung ermöglicht.
Lebenszyklus von Servicemitarbeitern
Installing → Installed → Activating → Activated → Idle → Terminated
│ │
└─── (new version detected) ──────────────┘
└─── Waiting (until all tabs close) ─────┘
// service-worker.js
const CACHE_NAME = 'my-pwa-v2';
const ASSETS_TO_CACHE = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/offline.html',
'/icons/icon-192.png'
];
// Install event — cache static assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
console.log('Caching app shell');
return cache.addAll(ASSETS_TO_CACHE);
})
);
// Force new service worker to activate immediately
self.skipWaiting();
});
// Activate event — clean old caches
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(name => name !== CACHE_NAME)
.map(name => caches.delete(name))
);
})
);
// Take control of all open pages immediately
clients.claim();
});
// Fetch event — intercept network requests
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
// Return cached response if available
if (cachedResponse) {
return cachedResponse;
}
// Otherwise fetch from network
return fetch(event.request).then(networkResponse => {
// Don't cache non-GET or error responses
if (!event.request.url.includes('/api/')) {
return networkResponse;
}
// Cache API responses for offline use
const responseClone = networkResponse.clone();
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, responseClone);
});
return networkResponse;
}).catch(() => {
// Network failed — return offline fallback
if (event.request.mode === 'navigate') {
return caches.match('/offline.html');
}
return new Response('Offline', { status: 503 });
});
})
);
});
Caching-Strategien
| Strategie | Beschreibung | Anwendungsfall |
|---|---|---|
| Zuerst zwischenspeichern | Überprüfen Sie den Cache und greifen Sie auf das Netzwerk zurück | Statische Assets (CSS, JS, Bilder) |
| Netzwerk zuerst | Versuchen Sie es mit dem Netzwerk und greifen Sie auf den Cache zurück | API-Aufrufe, dynamische Inhalte |
| Veraltet während der erneuten Validierung | Cache zurückgeben, im Hintergrund aktualisieren | Newsfeeds, Blogbeiträge |
| Nur Netzwerk | Immer aus dem Netzwerk abrufen | Sensible Daten (Banking) |
| Nur Cache | Niemals aus dem Netzwerk abrufen | App-Shell |
// Stale-while-revalidate strategy
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
const fetchPromise = fetch(event.request).then(networkResponse => {
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
}).catch(() => cachedResponse);
return cachedResponse || fetchPromise;
})
);
});
2. Web-App-Manifest – Die Identität
Das Manifest ist eine JSON-Datei, die steuert, wie die PWA bei der Installation angezeigt wird:
{
"name": "My Progressive Web App",
"short_name": "MyPWA",
"description": "An amazing PWA that works offline",
"start_url": "/?source=pwa",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#3367D6",
"orientation": "portrait-primary",
"scope": "/",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"categories": ["productivity", "utilities"],
"lang": "en-US",
"dir": "ltr",
"prefer_related_applications": false
}
Manifeste Eigenschaften erklärt:
- „display: standalone“ – Öffnet ohne Browser-Benutzeroberfläche (Adressleiste, Registerkarten).
- „display: fullscreen“ – Vollbild, überhaupt kein Browser-Chrome.
- „display: minimal-ui“ – Minimale Browser-Steuerelemente.
scope: „/“– Welche URLs sind Teil der PWA.- „start_url“ – Die Seite, die geöffnet wird, wenn die PWA gestartet wird.
- Maskierbare Symbole – Adaptive Symbole, die mit Maskierung auf Betriebssystemebene funktionieren (abgerundet, kreisförmig usw.).
3. HTTPS – Die Stiftung
Servicemitarbeiter benötigen einen sicheren Kontext. HTTPS ist obligatorisch für:
- Registrierung von Servicemitarbeitern.
- Push-Benachrichtigungen.
- Die Installationsaufforderung (vor dem Unload-Ereignis).
# Redirect HTTP to HTTPS
# Nginx
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
Aufbau einer PWA – Schritt für Schritt
1. Erstellen Sie die App Shell
Die App-Shell besteht aus dem minimalen HTML-, CSS- und JavaScript-Code, der zum Rendern der Benutzeroberfläche erforderlich ist. Es wird beim ersten Laden zwischengespeichert und dient als Grundlage der PWA.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#3367D6">
<link rel="manifest" href="/manifest.json">
<link rel="icon" href="/icons/icon-192.png" sizes="192x192">
<link rel="apple-touch-icon" href="/icons/icon-192.png">
<title>My PWA</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<header id="app-header">My PWA</header>
<main id="app-content"></main>
<footer id="app-footer">© 2026</footer>
<!-- Register service worker -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(reg => console.log('SW registered:', reg.scope))
.catch(err => console.error('SW registration failed:', err));
});
}
</script>
<script src="/app.js"></script>
</body>
</html>
2. Implementieren Sie den Offline-Support
// app.js — Fetch content with offline support
const CACHE_KEY = 'content-cache';
async function loadContent(url) {
try {
const response = await fetch(url);
const data = await response.json();
// Cache for offline
const cache = await caches.open(CACHE_KEY);
cache.put(url, new Response(JSON.stringify(data)));
renderContent(data);
} catch (error) {
// Offline — load from cache
const cached = await caches.match(url);
if (cached) {
const data = await cached.json();
renderContent(data);
showOfflineIndicator();
} else {
showOfflineMessage();
}
}
}
3. Fügen Sie Push-Benachrichtigungen hinzu
// Request permission
async function requestNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
// Get push subscription
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey)
});
// Send subscription to server
await fetch('/api/push/subscribe', {
method: 'POST',
body: JSON.stringify(subscription)
});
}
}
// service-worker.js — Handle push events
self.addEventListener('push', event => {
const data = event.data.json();
const options = {
body: data.body,
icon: '/icons/icon-192.png',
badge: '/icons/badge-72.png',
vibrate: [100, 50, 100],
data: { url: data.url },
actions: [
{ action: 'open', title: 'Open' },
{ action: 'close', title: 'Dismiss' }
]
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
self.addEventListener('notificationclick', event => {
event.notification.close();
if (event.action === 'open' || !event.action) {
clients.openWindow(event.notification.data.url);
}
});
PWA vs. native App
| Funktion | PWA | Native App |
|---|---|---|
| Verteilung | URL (kein Shop erforderlich) | App Store, Google Play |
| Reibung einbauen | Ein Fingertipp (Browser-Eingabeaufforderung) | Herunterladen, installieren, Berechtigungen |
| Dateigröße | KB zu MB | 10 MB bis 500 MB+ |
| Aktualisierungen | Instant (Servicemitarbeiter) | Via App-Store-Rezension |
| Offline | Ja (Servicemitarbeiter) | Ja (eingebaut) |
| Push-Benachrichtigungen | Ja | Ja |
| Geräte-APIs | Kamera, GPS, Beschleunigungsmesser, Zahlung | Voller Zugriff auf alle Geräte-APIs |
| Bluetooth | Web-Bluetooth (begrenzt) | Vollständige Bluetooth-API |
| Hintergrundaufgaben | Hintergrundsynchronisierung (begrenzt) | Vollständige Hintergrundausführung |
| Leistung | Nahezu nativ (JavaScript/WebAssembly) | Native (kompilierter Code) |
| SEO | Indexierbar durch Suchmaschinen | Nicht indexierbar |
| Entwicklungskosten | Einzelne Codebasis | iOS + Android (oder plattformübergreifend) |
| Benutzerbindung | Niedriger (leichter zu verlassen) | Höher (installiert = festgeschrieben) |
Wann sollte man sich für PWA anstelle von Native entscheiden?
- Inhaltsorientierte Apps – Nachrichten, Blogs, Zeitschriften.
- E-Commerce – Shopping-Websites (Pinterest PWA: 40 % mehr Zeitaufwand).
- Dienstprogramm-Apps – Rechner, Umrechner, Wetter.
- Apps mit geringer Interaktion – Apps, die Benutzer selten besuchen.
- Schwellenmärkte – Benutzer mit begrenztem Speicherplatz und begrenzten Daten.
- Progressive Verbesserung – Volle Erfahrung auf modernen Geräten, funktionsfähig auf älteren Geräten.
Wenn eine native App besser ist
- Hochleistungs-Gaming – 60+ FPS, komplexes 3D-Rendering.
- Umfassende Hardware-Integration – Bluetooth-Peripheriegeräte, NFC, ARKit/ARCore.
- Hintergrundverarbeitung – Musik-Streaming, Fitness-Tracking.
- Hohes Benutzerengagement erforderlich – Soziale Medien, Messaging.
Leistungsbenchmarks
| Metrisch | Vor PWA | Nach PWA | Verbesserung |
|---|---|---|---|
| Erster Ladevorgang | 5s | 1,2s | 76 % schneller |
| Nachfolgende Ladungen | 3s | 0,3s | 90 % schneller |
| Absprungrate | 45% | 20% | 55 % Ermäßigung |
| Conversion-Rate | 2% | 3.5% | Steigerung um 75 % |
| Seiten pro Sitzung | 3 | 5 | Steigerung um 66 % |
Ergebnisse aus der Praxis:
| Unternehmen | Schlüsselmetrik | Verbesserung |
|---|---|---|
| Twitter (X) Lite | Pro Sitzung gesendete Tweets | Steigerung um 75 % |
| Zeitaufwand | Steigerung um 40 % | |
| Uber | Buchungsabschluss | Reibungslos, auch bei 2G |
| Starbucks | Täglich aktive Benutzer | 2x Erhöhung |
| Alibaba | Conversion-Rate | Steigerung um 76 % |
PWA-Tests und Audits
Leuchtturm-PWA-Audit
# Run Lighthouse from CLI
npx lighthouse https://example.com --view --preset=desktop
# PWA-specific checks:
# - ✅ Registers a service worker
# - ✅ Responds with 200 when offline
# - ✅ Web app manifest exists
# - ✅ Manifest has display: standalone
# - ✅ Served over HTTPS
# - ✅ Redirects HTTP to HTTPS
# - ✅ Configurable start URL
# - ✅ Icons provided (192px and 512px)
Checkliste für manuelle Tests
## PWA Pre-Flight Checklist
- [ ] HTTPS enabled
- [ ] Service worker registered and active
- [ ] Pages load offline (test with airplane mode)
- [ ] Manifest configured with correct icons
- [ ] "Add to Home Screen" prompt appears
- [ ] PWA opens in standalone mode with no address bar
- [ ] Splash screen displays correctly
- [ ] Push notifications work
- [ ] All pages are responsive (mobile, tablet, desktop)
- [ ] Performance budget met (Lighthouse score > 90)
- [ ] Cross-browser tested (Chrome, Firefox, Safari, Edge)
- [ ] Accessibility audit passes
Erweiterte PWA-Funktionen
Hintergrundsynchronisierung
// Register a sync event
async function scheduleSync() {
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-pending-data');
}
// Service worker handles sync
self.addEventListener('sync', event => {
if (event.tag === 'sync-pending-data') {
event.waitUntil(syncPendingData());
}
});
Regelmäßige Hintergrundsynchronisierung
// Request periodic sync (requires user engagement)
const registration = await navigator.serviceWorker.ready;
await registration.periodicSync.register('update-content', {
minInterval: 24 * 60 * 60 * 1000 // Once per day
});
Web Share-API
// Share content using native share sheet
async function shareContent(title, text, url) {
if (navigator.share) {
await navigator.share({ title, text, url });
} else {
// Fallback to custom share UI
copyToClipboard(url);
}
}
Badge-API
// Set app icon badge
if (navigator.setAppBadge) {
await navigator.setAppBadge(5); // Show "5" on icon
} else if (navigator.clearAppBadge) {
await navigator.clearAppBadge();
}
Häufige Fallstricke
| Falle | Problem | Lösung |
|---|---|---|
| Übercaching | Die App stellt veraltete Inhalte bereit | Versions-Caches, Cache-First für die Shell und Network-First für den Inhalt implementieren |
| Kein Offline-Fallback | Weißer Bildschirm offline | Cachen Sie /offline.html immer mit einer aussagekräftigen Nachricht |
| Cache-Busting-Fehler | Alter Servicemitarbeiter bleibt bestehen | Verwenden Sie SkipWaiting() und Clients.claim() |
| Probleme mit dem Begrüßungsbildschirm | Falsche Designfarbe oder falsches Symbol | Konfigurieren Sie das Manifest richtig und verwenden Sie maskierbare Symbole |
| Safari-Kompatibilität | Einige Funktionen eingeschränkt (Push, regelmäßige Synchronisierung) | Progressive Verbesserung – sanfter Abbau |
| Große Cache-Größe | Speicherkontingent überschritten (50 MB pro Ursprung) | Alte Caches bereinigen, Cache-Größe begrenzen |
| Start-URL fehlt | Der Startbildschirm öffnet eine falsche Seite | Geben Sie im Manifest immer start_url an |
Fazit
Progressive Web Apps repräsentieren das Beste aus beiden Welten – die Reichweite und Zugänglichkeit des Webs kombiniert mit den Funktionen und dem Benutzererlebnis nativer Anwendungen.
Wann man eine PWA erstellt
- Sie wollen maximale Reichweite – Eine App für alle Plattformen.
- Benutzer benötigt Offline-Zugriff – Service-Worker-Caching.
- Sie möchten sofortiges Laden – App-Shell-Architektur.
- Sie möchten eine erneute Interaktion – Push-Benachrichtigungen ohne App Store.
- Sie haben eine inhaltsgesteuerte App – Nachrichten, E-Commerce, Dienstprogramme.
Erste Schritte
- Fügen Sie eine manifest.json und Symbole hinzu.
- Erstellen Sie einen Servicemitarbeiter mit Offline-Fallback.
- Führen Sie ein Lighthouse-Audit durch und beheben Sie kritische Probleme.
- Testen Sie auf realen Geräten (Android, iOS, Desktop).
- Implementieren und messen Sie das Benutzerengagement.
PWAs sind nicht in jedem Szenario ein Ersatz für native Apps – aber für die meisten inhaltsgesteuerten und nützlichen Anwendungen bieten sie ein hervorragendes Benutzererlebnis zu einem Bruchteil der Entwicklungskosten.
Empfohlene Artikel

6 Coding-Patterns, die ich von Senior Engineers gelernt habe

Einen privaten KI-Assistenten für Notizen bauen
Mache aus Markdown-Notizen einen durchsuchbaren Assistenten, ohne private Entwürfe offenzulegen.

Rechnungsdaten mit OCR automatisiert auslesen
Baue eine Pipeline, die Rechnungen liest, Felder extrahiert und unsichere Werte markiert.
Kommentare
0 KommentareNoch keine freigegebenen Kommentare sichtbar. Neue Antworten können moderiert werden.