Progressive Web Apps 2025 - Vollständiger Leitfaden
Progressive Web Apps
mobileProgressive Web Apps 2025 - Vollständiger Leitfaden
Progressive Web Apps (PWA) sind Webanwendungen, die die besten Eigenschaften von Websites und nativen Apps vereinen. Sie laufen im Browser, bieten aber Funktionen, die man von mobilen Anwendungen kennt: Offline-Funktionalität, Push-Benachrichtigungen, Installation auf dem Startbildschirm und flüssige Animationen. Im Jahr 2025 sind PWAs zum Standard für die Entwicklung moderner Anwendungen geworden, die schnell, zuverlässig und ansprechend sind.
Was sind Progressive Web Apps?#
PWAs sind Webanwendungen, die bestimmte technische und qualitative Kriterien erfüllen. Der Begriff wurde 2015 von Google eingeführt, aber seitdem hat sich die Technologie enorm weiterentwickelt. Die wichtigsten Merkmale von PWAs sind:
- Progressiv - funktionieren für jeden Benutzer unabhängig vom Browser
- Responsiv - passen sich an jedes Gerät an (Desktop, Tablet, Smartphone)
- Netzwerkunabhängig - funktionieren offline oder bei schlechter Verbindung
- App-ähnlich - Interaktionen und Navigation im nativen Stil
- Aktuell - dank Service Workern immer auf dem neuesten Stand
- Sicher - über HTTPS ausgeliefert
- Auffindbar - durch das W3C-Manifest als Anwendungen identifizierbar
- Installierbar - können ohne App Store installiert werden
- Verlinkbar - einfach teilbar über URL
Service Workers - Das Herzstück von PWA#
Ein Service Worker ist ein JavaScript-Skript, das im Hintergrund läuft, unabhängig von der Seite. Er ist verantwortlich für Offline-Funktionalität, Hintergrundsynchronisation und Push-Benachrichtigungen. Er fungiert als Proxy zwischen dem Browser und dem Netzwerk.
Registrierung eines Service Workers#
// app/register-sw.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/',
});
console.log('SW registriert:', registration.scope);
// Auf Updates prüfen
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'activated') {
console.log('Neue Version verfügbar!');
}
});
});
} catch (error) {
console.error('SW-Registrierung fehlgeschlagen:', error);
}
});
}
Lebenszyklus eines Service Workers#
Ein Service Worker durchläuft mehrere Zustände:
- Installation (
install) - Herunterladen und Cachen von Ressourcen - Warten (
waiting) - Warten auf das Schließen alter Tabs - Aktivierung (
activate) - Bereinigung des alten Caches - Abfangen (
fetch) - Abfangen von Netzwerkanfragen
// sw.js
const CACHE_NAME = 'pwa-cache-v1';
const STATIC_ASSETS = [
'/',
'/offline.html',
'/css/styles.css',
'/js/app.js',
'/images/logo.webp',
];
// Installation - statische Ressourcen cachen
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log('Cache geöffnet');
return cache.addAll(STATIC_ASSETS);
})
);
self.skipWaiting();
});
// Aktivierung - alten Cache bereinigen
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
self.clients.claim();
});
Web App Manifest#
Das Manifest ist eine JSON-Datei, die dem Browser Informationen über Ihre Anwendung mitteilt und festlegt, wie sie sich nach der Installation auf dem Gerät des Benutzers verhalten soll.
{
"name": "Meine PWA-Anwendung",
"short_name": "MeinePWA",
"description": "Eine beispielhafte Progressive Web App",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0070f3",
"orientation": "portrait-primary",
"scope": "/",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"screenshots": [
{
"src": "/screenshots/desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/screenshots/mobile.png",
"sizes": "375x812",
"type": "image/png",
"form_factor": "narrow"
}
],
"shortcuts": [
{
"name": "Neuer Beitrag",
"short_name": "Neu",
"url": "/new",
"icons": [{ "src": "/icons/shortcut-new.png", "sizes": "96x96" }]
}
]
}
Einbindung des Manifests in die Seite:
<head>
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#0070f3" />
<link rel="apple-touch-icon" href="/icons/icon-192x192.png" />
</head>
Caching-Strategien - Der Offline-First-Ansatz#
Die Wahl der richtigen Caching-Strategie ist entscheidend für die Performance und das Benutzererlebnis. Hier sind die wichtigsten Strategien:
Cache First (Cache, dann Netzwerk)#
Prüft zuerst den Cache. Wenn die Ressource nicht vorhanden ist, wird sie aus dem Netzwerk geladen. Ideal für statische Ressourcen, die sich selten ändern.
// 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) => {
// Neue Ressource im Cache speichern
if (response.status === 200) {
const responseClone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
}
return response;
});
})
);
});
Network First (Netzwerk, dann Cache)#
Versucht zuerst, aus dem Netzwerk zu laden. Wenn das fehlschlägt, wird die gecachte Version zurückgegeben. Ideal für dynamische Inhalte wie API-Antworten.
// Network-First-Strategie
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
// Cache mit frischen Daten aktualisieren
const responseClone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
return caches.match(event.request).then((cachedResponse) => {
return cachedResponse || caches.match('/offline.html');
});
})
);
});
Stale While Revalidate#
Gibt sofort die gecachte Version zurück (falls verfügbar) und holt gleichzeitig eine Aktualisierung aus dem Netzwerk im Hintergrund. Ideal für Ressourcen, die schnell verfügbar sein sollten, aber auch aktuell bleiben müssen.
// Stale-While-Revalidate-Strategie
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return cachedResponse || fetchPromise;
});
})
);
});
Strategievergleich#
| Strategie | Geschwindigkeit | Datenfische | Verwendung | |-----------|----------------|-------------|------------| | Cache First | Sehr schnell | Können veraltet sein | Schriften, Bilder, CSS | | Network First | Langsamer | Immer aktuell | APIs, dynamische Daten | | Stale While Revalidate | Schnell | Im Hintergrund aktualisiert | Artikellisten, Profile |
Push-Benachrichtigungen#
Push-Benachrichtigungen ermöglichen es, Benutzer auch dann einzubeziehen, wenn die Anwendung nicht geöffnet ist. Sie erfordern einen Service Worker und die Push-API.
Benachrichtigungen abonnieren#
// Push-Benachrichtigungen abonnieren
async function subscribeToPush() {
const registration = await navigator.serviceWorker.ready;
// Berechtigungen prüfen
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
console.log('Benutzer hat Benachrichtigungen abgelehnt');
return;
}
// Abonnement erstellen
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
});
// Abonnement an den Server senden
await fetch('/api/push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription),
});
}
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}
Benachrichtigungen im Service Worker verarbeiten#
// sw.js - Push-Verarbeitung
self.addEventListener('push', (event) => {
const data = event.data?.json() ?? {};
const options = {
body: data.body || 'Neue Nachricht!',
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png',
vibrate: [200, 100, 200],
data: { url: data.url || '/' },
actions: [
{ action: 'open', title: 'Öffnen' },
{ action: 'dismiss', title: 'Verwerfen' },
],
};
event.waitUntil(
self.registration.showNotification(data.title || 'PWA', options)
);
});
// Klick auf Benachrichtigung verarbeiten
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.action === 'dismiss') return;
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
});
PWA-Installierbarkeit#
Einer der wichtigsten Vorteile von PWAs ist die Möglichkeit, sie ohne App Store zu installieren. Der Browser zeigt eine Installationsschaltfläche an, wenn die Anwendung die PWA-Kriterien erfüllt.
// beforeinstallprompt-Event verarbeiten
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault();
deferredPrompt = event;
// Eigene Installationsschaltfläche anzeigen
const installBtn = document.getElementById('install-button');
installBtn.style.display = 'block';
installBtn.addEventListener('click', async () => {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(`Benutzer hat ${outcome === 'accepted' ? 'installiert' : 'abgelehnt'}`);
deferredPrompt = null;
installBtn.style.display = 'none';
});
});
// Installation erkennen
window.addEventListener('appinstalled', () => {
console.log('Anwendung installiert!');
deferredPrompt = null;
});
Workbox - Vereinfachung der Service-Worker-Entwicklung#
Workbox ist eine Sammlung von Bibliotheken von Google, die die Erstellung von Service Workern vereinfacht. Anstatt alles manuell zu schreiben, können Sie fertige Strategien verwenden.
// sw.js mit Workbox
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import {
CacheFirst,
NetworkFirst,
StaleWhileRevalidate,
} from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
// Precaching - automatisches Cachen von Build-Dateien
precacheAndRoute(self.__WB_MANIFEST);
// Bilder - Cache First mit 60 Bildern Limit, max 30 Tage
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images-cache',
plugins: [
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Tage
}),
new CacheableResponsePlugin({ statuses: [0, 200] }),
],
})
);
// API - Network First mit Cache-Fallback
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api-cache',
plugins: [
new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 5 * 60, // 5 Minuten
}),
],
})
);
// CSS und JS - Stale While Revalidate
registerRoute(
({ request }) =>
request.destination === 'style' || request.destination === 'script',
new StaleWhileRevalidate({
cacheName: 'static-resources',
})
);
// Google Fonts - Cache First, lange Lebensdauer
registerRoute(
({ url }) => url.origin === 'https://fonts.googleapis.com' ||
url.origin === 'https://fonts.gstatic.com',
new CacheFirst({
cacheName: 'google-fonts',
plugins: [
new ExpirationPlugin({
maxEntries: 30,
maxAgeSeconds: 365 * 24 * 60 * 60, // 1 Jahr
}),
],
})
);
PWA in Next.js - Schritt-für-Schritt-Konfiguration#
Next.js eignet sich hervorragend zum Erstellen von PWAs. So konfigurieren Sie PWA mit dem Paket next-pwa:
Installation#
npm install next-pwa
Konfiguration von next.config.js#
// next.config.js
const withPWA = require('next-pwa')({
dest: 'public',
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === 'development',
runtimeCaching: [
{
urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts',
expiration: {
maxEntries: 4,
maxAgeSeconds: 365 * 24 * 60 * 60,
},
},
},
{
urlPattern: /^https:\/\/cdn\.example\.com\/.*/i,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'cdn-assets',
expiration: {
maxEntries: 32,
maxAgeSeconds: 24 * 60 * 60,
},
},
},
{
urlPattern: /\/api\/.*$/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-responses',
expiration: {
maxEntries: 16,
maxAgeSeconds: 60 * 60,
},
networkTimeoutSeconds: 10,
},
},
{
urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
handler: 'CacheFirst',
options: {
cacheName: 'static-images',
expiration: {
maxEntries: 64,
maxAgeSeconds: 30 * 24 * 60 * 60,
},
},
},
],
});
module.exports = withPWA({
// Weitere Next.js-Konfiguration
reactStrictMode: true,
});
Manifest in Next.js (App Router)#
// app/manifest.ts
import type { MetadataRoute } from 'next';
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Meine PWA-Anwendung',
short_name: 'MeinePWA',
description: 'Eine moderne Progressive Web App',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#0070f3',
icons: [
{
src: '/icons/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/icons/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
};
}
PWA-Installationskomponente in React#
// components/InstallPWA.tsx
'use client';
import { useState, useEffect } from 'react';
interface BeforeInstallPromptEvent extends Event {
prompt: () => Promise<void>;
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
}
export function InstallPWA() {
const [deferredPrompt, setDeferredPrompt] =
useState<BeforeInstallPromptEvent | null>(null);
const [isInstalled, setIsInstalled] = useState(false);
useEffect(() => {
const handler = (e: Event) => {
e.preventDefault();
setDeferredPrompt(e as BeforeInstallPromptEvent);
};
const installedHandler = () => {
setIsInstalled(true);
setDeferredPrompt(null);
};
window.addEventListener('beforeinstallprompt', handler);
window.addEventListener('appinstalled', installedHandler);
// Prüfen, ob bereits installiert
if (window.matchMedia('(display-mode: standalone)').matches) {
setIsInstalled(true);
}
return () => {
window.removeEventListener('beforeinstallprompt', handler);
window.removeEventListener('appinstalled', installedHandler);
};
}, []);
const handleInstall = async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
setIsInstalled(true);
}
setDeferredPrompt(null);
};
if (isInstalled || !deferredPrompt) return null;
return (
<button
onClick={handleInstall}
className="fixed bottom-4 right-4 bg-blue-600 text-white px-6 py-3 rounded-lg shadow-lg hover:bg-blue-700 transition-colors z-50"
>
Anwendung installieren
</button>
);
}
Lighthouse PWA-Audit#
Google Lighthouse ist ein Tool zur Überprüfung der Seitenqualität, das einen dedizierten PWA-Test enthält. Um die vollständige Konformität zu erreichen, muss Ihre Anwendung folgende Kriterien erfüllen:
- Ladegeschwindigkeit - die Seite lädt schnell genug im 3G-Netzwerk
- HTTPS - die Anwendung wird über eine sichere Verbindung ausgeliefert
- Service Worker - registriert und die Seite kontrollierend
- Manifest - gültige Manifest-Datei mit erforderlichen Feldern
- Icons - Icons in den richtigen Größen (192x192 und 512x512)
- Themenfarbe - definiertes theme-color Meta-Tag
- Offline-Seite - die Anwendung zeigt Inhalte auch ohne Netzwerk an
- HTTP-zu-HTTPS-Weiterleitung - automatische Weiterleitung
# Lighthouse-Audit über CLI
npx lighthouse https://ihre-seite.de --view --preset=desktop
# Nur PWA-Audit
npx lighthouse https://ihre-seite.de --only-categories=pwa --output=json
Sie können auch den Lighthouse-Tab in Chrome DevTools verwenden (F12 > Lighthouse > Seitenladung analysieren).
PWA vs. native Apps#
| Merkmal | PWA | Native App | |---------|-----|------------| | Installation | Aus dem Browser, kein Store nötig | Store erforderlich (App Store / Google Play) | | Größe | Einige KB - MB | Dutzende bis Hunderte MB | | Updates | Automatisch | Download von Updates erforderlich | | API-Zugriff | Eingeschränkt, aber wachsend | Voller Gerätezugriff | | Entwicklungskosten | Eine Codebasis | Separater Code für jede Plattform | | SEO | Volle Unterstützung | Keine (nur Store-Sichtbarkeit) | | Offline | Ja (Service Worker) | Ja (nativer Speicher) | | Push-Benachrichtigungen | Ja (Web Push) | Ja (nativ) | | Leistung | Gut, aber geringer als nativ | Höchste | | Bluetooth/NFC | Eingeschränkt (Web Bluetooth API) | Volle Unterstützung |
Browser-Unterstützung#
Im Jahr 2025 ist die PWA-Unterstützung sehr breit gefächert:
- Chrome - volle Unterstützung auf allen Plattformen
- Edge - volle Unterstützung, Chromium-basiert
- Firefox - Service Worker- und Manifest-Unterstützung (eingeschränkte Installierbarkeit)
- Safari/iOS - Unterstützung seit iOS 16.4, einschließlich Push-Benachrichtigungen und Startbildschirm-Installation
- Samsung Internet - volle Unterstützung
- Opera - volle Unterstützung
Eine wichtige Änderung 2024/2025: Apple hat die PWA-Unterstützung auf iOS erheblich verbessert, einschließlich Push-Benachrichtigungen in Safari und besserer Unterstützung für installierbare Webanwendungen.
PWA-Beispiele aus der Praxis#
Viele bekannte Unternehmen nutzen PWAs mit beeindruckenden Ergebnissen:
- Twitter Lite - 65% mehr Seiten pro Sitzung, 75% mehr Tweets, 20% weniger Absprungrate
- Pinterest - 60% mehr Benutzerengagement, 44% mehr Werbeeinnahmen
- Starbucks - PWA ist 99,84% kleiner als die native iOS-App, Verdoppelung der Online-Bestellungen
- Uber - die App lädt in 3 Sekunden selbst im 2G-Netzwerk
- Trivago - 150% mehr Engagement nach PWA-Implementierung
Erweiterte PWA-Funktionen 2025#
Background Sync#
Hintergrunddatensynchronisation, wenn der Benutzer die Verbindung wiederherstellt:
// Synchronisation registrieren
async function saveData(data) {
try {
await fetch('/api/data', {
method: 'POST',
body: JSON.stringify(data),
});
} catch {
// Daten lokal speichern und Sync registrieren
await saveToIndexedDB('pending-sync', data);
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-data');
}
}
// sw.js - Synchronisation verarbeiten
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-data') {
event.waitUntil(syncPendingData());
}
});
async function syncPendingData() {
const pendingData = await getFromIndexedDB('pending-sync');
for (const data of pendingData) {
await fetch('/api/data', {
method: 'POST',
body: JSON.stringify(data),
});
await removeFromIndexedDB('pending-sync', data.id);
}
}
Periodic Background Sync#
Periodische Datensynchronisation:
// Periodische Synchronisation registrieren
const registration = await navigator.serviceWorker.ready;
const status = await navigator.permissions.query({
name: 'periodic-background-sync',
});
if (status.state === 'granted') {
await registration.periodicSync.register('update-content', {
minInterval: 24 * 60 * 60 * 1000, // alle 24 Stunden
});
}
// sw.js
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'update-content') {
event.waitUntil(updateContent());
}
});
Web Share API#
Natives Teilen von Inhalten:
async function shareContent() {
if (navigator.share) {
try {
await navigator.share({
title: 'Meine PWA-Anwendung',
text: 'Schau dir diese großartige App an!',
url: window.location.href,
});
} catch (err) {
console.log('Teilen abgebrochen');
}
}
}
PWA Best Practices#
- Beginnen Sie mit einer Offline-Strategie - entwerfen Sie Ihre Anwendung von Anfang an mit Offline-Funktionalität
- Cache-Größe optimieren - cachen Sie nicht alles, setzen Sie Limits
- Service Worker intelligent aktualisieren - informieren Sie Benutzer über neue Versionen
- HTTPS verwenden - erforderlich für Service Worker
- Offline testen - Chrome DevTools > Application > Service Workers > Offline
- Performance messen - regelmäßige Lighthouse-Audits
- Progressiv laden - zuerst ein Skelett (Skeleton Screen) anzeigen, dann Daten laden
- App Shell Pattern verwenden - minimale Oberfläche cachen und Inhalte dynamisch laden
Fazit#
Progressive Web Apps sind im Jahr 2025 eine ausgereifte Technologie, die das Beste aus beiden Welten bietet: die Reichweite von Websites und das Erlebnis nativer Anwendungen. Dank Service Workern, Web App Manifest und modernen Browser-APIs ermöglichen PWAs die Erstellung schneller, zuverlässiger und ansprechender Anwendungen, die auf jedem Gerät funktionieren.
Der Schlüssel zum Erfolg liegt in der Wahl der richtigen Caching-Strategien, einer durchdachten Offline-First-Architektur und regelmäßigem Testen mit Lighthouse. Tools wie Workbox und Framework-Integrationen (z.B. next-pwa) vereinfachen den PWA-Entwicklungsprozess erheblich.
Benötigen Sie Hilfe beim Erstellen einer Progressive Web App oder bei der Modernisierung einer bestehenden Anwendung? MDS Software Solutions Group ist spezialisiert auf die Entwicklung moderner Webanwendungen mit voller PWA-Unterstützung. Kontaktieren Sie uns, um Ihr Projekt zu besprechen und zu erfahren, wie PWA das Engagement Ihrer Benutzer verbessern kann.
Team von Programmierexperten, die sich auf moderne Webtechnologien spezialisiert haben.