Progressive Web Apps mit Claude Code: Offline-First 2026
Progressive Web Apps bringen native App-Erlebnisse ins Browser-Ökosystem — Offline-Fähigkeit, Push Notifications und Installation vom Homescreen. Claude Code implementiert alle PWA-Bausteine: Service Worker, Web App Manifest, Caching-Strategien, Background Sync und Push Notifications.
PWA Grundlagen: Manifest und Service Worker
ManifestWeb App Manifest — Die Visitenkarte deiner PWA
# Prompt: "Erstelle ein vollständiges Web App Manifest für meine PWA"
// public/manifest.json
{
"name": "MeinApp — Produktivität ohne Grenzen",
"short_name": "MeinApp",
"description": "Produktivitäts-App mit Offline-Unterstützung",
"start_url": "/",
"display": "standalone", // Kein Browser-Chrome!
"orientation": "portrait",
"theme_color": "#5a0fc8",
"background_color": "#ffffff",
"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" }
],
"screenshots": [
{ "src": "/screenshots/desktop.png", "sizes": "1280x800", "form_factor": "wide" },
{ "src": "/screenshots/mobile.png", "sizes": "390x844", "form_factor": "narrow" }
],
"shortcuts": [
{ "name": "Neue Aufgabe", "url": "/tasks/new", "icons": [{ "src": "/icons/add.png", "sizes": "96x96" }] }
]
}
// HTML <head> — Manifest verknüpfen:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#5a0fc8">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="apple-touch-icon" href="/icons/icon-192.png">
Claude Code Tipp: "Generiere Icons in allen PWA-Größen (72, 96, 128, 144, 152, 192, 384, 512px) aus meinem SVG-Logo und lege sie in public/icons/ ab." Claude Code nutzt sharp oder Jimp und generiert alle Größen automatisch — inklusive maskable-Versionen mit Padding für Android Adaptive Icons.
OfflineService Worker Grundgerüst
# Prompt: "Erstelle einen Service Worker mit Install, Activate und Fetch Events"
// public/sw.js
const CACHE_NAME = 'meinapp-v1';
const STATIC_ASSETS = [
'/', '/index.html', '/offline.html',
'/assets/css/style.css', '/assets/js/app.js'
];
// Install: Statische Assets precachen
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(STATIC_ASSETS))
.then(() => self.skipWaiting()) // Sofort aktiv werden
);
});
// Activate: Alte Caches löschen
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys
.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
)
).then(() => self.clients.claim())
);
});
// Service Worker im HTML registrieren:
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then(reg => console.log('SW registriert:', reg.scope))
.catch(err => console.error('SW Fehler:', err));
}
Caching-Strategien: Cache First, Network First, Stale-While-Revalidate
CacheDie drei wichtigsten Strategien
# Prompt: "Implementiere alle drei Haupt-Caching-Strategien in meinem Service Worker"
// --- 1. CACHE FIRST (statische Assets) ---
// Ideal für: CSS, JS, Bilder, Fonts — ändert sich selten
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached;
const response = await fetch(request);
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
return response;
}
// --- 2. NETWORK FIRST (API-Daten) ---
// Ideal für: REST-Endpoints, dynamische Inhalte
async function networkFirst(request) {
try {
const response = await fetch(request);
const cache = await caches.open('api-cache');
cache.put(request, response.clone());
return response;
} catch {
const cached = await caches.match(request);
return cached || new Response('{"error":"offline"}', { headers: { 'Content-Type': 'application/json' } });
}
}
// --- 3. STALE-WHILE-REVALIDATE (News, Feeds) ---
// Ideal für: Blog-Posts, Nachrichten — schnell laden + im Hintergrund aktualisieren
async function staleWhileRevalidate(request) {
const cache = await caches.open('content-cache');
const cached = await cache.match(request);
const networkPromise = fetch(request).then(res => {
cache.put(request, res.clone());
return res;
});
return cached || networkPromise; // Cache sofort, Update im Hintergrund
}
// Strategie nach URL-Muster routing:
self.addEventListener('fetch', event => {
const { url } = event.request;
if (url.includes('/api/')) event.respondWith(networkFirst(event.request));
else if (url.includes('/blog/')) event.respondWith(staleWhileRevalidate(event.request));
else event.respondWith(cacheFirst(event.request));
});
Strategie-Auswahl: Cache First für unveränderliche Assets (fingerprinted), Network First für frische Daten, Stale-While-Revalidate für Content der sich gelegentlich ändert. Claude Code wählt automatisch die richtige Strategie wenn ihr den Inhaltstyp beschreibt.
Push Notifications mit der Web Push API
PushWeb Push — Vollständige Implementierung
# Prompt: "Implementiere Web Push Notifications mit VAPID-Authentifizierung"
// 1. VAPID Keys generieren (einmalig auf dem Server):
// npm install web-push
const webpush = require('web-push');
const keys = webpush.generateVAPIDKeys();
// → publicKey + privateKey in .env speichern!
// 2. Frontend — Push-Subscription erstellen:
async function subscribeToPush() {
const permission = await Notification.requestPermission();
if (permission !== 'granted') return;
const reg = await navigator.serviceWorker.ready;
const subscription = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
});
// Subscription ans Backend senden:
await fetch('/api/push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription)
});
}
// 3. Service Worker — Push empfangen + anzeigen:
self.addEventListener('push', event => {
const data = event.data?.json() || { title: 'Neue Nachricht', body: '' };
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192.png',
badge: '/icons/badge-96.png',
tag: data.tag || 'default',
data: { url: data.url || '/' },
actions: [
{ action: 'open', title: 'Öffnen' },
{ action: 'dismiss', title: 'Schließen' }
]
})
);
});
// Notification Klick → App öffnen:
self.addEventListener('notificationclick', event => {
event.notification.close();
if (event.action === 'open' || !event.action) {
event.waitUntil(clients.openWindow(event.notification.data.url));
}
});
// 4. Backend — Notification versenden (Node.js):
webpush.setVapidDetails('mailto:admin@example.com', VAPID_PUBLIC, VAPID_PRIVATE);
await webpush.sendNotification(subscription, JSON.stringify({
title: '🚀 Neue Funktion verfügbar',
body: 'Schau dir das neue Dashboard an!',
url: '/dashboard'
}));
Background Sync für Offline-Aktionen
SyncBackground Sync — Aktionen offline zwischenspeichern
# Prompt: "Implementiere Background Sync für Offline-Formularübermittlung"
// Frontend — Sync registrieren wenn offline:
async function submitFormWithSync(formData) {
if (navigator.onLine) {
return fetch('/api/tasks', { method: 'POST', body: JSON.stringify(formData) });
}
// Offline: Daten in IndexedDB speichern
await saveToIDB('pending-tasks', { id: Date.now(), ...formData });
// Background Sync registrieren:
const reg = await navigator.serviceWorker.ready;
await reg.sync.register('sync-tasks');
showToast('Gespeichert — wird synchronisiert sobald online');
}
// Service Worker — Sync Event abarbeiten:
self.addEventListener('sync', event => {
if (event.tag === 'sync-tasks') {
event.waitUntil(syncPendingTasks());
}
});
async function syncPendingTasks() {
const tasks = await getFromIDB('pending-tasks');
for (const task of tasks) {
const res = await fetch('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(task)
});
if (res.ok) await deleteFromIDB('pending-tasks', task.id);
}
}
// Periodic Background Sync (experimentell — Chrome 80+):
const reg = await navigator.serviceWorker.ready;
if ('periodicSync' in reg) {
await reg.periodicSync.register('refresh-feed', { minInterval: 24 * 60 * 60 * 1000 });
}
IndexedDB Helper: "Erstelle einen einfachen IndexedDB Wrapper mit get, set, delete und getAll für die Offline-Queue." Claude Code generiert einen sauberen Promise-basierten Wrapper — alternativ empfiehlt es die idb-Bibliothek von Jake Archibald für typsicheren IDB-Zugriff.
PWA in Next.js mit next-pwa
Next.jsnext-pwa Integration — Zero Config PWA
# Prompt: "Füge PWA-Support zu meiner Next.js App hinzu mit next-pwa"
// 1. Installation:
npm install next-pwa
// 2. next.config.js:
const withPWA = require('next-pwa')({
dest: 'public',
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === 'development',
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com\/.*/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: { maxEntries: 200, maxAgeSeconds: 24 * 60 * 60 }
}
},
{
urlPattern: /\.(png|jpg|jpeg|svg|gif|webp)$/,
handler: 'CacheFirst',
options: {
cacheName: 'image-cache',
expiration: { maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 }
}
},
{
urlPattern: /\/_next\/static\/.*/i,
handler: 'CacheFirst',
options: { cacheName: 'next-static', expiration: { maxEntries: 300 } }
}
]
});
module.exports = withPWA({
reactStrictMode: true,
});
// 3. pages/_app.tsx — Offline-Status anzeigen:
import { useEffect, useState } from 'react';
export default function App({ Component, pageProps }) {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const update = () => setIsOnline(navigator.onLine);
window.addEventListener('online', update);
window.addEventListener('offline', update);
return () => {
window.removeEventListener('online', update);
window.removeEventListener('offline', update);
};
}, []);
return (
<>
{!isOnline && <div className="offline-banner">Du bist offline — Daten werden synchronisiert sobald du wieder verbunden bist.</div>}
<Component {...pageProps} />
</>
);
}
App RouterNext.js 14+ App Router PWA
# Prompt: "Konfiguriere PWA für Next.js App Router mit Metadata API"
// app/layout.tsx — Manifest via Metadata API:
import type { Metadata } from 'next';
export const metadata: Metadata = {
manifest: '/manifest.json',
appleWebApp: {
capable: true,
statusBarStyle: 'default',
title: 'MeinApp',
},
formatDetection: { telephone: false },
themeColor: [
{ media: '(prefers-color-scheme: light)', color: '#5a0fc8' },
{ media: '(prefers-color-scheme: dark)', color: '#7c3aed' }
]
};
// app/manifest.ts — Dynamisches Manifest (Next.js 14+):
import type { MetadataRoute } from 'next';
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'MeinApp',
short_name: 'MeinApp',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#5a0fc8',
icons: [
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' }
]
};
}
App Router Hinweis: next-pwa hat noch keine offizielle App-Router-Unterstützung (Stand Mai 2026). Alternativen: @ducanh2912/next-pwa (aktiv gepflegt) oder manueller Service Worker in
public/sw.js mit next-pwa@5.x. Claude Code kennt beide Ansätze und empfiehlt je nach Projektanforderungen.Lighthouse PWA Score optimieren
Lighthouse100/100 PWA Score — Checkliste
# Prompt: "Analysiere meinen Lighthouse PWA Score und fixe alle Probleme"
// ✅ PFLICHT für PWA Installierbarkeit:
// 1. manifest.json mit name, short_name, icons (192px + 512px maskable)
// 2. Service Worker registriert + aktiv
// 3. HTTPS (localhost zählt für Dev)
// 4. start_url im Cache vorhanden (Offline-Start möglich)
// 5. viewport meta-Tag
// Service Worker — Offline-Seite als Fallback:
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(() => caches.match('/offline.html'))
);
}
});
// Performance-Optimierungen für PWA Score:
// ✅ Precaching: Alle Assets bei SW-Install cachen
// ✅ Lazy Loading: Images + nicht-kritisches JS
// ✅ Critical CSS inline in <head>
// ✅ Fonts: font-display: swap
// ✅ WebP/AVIF für Bilder
// Lighthouse CI automatisieren:
npm install -g @lhci/cli
// .lighthouserc.json:
{
"ci": {
"assert": {
"assertions": {
"categories:pwa": ["error", { "minScore": 0.9 }],
"categories:performance": ["warn", { "minScore": 0.8 }]
}
}
}
}
lhci autorun # In CI/CD Pipeline
// Häufige Lighthouse-Fehler + Fixes:
// ❌ "Does not provide a valid apple-touch-icon"
// → <link rel="apple-touch-icon" href="/icons/icon-180.png"> in <head>
// ❌ "Manifest icons are not maskable"
// → "purpose": "any maskable" + 10% safe-zone Padding im Icon
// ❌ "Service worker does not handle offline"
// → start_url muss im Cache sein (precachen!)
// ❌ "Page does not work offline"
// → offline.html precachen + als Fallback in fetch handler
Claude Code Prompt: "Führe einen Lighthouse-Audit meiner PWA durch und liste alle failing PWA-Checks mit konkreten Fix-Anweisungen auf. Generiere danach die benötigten Code-Änderungen." Claude Code analysiert euer Manifest, Service Worker und HTML systematisch gegen alle Lighthouse-Kriterien.
DevToolsPWA debuggen — Service Worker DevTools
# Nützliche Debugging-Befehle und -Prompts:
// Chrome DevTools → Application Tab:
// - Service Workers: Status, Update erzwingen, Offline simulieren
// - Cache Storage: Gespeicherte Requests anzeigen/löschen
// - IndexedDB: Offline-Queue debuggen
// - Manifest: Installierbarkeit + Fehler anzeigen
// Service Worker im Browser deregistrieren (Dev-Reset):
const regs = await navigator.serviceWorker.getRegistrations();
for (const reg of regs) await reg.unregister();
// Update-Check erzwingen:
const reg = await navigator.serviceWorker.ready;
await reg.update();
// Prompt für Claude Code: "Mein Service Worker cached zu aggressiv.
// Implementiere eine Update-Benachrichtigung wenn eine neue SW-Version verfügbar ist"
// SW-Update-Notification Pattern:
navigator.serviceWorker.addEventListener('controllerchange', () => {
showUpdateBanner('Neue Version verfügbar!', () => location.reload());
});
reg.addEventListener('updatefound', () => {
reg.installing.addEventListener('statechange', () => {
if (reg.installing.state === 'installed' && navigator.serviceWorker.controller) {
showUpdateBanner('Update bereit — Seite neu laden?');
}
});
});
PWA-Modul im Kurs
Im Claude Code Mastery Kurs: vollständiges PWA-Modul mit Workbox, Push Notifications, Background Sync, IndexedDB und Lighthouse-Optimierung — von Grundlagen bis Production-Deployment.
14 Tage kostenlos testen →