Lösungen·Architektur·Live·geprüft 2026-06-07

Was öffentlich ist, liefern wir statisch aus

Öffentliche Lese-Inhalte rendern wir einmal zur Bauzeit zu fertigen HTML-Dateien — nicht bei jedem Aufruf neu auf einem Server. Was keinen Server zum Anzeigen braucht, kann auch nicht mit ihm ausfallen. Zitare ist die App, an der dieses Muster zuerst greift.

These
Für Inhalte, die öffentlich sind und für alle gleich aussehen, ist statisches Vorab-Bauen langlebiger, ausfallsicherer und günstiger als ein dynamischer Server hinter jeder Seite.
Was wir tun

Die meisten Webseiten funktionieren so: jemand ruft eine Adresse auf, ein Server wacht auf, fragt eine Datenbank, baut aus dem Ergebnis eine Seite zusammen und schickt sie zurück. Das passiert bei jedem einzelnen Aufruf neu. Für eine Bank-App, in der jede Person etwas anderes sieht, ist das richtig. Für einen öffentlichen Zitate-Korpus, in dem alle dasselbe lesen, ist es Verschwendung — und ein unnötiges Risiko.

Zitare dreht das um. Der Lese-Teil — alle Zitate, Autor:innen, Themen, Regionen, Sammlungen — wird einmal gebaut: aus der Datenbank werden zur Bauzeit JSON-Schnappschüsse gezogen, und aus denen bäckt der Build rund 520 fertige HTML-Seiten plus 109 RSS-Feeds. Diese Dateien liegen dann einfach da. Wer zitare.com aufruft, bekommt eine Datei ausgeliefert, so wie man ein Bild von einem Dateiserver lädt. Kein Server rechnet die Seite aus, weil sie schon fertig ist.

Daraus fallen drei Eigenschaften ab, die wir nicht extra bauen mussten, sondern die der Architektur gehören.

Ausfallsicherheit. Was keinen Server zum Anzeigen braucht, kann auch nicht mit ihm ausfallen. Wenn unsere GPU-Box streikt, wenn ein Datenbank-Prozess hängt, wenn beim Mitmach-Teil etwas kaputtgeht — zitare.com liest sich weiter. Der Lese-Korpus hat keinen laufenden Applikations-Prozess, den man umkippen könnte; es gibt keine Session, kein Cookie, keinen Zustand, der unter Last oder Angriff bricht. Im schlimmsten Fall ist die Seite minimal veraltet, nie offline.

Langlebigkeit. Eine Sammlung fertiger HTML-Dateien überdauert die Software, die sie erzeugt hat. Solche Dateien lassen sich umziehen, archivieren, spiegeln und in zehn Jahren noch öffnen, ohne dass irgendein Framework, irgendeine Laufzeitumgebung oder irgendeine gemietete Cloud dafür am Leben gehalten werden muss. Ein offener Korpus unter freier Lizenz, der von der nötigen Server-Komplexität gefesselt wäre, wäre nur so lange offen, wie jemand den Server bezahlt. Statisch ausgeliefert ist er das auch dann noch, wenn wir es nicht mehr sind.

Selbstkostenpreis und Achtsamkeit. Eine statische Seite kostet im Betrieb fast nichts — kein Rechenaufwand pro Aufruf, kaum Strom, beliebig cachebar. Das ist kein Detail für die Buchhaltung, sondern Teil derselben Haltung wie der Eigenbetrieb auf einer überschaubaren Maschine: wir bauen so, dass das Anzeigen von öffentlichem Wissen nicht fortlaufend Rechenzentrums-Last erzeugt, die irgendwer irgendwie refinanzieren muss.

Wichtig ist die Grenze, an der das Muster aufhört. Statisch ist nicht gratis-magisch — es passt nur dort, wo der Inhalt öffentlich ist und für alle gleich aussieht. Genau deshalb ist Zitare bei uns der Sonderfall und nicht die Regel: Es ist die einzige App, die zugleich eine öffentliche Publikation und eine interaktive Anwendung ist. Der schreibende, persönliche Teil — Zitate vorschlagen, moderieren, das eigene Konto — braucht sehr wohl einen Server mit Login, und den hat er auch (app.zitare.com). Unsere anderen Apps tragen dagegen private, personalisierte Daten; die laufen local-first oder serverseitig, nicht statisch. Das Muster ist eine scharfe Klinge für einen bestimmten Schnitt, kein Allzweckwerkzeug.

Und es ist nicht umsonst zu haben. Beide Welten — die statische Lese-Seite und die dynamische Mitmach-App — aus einer Codebase zu bauen, kostet Komplexität: ein Build-Skript muss die App-Routen während des statischen Builds beiseiteschieben, weil das Framework keinen sauberen Schalter dafür bietet. Das steht in den Spannungen, weil es ehrlich ein Workaround ist und nicht der Mechanismus, den wir uns gewünscht hätten. Wir nehmen ihn in Kauf, weil das Ergebnis es wert ist: eine öffentliche Allmende, die so gebaut ist, dass sie erreichbar bleibt — auch dann, wenn alles andere mal nicht erreichbar ist.

Umsetzung

Wie genau das funktioniert

  • Zwei Build-Ziele aus einer Codebase: der Lese-Korpus zitare.com fällt auf `adapter-static` (reine HTML-Dateien, kein Node-Prozess), die Mitmach-Oberfläche app.zitare.com auf `adapter-node`. Eine Routegroup `(read)` wird vorab zu HTML gebacken, `(app)` bleibt eine interaktive App mit Login.
  • Die Datenbank wird zur Bauzeit in JSON-Schnappschüsse exportiert. Der statische Build liest nur diese Snapshots, nie eine Live-DB — Aktualität kommt nächtlich plus über manuelle Auslöser.
  • Ergebnis sind rund 520 fertige HTML-Seiten und 109 RSS-Feeds, dazu ein clientseitiger Volltext-Index (Pagefind). Ausgeliefert von einem simplen Datei-Webserver, ohne laufenden Applikations-Prozess.
  • zitare.com setzt keine Cookies und kennt keine Session. Es gibt keinen Server-Zustand, der überlastet, kompromittiert oder fehlkonfiguriert werden könnte — es gibt schlicht keinen Server, der die Seite ausrechnet.
  • Nur der schreibende, persönliche Teil — Beitragen, Moderation, Konto — läuft als echter Server (app.zitare.com, OIDC-Login), sauber getrennt vom Lese-Korpus.
Trade-offs

Was uns das kostet

Jeder Lösung hat einen Preis. Diese hier zahlen wir bewusst — weil das, was wir damit erreichen, mit nachgelagerten Tricks nicht ehrlich zu bauen ist.

  • Statisch heißt nicht Echtzeit. Eine frische Kuratierung erscheint erst nach dem nächsten Build, nicht in der Sekunde. Für einen bewusst langsam wachsenden Korpus ist das richtig — für einen Live-Feed wäre es falsch.
  • Das Muster passt nur dort, wo Inhalt öffentlich und für alle gleich ist. Personalisierte oder private Daten — der Normalfall in unseren anderen Apps — gehören nicht hierher; die sind local-first oder serverseitig.
  • Zwei Build-Ziele aus einer Codebase kosten Komplexität: ein Build-Skript schiebt die App-Routen während des statischen Builds beiseite, weil SvelteKit 2 keinen Routen-Filter mehr bietet. Das ist ein Workaround gegen eine Framework-Grenze, kein eleganter Mechanismus — und die Stelle, die wir am ehesten irgendwann sauberer ziehen.
Diagnose

Welche Probleme dieser Lösung adressiert

Diagnose ohne Antwort ist Lamentation; Antwort ohne Diagnose ist Ingenieurs-Spielerei. Hier verbindet sich beides.

Stand

Offene Punkte

  • Bislang ist das Muster nur bei Zitare voll ausgereizt. Welche weiteren öffentlichen Lese-Oberflächen (Inhalts-, Vergleichs-, Korpus-Seiten) davon profitieren, ist nicht systematisch durchdeklariert.
  • Ein rein statisches zitare.com ließe sich trivial an mehreren Orten spiegeln (CDN, zweiter Host) — die echte Off-Site-Redundanz dafür ist noch nicht eingerichtet.

Lösungen sind die strukturellen Antworten des Vereins. Werte sagen, wonach wir uns ausrichten; Probleme sagen, wogegen; Lösungen sagen, was wir daraus baulich machen.