So funktioniert FolioPress: eine Seite, jede Sprache

Die meisten Websites sprechen nur eine Sprache. Sobald ein Unternehmen eine zweite braucht, wird es umständlich: ein nachträglich angeflanschtes Übersetzungs-Plugin, eine zweite Kopie der Website, die synchron gehalten werden muss, oder ein teurerer Tarif, nur um „Hello“ neben „Hallo“ zu schreiben.

FolioPress ist aus der umgekehrten Idee entstanden: Eine Seite ist keine Seite mit angehängten Übersetzungen – sie ist alle ihre Sprachen zugleich. Der Titel, die Adresse, das Menü, die Tags, das, was Google sieht: Alles existiert in jeder Sprache, die du auswählst, und zwar von der ersten Minute an.

Angefangen hat alles mit dem Wunsch, ein selbst gebautes CMS zu ersetzen, dem mehrere Sprachen nachträglich aufgepfropft worden waren. Bei der Suche nach Alternativen habe ich festgestellt, dass sie alle kläglich daran scheiterten, mehrsprachige Inhalte in einen einfach zu bedienenden Website-Baukasten zu integrieren.

Dies ist ein Blick hinter die Kulissen: wie alles zusammenpasst, welche Abwägungen dahinterstecken und was unterwegs kaputtgegangen ist.

Aus der Vogelperspektive

Wie ein Besuch eine FolioPress-Website erreicht: Besucher, Cloudflare, Eingangstür, dann das Mehrfamilienhaus oder das eigene Haus einer Website; der Kontrollturm und die nächtlichen Backups stehen daneben

Ein Besuch läuft von links nach rechts. Der Kontrollturm steht nie auf diesem Weg. Er baut, verlegt und betreut die Websites nur.

Ein Besuch auf einer FolioPress-Website durchläuft drei Stationen. Cloudflare fängt ihn in der Nähe des Besuchers ab und filtert Müll heraus. Eine Eingangstür – Traefik, das sich auch um die HTTPS-Zertifikate kümmert – schaut sich die Adresse an und findet die richtige Website. Die Website selbst antwortet, entweder aus einem Mehrfamilienhaus, in dem viele kleine Websites Tür an Tür wohnen, oder aus einem eigenen Haus, wenn bei ihr viel los ist. Abseits davon kümmert sich ein Kontrollturm um alles, was ein Besucher nie sieht: Anmeldungen, Abrechnung, Webadressen, Updates und Gesundheitschecks, mit einem kleinen Hausmeister auf jedem Server, der die Websites startet und stoppt.

Das Ganze ist Python, in drei Schichten, die ich selbst geschrieben habe: ein Web-Framework (pystarter, auf Basis von FastAPI), darauf das Content-Management-System (foliopress) und der Kontrollturm (Forestry). Zahlungen laufen über Polar, das als Verkäufer gegenüber den Kunden auftritt (Merchant of Record) – die europäische Mehrwertsteuer ist also deren Kopfschmerz und nicht meiner.

Jede Seite, jede Sprache

Eine Seite mit einer Adresse pro Sprache: Englisch /about, Portugiesisch /pt-pt/sobre, Deutsch /de-de/ueber-uns, optional auf eigenen Domains

Jede Sprache bekommt ihre eigene, gut lesbare Adresse und, wenn du willst, ihre eigene Domain.

Wenn eine Pension in Lissabon ihre „Über uns“-Seite schreibt, schreibt sie sie einmal pro Sprache, und jede Fassung bekommt eine Adresse, die sich in dieser Sprache natürlich liest. Unter der Haube ist eine Übersetzung ein Datensatz neben dem Original, keine Kopie der Seite: Titel, Adressen, Menüs, Tags und Beschreibungen für Suchmaschinen gibt es jeweils pro Sprache. Besucher landen automatisch in ihrer Sprache, und Google erfährt genau, welche Seite welche ist (über hreflang-Tags und eine Sitemap pro Sprache), sodass die portugiesische Fassung bei portugiesischen Suchanfragen erscheint. Du willst die deutsche Website auf einer eigenen .de-Domain? Geht auch. Und wenn du lieber nicht mit einer leeren Seite anfangen möchtest, ist ein KI-Erstentwurf jeder Übersetzung nur einen Klick entfernt – du prüfst ihn, du entscheidest.

Jede Website speichert all das in ihrer eigenen kleinen Datenbankdatei (SQLite). Es gibt keinen Datenbankserver, den man betreiben müsste, das nächtliche externe Backup ist eine einfache Dateikopie, und eine komplette Website ist ein einziger Ordner, der sich in einem Rutsch verschieben, wiederherstellen oder löschen lässt.

Kleine Websites teilen sich ein Haus, gut besuchte ziehen aus

Die meisten Websites sind fast immer ruhig. Jeder ihre eigene Maschinerie zu geben, wäre, als würde man ein ganzes Haus für jemanden mieten, der zweimal im Jahr vorbeikommt. Deshalb teilen sich kleine Websites ein Mehrfamilienhaus – ein Prozess, streng getrennte Daten –, und die Plattform beobachtet den Traffic jeder Website. Wird bei einer viel los, zieht sie in ein eigenes Haus. Wird es ein paar Wochen lang ruhiger, zieht sie zurück.

Ein gemeinsames Haus funktioniert nur, wenn die Wände solide sind. Kommt eine Anfrage an, schaut der gemeinsame Prozess auf die Adresse und wechselt dann nur für diese eine Anfrage auf die Datenbank und die Einstellungen dieser Website (mit Pythons contextvars), sodass eine Website nie die Daten einer anderen sieht. Der Container jeder Website hat außerdem sein eigenes privates Netzwerk mit einer Firewall für ausgehenden Verkehr. Und weil Betreiber ihre eigenen Themes hochladen und bearbeiten können, laufen diese Templates in einer Sandbox mit Limits für Laufzeit, Ausgabe und Speicher.

Was als „gut besucht“ gilt, hat ein paar Anläufe gebraucht. Ein einzelner Newsletter oder ein Crawler, der kurz durchdreht, sollte eine Website nicht beliebt aussehen lassen. Deshalb ist der Wert der typische Tag der Website in der letzten Woche (der Median) plus das, was in ihren Stoßzeiten los ist (die Stunde beim 95. Perzentil) – so fällt die Handvoll wildester Stunden heraus.

Umzugstag, ohne den Laden zu schließen

Ein Update ohne Ausfallzeit in fünf Schritten: bauen, prüfen, öffnen, auslaufen lassen, abschalten

Jedes Update und jeder Umzug zwischen dem Mehrfamilienhaus und einem eigenen Haus passiert, während die Website weiter Besucher bedient. Ingenieure nennen das ein Blue/Green-Deployment: Eine neue Kopie der Website startet neben der alten, und erst wenn sie gesund ist, schickt die Eingangstür Leute zu ihr. Zwei Details machen das wirklich nahtlos. Der Hausmeister geht nicht einfach davon aus, dass die Eingangstür umgeschaltet hat, sondern prüft die Zahlen der Eingangstür selbst, bis es so weit ist. Und die alte Kopie wird zuerst angewiesen, „auszulaufen“: Sie nimmt keine neuen Besucher mehr an, erledigt, was sie gerade tut, und schaltet sich erst dann ab.

Was kaputtging

Nichts lehrt dich so viel über ein System, wie ihm beim Scheitern zuzusehen. Ein paar Lieblingsgeschichten:

Als ich alles selbst lahmgelegt habe. Damit eine langsame Seite nicht alle anderen aufhält, habe ich das Rendern der Seiten auf einen Pool von Worker-Threads verlagert. Um dann zu prüfen, ob Website-Umzüge ohne Ruckler ablaufen, habe ich eine Flut von Testtraffic auf die Live-Websites losgelassen. Sie froren neun Minuten lang ein und blieben auch eingefroren, nachdem der Traffic aufgehört hatte. Jede Seite hielt eine Datenbankverbindung fest, während sie eine zweite anforderte, und bei Dutzenden Threads und einem Pool von fünfzehn Verbindungen wartete am Ende jeder Thread auf einen anderen. Währenddessen meldete der Gesundheitscheck, der die Datenbank nie anrührte, fröhlich, alles sei in Ordnung.
Die Lösung: Datenbank-Pools, die sich nicht gegenseitig blockieren können, ein Limiter, der eine Überlastung in ein schnelles „Versuch's gleich noch mal“ statt in einen Hänger verwandelt (mit einem fairen Anteil für jede Website, damit eine gut besuchte Website die anderen nicht aushungern kann), ein Gesundheitscheck, der einen hängenden Prozess bemerkt, ein Watchdog, der ihn neu startet, und eine feste Regel, wo Lasttests stattfinden.

Der 45-Sekunden-Schluckauf. Die meisten Website-Umzüge liefen nahtlos, eine Art aber nicht: Wenn der Umzug die Routing-Regeln der Website änderte, beschrieben die alte und die neue Kopie kurzzeitig dieselbe Route auf zwei verschiedene Weisen. Die Eingangstür konnte sich nicht entscheiden, wem sie glauben sollte, und bediente etwa 45 Sekunden lang keine von beiden. Alles andere am Umzug war korrekt – genau deshalb hat es eine Weile gedauert, den Fehler zu finden.

Eine winzige Website bei 100 % CPU. Ein kleiner Shop mit ein paar Hundert Besuchern am Tag lastete ständig seinen Prozessor voll aus. Schuld waren nicht die Kunden, sondern automatisierte Scanner, die mehr als hundert Anfragen in zehn Sekunden abfeuerten, auf der Suche nach Dateien wie /.env oder /wp-login.php, die eine FolioPress-Website nie hat. Jede bekam eine schöne, komplett gestaltete „Seite nicht gefunden“: etwa 70 ms Arbeit, hundertfach.
Die Lösung: Diese Anfragen bekommen jetzt ein neun Byte großes „Not found“, bevor irgendeine echte Arbeit passiert, und Cloudflare weist die meisten schon ab, bevor sie überhaupt ankommen.

„Server nicht gefunden“ für eine Website, die lief. Die Webadresse einer brandneuen Website wird bei der Einrichtung angelegt. Hat irgendetwas den Namen einen Moment zu früh nachgeschlagen, merkten sich Internetanbieter bis zu dreißig Minuten lang „diesen Namen gibt es nicht“, obwohl die Website einwandfrei funktionierte. Die Lösung war eine Wildcard-Adresse, sodass jeder Name unter foliopress.io immer existiert; Namen ohne Website bekommen eine freundliche „Hier gibt es keine Website“-Seite.

Nachbarn. Viele Websites Tür an Tür zu hosten, wirft Fragen auf, die sich eine einzelne Website nie stellen muss: was der Code einer Website einer anderen antun kann und was eine Website für sich beanspruchen kann, das ihr nicht gehört. Ein Sicherheitsreview vor dem Start der Registrierung hat ein paar solcher Fälle zutage gefördert. Alle sind behoben: strengere Cookies, ein Eigentumsnachweis für eigene Domains und jede Website in ihrem eigenen, separaten Ordner eingehängt, sodass keine Website an die Dateien einer anderen herankommt.

Was ich jemandem raten würde, der gerade anfängt

  • Langweilige, bewährte Bausteine bringen dich weit. SQLite, ein einziges Framework, schlichte Container. Die meisten schwierigen Probleme steckten in den Nahtstellen dazwischen.
  • Erst messen, dann glauben. Der Traffic-Wert, der Ausfall und die CPU-Spitzen sahen anfangs alle nach etwas anderem aus.
  • Für die stille Mehrheit bauen. Die meisten Websites sind klein. Dass Kleines günstig ist, macht das Ganze überhaupt erst möglich.

Wie es weitergeht

Social-Media-Publishing, Mermaid-Diagramme – vor allem aber warte ich auf das Feedback der Nutzer, das die weitere Entwicklung prägen soll.

Wenn du eine Website hast, die mehr als eine Sprache sprechen soll, probier FolioPress auf foliopress.io aus – die ersten 30 Tage sind kostenlos. Und wenn du eher technisch unterwegs bist: Ich würde gern hören, wie du es gebaut hättest.