Program Planner DOCS & HILFE

Vollbildseiten als PWA einrichten

Eine Progressive Web App (PWA) ist eine Website, die sich wie eine App auf dem Handy installieren lässt: mit eigenem Symbol, ohne Adressleiste und auch bei schlechtem Netz nutzbar. Ein App Store ist nicht nötig. Diese Anleitung macht aus den Vollbildseiten in etwa 30 Minuten eine PWA.

Was das Plugin mitbringt

Das Plugin enthält weder Manifest noch Service Worker, denn beide gehören zu Ihrer Website und nicht zu einem einzelnen Plugin. Es erkennt eine installierte App aber automatisch und passt Filter und Suche an. Voraussetzung sind die Vollbildseiten aus dem Paket PRO.

Was Sie brauchen

  • Ihre Website läuft über HTTPS. Ohne HTTPS funktioniert eine PWA nicht.
  • Sie kommen per FTP oder Dateimanager Ihres Hosters in das Hauptverzeichnis der Website, also dorthin, wo die Datei wp-config.php liegt.
  • WordPress liegt direkt im Hauptverzeichnis. Liegt es in einem Unterordner, müssen Sie alle Pfade unten anpassen.
DateiAufgabeOrt
manifest.jsonDer „Ausweis“ der App: Name, Symbol, Farbe, StartseiteHauptverzeichnis
sw.jsDer Service Worker: speichert Inhalte auf dem Gerät, damit sie auch ohne Netz da sindHauptverzeichnis
offline.htmlHinweis, wenn eine Seite offline nicht vorliegtHauptverzeichnis
app-icons/Die App-SymboleOrdner im Hauptverzeichnis
event-app.phpBindet alles in WordPress einwp-content/mu-plugins/

Schritt 1: Symbole erstellen

Legen Sie im Hauptverzeichnis den Ordner app-icons an und speichern Sie darin drei PNG-Dateien: icon-192.png (192 × 192 Pixel), icon-512.png (512 × 512 Pixel) und icon-maskable-512.png (512 × 512 Pixel). Beim dritten Symbol füllt der Hintergrund die ganze Fläche, und das Motiv hält rundum etwa 10 Prozent Abstand zum Rand, weil Android die Ecken je nach Gerät abschneidet.

Schritt 2: Manifest anlegen

Erstellen Sie die Datei manifest.json im Hauptverzeichnis. Ändern Sie Name, Kurzname (erscheint unter dem Symbol, höchstens etwa 12 Zeichen) und Farbe.

manifest.json
{
  "name": "Seitenwind Convention 2026",
  "short_name": "Seitenwind",
  "lang": "de",
  "start_url": "/?ec_plan&ec_app=1",
  "scope": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#f76b15",
  "icons": [
    { "src": "/app-icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/app-icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/app-icons/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ]
}

start_url ist die Seite, mit der die App startet. Haben Sie eine eigene Startseite eingerichtet, erscheint diese automatisch zuerst. Der Zusatz &ec_app=1 zeigt die Navigationsleiste unten auch dann, wenn der App-Modus nicht für alle eingestellt ist.

Schritt 3: Service Worker anlegen

Der Service Worker ist ein kleines Skript, das im Hintergrund läuft und zwischen App und Internet vermittelt. Erstellen Sie die Datei sw.js im Hauptverzeichnis. Sie muss dort liegen, denn ein Service Worker wirkt nur auf Adressen unterhalb seines Ordners. Tragen Sie bei START_PAGES nur Seiten ein, die Sie wirklich nutzen. Läuft auch das Aussteller-Plugin, ergänzen Sie /?eem_map&eem_app=1 und /?eem_list&eem_app=1.

sw.js
// Bei jeder Änderung an dieser Datei die Nummer erhöhen
var CACHE_NAME = 'event-app-v1';
var OFFLINE_PAGE = '/offline.html';
// Seiten, die schon bei der Installation offline bereitliegen
var START_PAGES = ['/?ec_plan&ec_app=1', '/?ec_speakers&ec_app=1'];

// Eine Weiterleitung (etwa zur eigenen Startseite) darf nicht gespeichert bleiben,
// der Browser würde sie offline ablehnen
function plain(response) {
  if (!response.redirected) return response;
  return new Response(response.body, { status: response.status, statusText: response.statusText, headers: response.headers });
}

function save(key, response) {
  if (response.status === 200) {
    var copy = response.clone();
    caches.open(CACHE_NAME).then(function (cache) { return cache.put(key, copy); }).catch(function () {});
  }
  return response;
}

self.addEventListener('install', function (event) {
  event.waitUntil(caches.open(CACHE_NAME).then(function (cache) {
    return Promise.all(START_PAGES.concat(OFFLINE_PAGE).map(function (page) {
      return fetch(page).then(function (response) {
        return cache.put(page, plain(response));
      }).catch(function () {});
    }));
  }).then(function () { return self.skipWaiting(); }));
});

self.addEventListener('activate', function (event) {
  event.waitUntil(caches.keys().then(function (names) {
    return Promise.all(names.filter(function (name) {
      return name.indexOf('event-app-') === 0 && name !== CACHE_NAME;
    }).map(function (name) { return caches.delete(name); }));
  }).then(function () { return self.clients.claim(); }));
});

self.addEventListener('fetch', function (event) {
  var request = event.request, url = new URL(request.url);

  // Formulare, fremde Adressen, Videos und das Backend bleiben unberührt
  if (request.method !== 'GET' || url.origin !== self.location.origin || request.headers.has('range')) return;
  if (/^/(wp-admin|wp-login)/.test(url.pathname)) return;

  // Programm- und Ausstellerdaten: erst das Netz, offline die letzte Fassung
  if (//wp-json/(event-calendar|event-exhibitor)/v1//.test(url.pathname)) {
    url.searchParams.delete('t');
    url.searchParams.delete('_t');
    var key = url.toString();
    event.respondWith(fetch(request).then(function (response) {
      return save(key, response);
    }).catch(function () {
      return caches.match(key, { ignoreVary: true }).then(function (hit) {
        return hit || caches.match(key, { ignoreSearch: true, ignoreVary: true });
      }).then(function (hit) {
        if (hit) return hit;
        return url.pathname.indexOf('/event-calendar/') !== -1
          ? new Response('[]', { status: 200, headers: { 'Content-Type': 'application/json' } })
          : Response.error();
      });
    }));
    return;
  }

  // Skripte, Stile und Bilder: sofort aus dem Speicher, im Hintergrund erneuern
  if (/^/(wp-content|wp-includes)//.test(url.pathname)) {
    event.respondWith(caches.match(request, { ignoreVary: true }).then(function (hit) {
      var network = fetch(request).then(function (response) { return save(request, response); });
      if (!hit) return network;
      event.waitUntil(network.catch(function () {}));
      return hit;
    }));
    return;
  }

  // Vollbildseiten: erst das Netz, offline die letzte Fassung oder der Hinweis
  if (request.mode === 'navigate') {
    var isAppPage = /[?&](ec|eem)_/.test(url.search);
    event.respondWith(fetch(request).then(function (response) {
      return isAppPage ? save(request, response) : response;
    }).catch(function () {
      return caches.match(request, { ignoreVary: true }).then(function (hit) {
        return hit || caches.match(OFFLINE_PAGE);
      });
    }));
  }
});
InhaltWas passiert
Programm- und AusstellerdatenMit Netz immer aktuell, ohne Netz die zuletzt geladene Fassung. Die Zeitstempel t und _t werden dafür aus der Adresse entfernt, sonst würde die Fassung nie wiedergefunden.
Skripte, Stile, BilderKommen sofort aus dem Speicher und werden im Hintergrund erneuert.
VollbildseitenMit Netz aktuell, ohne Netz die zuletzt besuchte Fassung.
Formulare, Backend, fremde AdressenBleiben unberührt und werden nie gespeichert.

Schritt 4: Offline-Seite anlegen

Diese Seite erscheint, wenn eine Seite ohne Netz nicht vorliegt. Speichern Sie sie als offline.html im Hauptverzeichnis.

offline.html
<!doctype html>
<html lang="de">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Gerade keine Verbindung</title>
</head>
<body style="font-family: system-ui, sans-serif; text-align: center; padding: 3rem 1rem;">
  <h1>Gerade keine Verbindung</h1>
  <p>Diese Seite war noch nicht geöffnet. Bereits besuchte Seiten funktionieren weiter.</p>
  <button onclick="location.reload()">Erneut versuchen</button>
</body>
</html>

Schritt 5: In WordPress einbinden

Die Vollbildseiten haben kein Theme, geben aber die üblichen WordPress-Bereiche aus. Darüber kommen Manifest und Service Worker in die Seite. Legen Sie die Datei event-app.php im Ordner wp-content/mu-plugins/ an. Gibt es den Ordner noch nicht, erstellen Sie ihn. Dateien dort sind automatisch aktiv und bleiben bei Theme- und Plugin-Updates erhalten.

wp-content/mu-plugins/event-app.php
<?php
/**
 * Plugin Name: Event App (PWA)
 */
add_action( 'wp_head', function () {
  echo '<link rel="manifest" href="/manifest.json">' . "
";
  echo '<meta name="theme-color" content="#f76b15">' . "
";
  echo '<link rel="apple-touch-icon" href="/app-icons/icon-192.png">' . "
";
} );

add_action( 'wp_footer', function () {
  echo "<script>if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js'); }</script>
";
} );

Schritt 6: Testen und installieren

  1. 1
    Cache leeren

    Leeren Sie den Cache Ihres Caching-Plugins. Nehmen Sie sw.js und manifest.json von der Optimierung aus und fassen Sie die Plugin-Skripte nicht zusammen.

  2. 2
    Prüfen

    Öffnen Sie die Vollbildseite in Chrome oder Edge am Computer, drücken Sie F12 und wählen Sie „Anwendung“. Unter „Manifest“ dürfen keine Fehler stehen, unter „Service Worker“ muss sw.js aktiv sein.

  3. 3
    Android

    Vollbildseite im Browser öffnen, im Menü „App installieren“ oder „Zum Startbildschirm hinzufügen“ wählen.

  4. 4
    iPhone und iPad

    Seite in Safari öffnen, auf „Teilen“ tippen, dann „Zum Home-Bildschirm“.

  5. 5
    Computer

    In Chrome oder Edge erscheint in der Adressleiste ein Symbol zum Installieren.

  6. 6
    Offline testen

    App öffnen, einmal neu laden und durch die Seiten gehen. Dann den Flugmodus einschalten und alles erneut öffnen.

Warum einmal neu laden?

Beim allerersten Besuch installiert sich der Service Worker erst. Skripte und Bilder dieser Seite speichert er deshalb ab dem nächsten Aufruf. Testen Sie abgemeldet, damit keine Werkzeugleiste von WordPress mitgespeichert wird.

Was offline funktioniert

BereichVerhalten ohne Netz
MerklisteFunktioniert vollständig, sie liegt auf dem Gerät.
Live-TickerEinmal geladen, wechseln Programmpunkte auch ohne Netz pünktlich. Stellen Sie die Vorladezeit im Reiter „Live-Ticker & Monitor“ auf mindestens die Dauer der Veranstaltung.
Programm und ZeitplanZeigen den Stand des letzten Abrufs. Änderungen kommen an, sobald wieder Netz da ist.
SprecherDas Verzeichnis ist offline da, wenn die Seite vorab geladen wurde. Pop-ups nur, wenn sie schon einmal geöffnet waren.
Neue SeitenNoch nie geöffnete Seiten zeigen den Offline-Hinweis aus Schritt 4.

Später etwas ändern

Ändern Sie sw.js, erhöhen Sie jedes Mal die Nummer bei CACHE_NAME, etwa von event-app-v1 auf event-app-v2. Der neue Service Worker räumt den alten Speicher dann selbst auf. Inhalte im Plugin pflegen Sie wie gewohnt. Gäste bekommen sie beim nächsten Öffnen mit Netz.

Wenn etwas nicht klappt

ProblemLösung
Kein Installieren-AngebotHTTPS prüfen und in den Entwicklerwerkzeugen nachsehen, ob Manifest und Symbole gefunden werden. Ist die App schon installiert, erscheint kein Angebot mehr.
Service Worker startet nichtLiegt sw.js im Hauptverzeichnis? Rufen Sie ihre-seite.de/sw.js direkt auf. Eine Fehlerseite deutet auf einen falschen Ort oder eine Sperre durch ein Sicherheits-Plugin hin.
Änderungen an sw.js kommen nicht anCACHE_NAME erhöhen und sw.js vom Caching ausnehmen.
Kalender bleibt offline leerDie Seite einmal mit Netz öffnen und vollständig laden lassen, danach erneut testen.
Zwei NavigationsleistenHat Ihre App eine eigene Leiste, hängen Sie &ec_app=0 an die start_url und an START_PAGES.