Station 01/Die drei Dateien, konkret·Vorschau
Station 01 · Die installierbare Web-App/Phase 2

Die drei Dateien, konkret

Was hineingehört, im Klartext — mit dem Mindestmaß, das wirklich genügt.

Legen Sie die drei Dateien neben Ihre Anwendung, dorthin, wo sie öffentlich erreichbar sind. In einem Next.js-Projekt aus Kurs 1 ist das der Ordner public/.

1

Die Beschreibung: public/manifest.webmanifest

Sie sagt dem Gerät, wie die Anwendung heißt, welches Symbol sie hat und wo sie startet. Ohne sie bietet kein Gerät „Installieren“ an.

public/manifest.webmanifest
{
  "name": "Rundgang",
  "short_name": "Rundgang",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#16386D",
  "theme_color": "#16386D",
  "icons": [
    { "src": "/symbol-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/symbol-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/symbol-512.png", "sizes": "512x512", "type": "image/png",
      "purpose": "maskable" }
  ]
}
display: standalone ist die Zeile, die die Browserleiste verschwinden lässt. purpose: maskable erlaubt Android, das Symbol rund oder eckig zu beschneiden, ohne dass etwas Wichtiges abgeschnitten wird — dafür braucht das Bild ringsum etwa 20 % Rand.
2

Die Verknüpfung im Kopf der Seite

Eine Zeile im <head>. In Next.js gehört sie in das Wurzel-Layout.

app/layout.tsx (Ausschnitt)
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#16386D" />
<link rel="apple-touch-icon" href="/symbol-192.png" />
Die dritte Zeile ist nur für iPhone und iPad da. Ohne sie zeigt iOS auf dem Startbildschirm einen verkleinerten Bildschirmausschnitt statt Ihres Symbols — das sieht aus wie ein Versehen und ist eines.
3

Der Zwischenspeicher: public/sw.js

Ein kleines Programm, das jede Anfrage abfängt. Diese Fassung ist bewusst die einfachste, die etwas taugt: Sie legt beim ersten Besuch das Nötigste ab und liefert es später auch ohne Netz.

public/sw.js
const LAGER = 'rundgang-v1';
const NOETIG = ['/', '/manifest.webmanifest', '/symbol-192.png'];

self.addEventListener('install', e => {
  e.waitUntil(caches.open(LAGER).then(c => c.addAll(NOETIG)));
});

self.addEventListener('activate', e => {   // alte Lager aufräumen
  e.waitUntil(caches.keys().then(k =>
    Promise.all(k.filter(n => n !== LAGER).map(n => caches.delete(n)))));
});

self.addEventListener('fetch', e => {
  if (e.request.method !== 'GET') return;   // nur Lesen aus dem Lager
  e.respondWith(
    fetch(e.request)
      .then(a => { const k = a.clone();
                   caches.open(LAGER).then(c => c.put(e.request, k)); return a; })
      .catch(() => caches.match(e.request))  // kein Netz: aus dem Lager
  );
});
Die Zeile if (e.request.method !== 'GET') return; ist keine Kür. Ohne sie legen Sie irgendwann auch Formularabsendungen ins Lager und liefern gespeicherte Antworten auf Vorgänge aus, die nie stattgefunden haben. Ein Zwischenspeicher darf nur lesen.
4

Anmelden — eine Zeile in der Anwendung

app/layout.tsx oder eine Client-Komponente
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

Danach: Seite laden, einmal neu laden — und Android bietet die Installation an.

Zwei Voraussetzungen, die schnell übersehen werden. Erstens: Es muss HTTPS sein — ohne Zertifikat kein Service Worker (Ausnahme: localhost). Zweitens: Der Zwischenspeicher hält sich hartnäckig. Wenn Sie beim Entwickeln alte Fassungen sehen, ist fast immer das Lager schuld; die Fassungsnummer im Namen (rundgang-v1) ist die Lösung, nicht die Notabschaltung.
Auf Stufe 2 (Fortgeschritten) steht hier zusätzlich: wie man den Zwischenspeicher so aufbaut, dass eine Aktualisierung ankommt, ohne dass jemand den Browser leert
WarumWie eine Aktualisierung ankommt, ohne dass jemand den Browser leert

Der Zwischenspeicher ist die häufigste Ursache dafür, dass eine Änderung „nicht ankommt“. Das Muster, das trägt, hat drei Teile:

Ein Name mit Fassungsnummer. rundgang-v1, rundgang-v2. Beim Aktivieren löscht der Service Worker alle Lager, die nicht den aktuellen Namen tragen — das steht im Beispiel bereits drin.

Netz zuerst, Lager als Rückfall. Die Fassung im Kurs versucht immer zuerst das Netz und legt die Antwort ins Lager. Damit ist die Anzeige aktuell, solange Netz da ist, und funktioniert ohne. Der umgekehrte Weg — Lager zuerst — ist schneller und liefert deshalb zuverlässig alte Inhalte aus.

Ein Hinweis, wenn eine neue Fassung bereitliegt. Der Service Worker meldet das über ein Ereignis. Eine kleine Leiste „Neue Fassung verfügbar — neu laden“ ist ehrlicher als ein erzwungener Neustart mitten in einer Eingabe.

TiefeWarum der Service Worker nur GET-Anfragen behandeln darf

Ein Service Worker sieht jede Anfrage der Seite, auch das Absenden eines Formulars. Wer alles gleich behandelt, legt irgendwann die Antwort auf ein POST ins Lager — und liefert sie später aus, ohne dass der Vorgang stattgefunden hat.

In einer Bestellstrecke heißt das: eine Bestätigung ohne Bestellung. Der Fehler ist selten, schwer zu finden und teuer, weil er das Vertrauen in die Anzeige zerstört. Deshalb die eine Zeile ganz oben im Beispiel — sie ist die wichtigste der ganzen Datei.

Zugang erhalten
Fortschritt merken?Mit Zugang bleiben Häkchen und erledigte Phasen am Konto — auf jedem Gerät.