Projekte
Migration: 20 Jahre Joomla nach Astro + Directus.
Eigenprojekt30. Juli 2026
- Bereich
- Tourismus
- Umsetzung
- Juni bis Juli 2026 bis zum Cutover, danach laufend
- Kompetenz
- Webentwicklung · SEO
- Stack
- Astro · Directus · PostgreSQL · Cloudflare · Python · Docker · n8n · Git
Ergebnis
- 11.660 statische Seiten in vier Sprachen aus einem EOL-CMS von 2005
- Cutover ohne Rankingverlust: legacy-URLs 1:1 auf neue IDs abgebildet
- Bildauslieferung 31 bis 57 Prozent leichter durch Format-Verhandlung im Browser
- Maschinenlesbare Tour-API und llms.txt für KI-Suche — vor der Konkurrenz
- HTML-Fehler über den ganzen Katalog von 1.285 auf 21 gesenkt
In vier Sätzen: Ein Reiseveranstalter betreibt seit 2005 ein Portal auf Joomla 1.0 — einem CMS, dessen letzter Sicherheitspatch rund fünfzehn Jahre zurückliegt, mit 2.094 Artikeln, 11.247 Übersetzungszeilen und vier Sprachen in gemischten Zeichenkodierungen. Daraus ist eine Headless-Architektur geworden: Inhalte in Directus, Ausgabe als statisches HTML über Astro, ausgeliefert von Cloudflare — 11.660 Seiten, die ohne Datenbankabfrage im Auslieferungsweg entstehen. Der schwierige Teil war nicht der neue Stack, sondern die Daten: In zwanzig Jahren sammelt ein CMS Eigenheiten, die kein Import-Skript erwartet, und ein falsch verstandenes Bildfeld oder eine stillschweigend abgeschnittene Redirect-Datei kostet mehr Sichtbarkeit als jeder Relaunch-Fehler im Design. Dieser Text beschreibt die Architektur, die Datenfallen und die Automatisierung drumherum — ausführlich, weil genau diese Details den Unterschied machen.
Hinweis: Dies ist ein eigenes Projekt, das ich baue und betreibe — kein Kundenauftrag. Alle Zahlen stammen aus dem laufenden System, nicht aus einer Planung. Wo Wissensbasis und Code auseinanderliefen, steht der Code.
Die Ausgangslage: ein CMS von 2005
Das alte Portal lief auf Joomla 1.0.12. Diese Version stammt aus 2006, ist
seit Jahren End-of-Life und bekommt keine Sicherheitspatches mehr. Sie lief auf
einem klassischen Shared-Hosting mit PHP und MySQL, alle Inhalte in Tabellen mit
dem Präfix jos_.
Was in zwanzig Jahren zusammenkam:
| Tabelle | Zeilen | Was drin ist |
|---|---|---|
jos_content |
2.094 | Artikel: Programme, Berichte, Ratgeber, Nachrichten |
jos_jf_content |
11.247 | Übersetzungen (JoomFish), teils Maschinenübersetzung von ~2010 |
jos_eventlist_locate |
138 | die Touren — nicht als Artikel, sondern als Datensätze |
jos_eventlist_dates |
317 | Termine mit Preis und Guide |
jos_eventlist_categories |
23 | Reiseziele |
jos_content2rez |
2.344 | Altbestand eines Buchungssystems — nicht importieren |
Dazu ein Zoo aus Erweiterungen, von denen jede ihr eigenes Datenformat
mitbringt: EventList für den Tourkalender, JoomFish für
Mehrsprachigkeit, ein selbstgebauter SEF-Patch für die URLs, FacileForms
für Anfragen, dazu JCE, JComments und JoomMap. Im Text stecken Shortcodes wie
{mosimage} oder {events_list/get_dates/locid=X}, die ein Import-Skript
verstehen muss, sonst landen sie als sichtbarer Müll auf der neuen Seite.
Das eigentliche Problem war nicht die Technik, sondern was sie unmöglich machte. Ein Artikel war eine Textwand: Programm, Leistungen, Preise, Termine und Bilder als ein HTML-Feld. Für vier Sprachen heißt das vier Textwände, die auseinanderlaufen — jemand korrigiert den Preis im ukrainischen Text und vergisst den deutschen. Und die Frage «welche Touren haben keinen gültigen Preis?» ist gegen eine Textwand nicht beantwortbar.
Warum Astro und Directus
Die naheliegende Option wäre WordPress mit ACF und WPML gewesen. Drei der vier regionalen Wettbewerber fahren genau das. Genau deshalb nicht: Ein WordPress-Stack hätte dieselbe Kopplung von Inhalt und Ausgabe reproduziert, nur in moderner Verpackung, und die Ladezeiten wären im Feld der Konkurrenz geblieben.
Die Entscheidung fiel auf Headless: Inhalte in einem CMS, das nichts über die Darstellung weiß, Ausgabe als vorab generiertes HTML.
- Directus als CMS auf eigenem Server, PostgreSQL darunter. Es erzeugt keine Seiten, es hält strukturierte Daten und liefert sie über eine API.
- Astro als Generator: Er zieht die Inhalte beim Build und schreibt fertige HTML-Dateien. Im Auslieferungsweg gibt es danach kein PHP und keine Datenbank.
- Cloudflare Pages als Auslieferung: statische Dateien vom Content-Delivery-Netz.
Der Nebeneffekt ist der wichtigste: Wenn der CMS-Server ausfällt, bleibt die Seite online. Sie ist ja nur eine Sammlung von Dateien. Ein Angriff auf das CMS erreicht die öffentliche Seite gar nicht, weil dort nichts läuft, was man angreifen könnte.
Die Zielarchitektur
Vier Bausteine, die bewusst getrennt sind, damit jeder für sich ausfallen oder umgezogen werden kann.
Ein Detail, das sich erst im Betrieb zeigte: Der Build holt aus Directus nur JSON. Bilder lädt der Browser des Besuchers direkt, aus R2 über den CDN-Cache. Das klingt banal, korrigierte aber eine falsche Annahme aus der Planungsphase — nämlich dass R2 den Datenverkehr beim Bauen spare. Tut es nicht, es gibt beim Bauen keinen Bildverkehr. Der echte Grund für R2 ist ein anderer und besserer: Der VPS bleibt praktisch zustandslos, das Backup ist ein Datenbank-Dump, und das Fotoarchiv hängt nicht an der Festplatte einer Maschine, die man neu aufsetzen können muss.
Das Datenmodell und ein Fund, der alles einfacher machte
Vor dem ersten Import stand ein Audit des Datenbank-Dumps. Der Fund dort war der wertvollste Moment des ganzen Projekts.
Die Annahme war: Touren sind Artikel, also Textwände, die geparst werden müssen.
Falsch. Touren lagen in jos_eventlist_locate als strukturierte Datensätze
— mit getrennten Feldern für Name je Sprache, Beschreibung, Preis, Schwierigkeit,
Dauer, Kilometer, Sammelort und vier Bildslots. Das heißt: Der frühere
Betreiber hatte vor fünfzehn Jahren eine sauber modellierte Struktur angelegt,
die fast 1:1 in ein modernes Schema passt.
Das änderte den Aufwand des Kernstücks von «alles aus Prosa rekonstruieren» auf
«Felder abbilden und nur das Programm parsen». Die Verbindung zwischen beidem
war unscheinbar und entscheidend: Das Feld url im Tour-Datensatz enthält keine
Adresse, sondern die ID des Artikels mit dem Tagesprogramm.
Die Abbildung ins neue Schema:
| Directus-Sammlung | Quelle in Joomla |
|---|---|
destination (Reiseziel) |
jos_eventlist_categories |
tour |
jos_eventlist_locate |
tour_day (Tagesetappe) |
jos_content + Übersetzungen, geparst nach «Tag N» |
tour_departure (Termin) |
jos_eventlist_dates |
report (Reisebericht) |
jos_content nach Kategorie |
guide |
Kategorie «Instruktoren», verknüpft über die Termine |
Für die Verknüpfung gilt eine Regel, die ich inzwischen in jedem Migrationsprojekt
anwende: keine harten Zuordnungen im Code, sondern Herkunfts-IDs in den Daten.
Jeder Datensatz trägt seine alte ID mit — tour.legacy_locid,
guide.legacy_id, report.public_id. Damit ist jeder Import wiederholbar, jede
Prüfung gegen die Quelle möglich, und die URL-Umleitung später trivial.
Der ETL-Pfad
Extraktion, Umformung, Laden — der Teil, in dem die eigentliche Arbeit steckt. 85 Python-Skripte, gewachsen über die Migration, mit gemeinsamen Bibliotheken für die Dinge, die alle Inhaltstypen brauchen.
Am Ende standen in der neuen Datenbank: 135 Touren mit 980 Tagesetappen, 299 Reiseberichte, 744 Journalbeiträge, 191 Ratgeber, 520 Sehenswürdigkeiten, 25 Reiseziele, 11 Guides — und rund 10.600 Fotos.
Kodierung: die Hölle der ersten Woche
Der erste Import produzierte Text wie рук statt Wörtern. Die
Ursache war ein Dreifach-Problem, das in dieser Kombination typisch für alte
CMS ist:
- Die Tabellen hatten gemischte Zeichenkodierung — ein Teil in cp1251 (Windows-Kyrillisch), ein Teil in UTF-8. Nicht pro Datenbank, sondern pro Tabelle, teils pro Spalte.
- Die Übersetzungstabelle enthielt Text zusätzlich als HTML-Entities, weil JoomFish so gespeichert hat.
- Der Dump selbst behauptete eine Kodierung, die nicht für alle Tabellen stimmte.
Die Lösung ist eine feste Reihenfolge, und die Reihenfolge ist nicht beliebig: erst aus cp1251 dekodieren, dann HTML-Entities auflösen, dann als UTF-8 schreiben. Wer die ersten zwei Schritte tauscht, bekommt Text, der aussieht wie Text, aber an den Rändern kaputt ist — und das fällt bei 11.247 Übersetzungszeilen erst auf, wenn ein Leser sich beschwert.
Daraus wurde eine Regel für alle folgenden Importe: Rohwerte immer als Text
lesen, nie als Zahl. Sonst wird aus der Artikelnummer 001234 die Zahl
1234, und die Verknüpfung zur alten Seite ist weg.
Bilder: der Slot, der beinahe verloren ging
Jede Tour hatte in Joomla vier Bildfelder: img_s, img_m, img_b, img_v.
Die naheliegende Lesart: small, medium, big, very big — also dieselbe
Aufnahme in vier Größen. Dann importiert man die größte und lässt den Rest
weg.
Ein Blick in die Daten zeigte etwas anderes. s, m und b sind tatsächlich
Größen desselben Querformats. img_v ist kein Format, sondern ein anderes
Bild — ein bewusst hochkant beschnittenes Titelbild für schmale Displays, mit
eigener Komposition. Vorhanden bei 64 von 138 Touren.
Wer der naheliegenden Lesart folgt, verliert 64 handgemachte mobile Titelbilder
und merkt es nie, weil «es gibt ja ein Titelbild». Im neuen Schema wurde daraus
ein eigenes Feld cover_mobile, und die Tourseite entscheidet per
Art-Direction, welches Bild sie zeigt.
Die Auslieferung selbst läuft nicht über den Build. Directus hat eine
Assets-API, die Bilder auf Anfrage skaliert und das Format aushandelt:
Der Browser sagt im Accept-Header, ob er AVIF versteht, und bekommt AVIF,
sonst WebP, sonst JPEG. Gemessen an derselben Aufnahme: −31 Prozent bei
Vorschaubildern, −40 Prozent bei 1.280 Pixeln, −57 Prozent bei
Großansichten.
Die Alternative wäre gewesen, alle Formate beim Bauen zu erzeugen. Bei rund 10.600 Fotos × mehreren Breiten × drei Formaten wären das über 80.000 zusätzliche Dateien im Build und eine Bauzeit von Minuten auf Stunden — für exakt dasselbe Ergebnis in Bytes. Diese Entscheidung ist der Grund, warum der Build heute knapp zwei Minuten dauert und nicht eine halbe Stunde.
Eine harte Regel kam noch dazu: Dateinamen nur in ASCII. Kyrillische oder umlautbehaftete Dateinamen überleben den Weg durch S3-Signaturen, CDN-Caches und Content-Disposition-Header nicht zuverlässig.
URLs: wo Sichtbarkeit wirklich verloren geht
Bei einem Relaunch entscheidet nicht das Design über den Traffic danach, sondern ob die alten Adressen ankommen. Das alte Portal hatte drei Muster, die Google seit Jahren indexiert hatte:
/{sprache}/a/{id}/— Artikel, die real indexierte Seite/{sprache}/c/{id}/— Kategorie/{sprache}/s/{id}/— Sektion
Der erste Glücksfall: Weil im neuen Schema public_id gleich der alten
Artikel-ID ist, ist die Umleitung für Artikel trivial — /uk/a/1655 wird
/uk/1655. Gleiche Nummer, nur das /a/ fällt weg.
Der erste Stolperstein steckt direkt daneben: /c/16 und /a/16 sind
verschiedene Seiten. Kategorien und Artikel haben getrennte Nummernkreise, die
sich überschneiden. Eine Umleitung «nach Nummer» hätte Besucher auf inhaltlich
fremde Seiten geschickt — mit korrektem HTTP-Status, also unbemerkt. Kategorien
brauchen eine explizite Zuordnungstabelle.
Ein weiterer Fehler war ein Ein-Zeichen-Problem mit großer Wirkung: Die erste
Regelversion leitete /ua/a/{id} auf /ua/{id}. Nur existiert /ua/ auf der
neuen Seite nicht — die ukrainische Sprache heißt dort uk. Jede Umleitung
landete im Nichts. Solche Fehler findet man nicht durch Nachdenken, sondern indem
man jedes Ziel gegen den echten Build prüft.
Genau das deckte den nächsten Fall auf: Die Kategorie-Umleitungen zeigten auf Themen-Hubs, die im ursprünglichen Plan standen, aber nie gebaut wurden. Eine Validierung aller Ziele gegen die generierten Dateien ergab 32 von 44 Zielen im 404. Nach der Korrektur: 661 Ziele, null Fehler.
Der teuerste Fehler des Projekts
Cloudflare Pages liest Umleitungen aus einer Datei _redirects. Die
Dokumentation nennt großzügige Grenzen. Die Realität: ab etwa Regel 120 wird
der Rest stillschweigend verworfen. Kein Fehler beim Deploy, keine Warnung —
die Regeln existieren einfach nicht.
Über Monate wuchs die Datei, weil auch Umleitungen von zusammengeführten Duplikaten dort landeten: 59 Paare mal vier Sprachen sind 236 Zeilen. Insgesamt rund 240 Regeln. Alles über der Grenze war unwirksam — und dort standen die legacy-Joomla-Regeln, also genau die Adressen mit dem realen Suchmaschinen-Traffic. Sie lieferten 404, bei laufendem Besucherstrom.
Aufgefallen ist es durch einen Zufall: In einem Session-Recording-Werkzeug tauchte
ein Fehlerbericht für die Adresse /ru/a/2032 auf. Ein Besucher war über eine
alte Adresse gekommen und ins Leere gelaufen. Die Grenze selbst wurde danach
durch Bisektion gemessen — Regel 124 wirkt, Regel 125 ist weg.
Die Lehre ist nicht «Cloudflare ist schuld». Sie ist: Wenn ein System eine Grenze hat und beim Überschreiten schweigt, muss die Grenze im eigenen Prozess sichtbar werden. Heute ist das Zählen der Regeln Teil des Deploys, und Dedup-Umleitungen stehen in einem Skript, das sie am Edge synchronisiert und danach gegen das Live-System verifiziert.
Vier Sprachen, und was «übersetzt» wirklich heißt
Das alte System hatte Englisch und Deutsch mit beeindruckenden Zahlen: 807 englische und 1.052 deutsche Artikel. Ein Blick in die Inhalte beendete die Freude — es waren eingefrorene Maschinenübersetzungen aus den 2010er Jahren. Die Zahlen hatten sich seit einem Dump von 2019 kaum verändert.
Die Entscheidung: nicht übernehmen, neu übersetzen. Mit einer Ausnahme, die Aufmerksamkeit brauchte — bei einigen sehr alten Artikeln war die englische Version kein Übersetzungstext, sondern anderes Material. Solche Fälle erkennt man an Länge und Ähnlichkeit und legt sie einem Menschen vor, statt sie zu überschreiben.
Der Sprachstand steht heute in den Daten selbst: Der Detektor im Import hat für jeden Datensatz festgehalten, welche Sprache das Original ist. Damit muss keine späte Pipeline mehr die alte Seite befragen — die Antwort liegt in der Datenbank.
Was Mehrsprachigkeit in einem strukturierten Modell wirklich bedeutet, zeigt ein einzelnes Feld: Visa- und Einreisehinweise sind keine Übersetzung. Für ukrainische und deutsche Gäste gelten unterschiedliche Regeln — das ist zielgruppenspezifischer Inhalt, der zufällig in verschiedenen Sprachen steht. Ein normales Übersetzungsfeld wäre hier die falsche Struktur und würde über Jahre falsche Auskunft geben.
Bei der Korrektur half ein Sprachmodell, aber nicht als Übersetzer, sondern als Korrektor: Durchlauf über bestehende ukrainische Tourtexte, Suche nach Russizismen, Grammatik- und Zeichensetzungsfehlern, Vorschläge zur Prüfung. Ergebnis: 107 Touren, 2.363 angenommene Korrekturen. Der Weg ist auf andere Sprachen und Inhaltstypen übertragbar, weil er auf Feldern arbeitet und nicht auf Textwänden.
Datenqualität: was zwanzig Jahre anrichten
Drei Fälle, die zeigen, warum eine Prüfschicht kein Luxus ist.
Der falsche Krim-Bestand. Ein früherer Import hatte Kopien ausländischer Objekte angelegt und ihnen die Region «Krim» zugewiesen — Orte in Norwegen, der Türkei, Georgien. Rund 42 Duplikate wurden zu Kanons zusammengeführt und den richtigen Regionen zugeordnet. Solche Fehler sind aus dem CMS heraus unsichtbar, weil jede einzelne Seite plausibel aussieht. Sichtbar werden sie erst in einer Auswertung über den gesamten Bestand.
Die Verknüpfungsquote. Von 299 Reiseberichten ließen sich 154 automatisch einer Tour zuordnen — über ein Signal im Text, nämlich einen Link auf den Tourartikel. Die restlichen 145 blieben ohne Zuordnung. Wichtig ist die Haltung dahinter: nicht raten. Ein Bericht ohne Tour ist ein sauberer Zustand; ein Bericht an der falschen Tour ist eine Falschaussage im Verkauf.
Der Validierungsverlauf. Der HTML-Prüfer läuft über den kompletten Katalog. Sein Verlauf ist die ehrlichste Kurve des Projekts:
| Stand | HTML-Fehler | Was passierte |
|---|---|---|
| Ausgangsmessung | 1.285 | Joomla-Altlast: <font>-Tags, offene Listen, Word-Reste |
| erste Bereinigung | 80.231 | ein Ausdruck für Attribut-Abstände zerstörte alle Attributwerte |
| nach dem Rückbau | 678 | Fehler behoben, dazu FAQ-Bereinigung |
| Serie kleiner Fixes | 398 | <font> entfernt, Slider-Reste entfernt |
| weiter | 49, dann 21 | Zeichensetzung in Style-Attributen, Inline-Tag-Balance |
| nach Massenübersetzung | 37 | neue Übersetzungen brachten neue offene Tags mit |
| aktuell | 21 | Rest sind tiefe Kaskaden aus dem Alt-HTML |
Der Sprung auf 80.231 ist die Stelle, die ich in jedem Rückblick zeige. Eine einzelne Ersetzungsregel, gedacht für Kosmetik, hat in einem Durchlauf jedes HTML-Attribut im Katalog beschädigt. Sichtbar wurde das nur, weil die Validierung bei jedem Commit automatisch läuft und eine Zahl ausgibt. Ohne diese Zahl wäre der Schaden mit dem nächsten Deploy live gegangen.
Ein zweiter Fall aus derselben Serie ist lehrreicher, als er klingt: Eine Bereinigungsregel sollte fehlplatzierte Listenelemente aus Absätzen entfernen. Ihr Muster griff über den Absatz hinaus, verschluckte eine komplette verschachtelte Liste und entfernte deren Listenelemente mit. Ein nachgelagerter Schritt fügte danach neue, falsche Elemente hinzu. Die Korrektur bestand darin, verschachtelte Blöcke vor dem Bereinigen durch Platzhalter zu schützen — und nichts zu ändern, wenn danach kein Ziel mehr übrig ist.
Automatisierung: was ohne Zutun läuft
Ein Projekt dieser Größe ist mit Handarbeit nicht zu halten. Was heute von selbst passiert:
- Bei jedem Commit baut GitHub Actions die komplette Seite, crawlt alle internen Links auf Bruchstellen und validiert das HTML. Der Linkcheck läuft gegen die gebaute Seite, nicht gegen eine Vorschau — er sieht also genau das, was Besucher sehen.
- Nachts holt ein Skript die Zahlen aus der Google Search Console in die Projektdatenbank. Daraus entsteht das Dashboard, das ich in einem eigenen Projekt beschrieben habe.
- Ein Monitor prüft die Container auf dem Server, ein zweites Skript legt täglich einen Datenbank-Dump ab. Weil die Bilder in R2 liegen und nicht auf der Festplatte, ist dieser Dump das vollständige Backup.
- Anfragen aus dem Formular gehen in die Datenbank und parallel als Benachrichtigung heraus — ohne Umweg über ein Postfach, in dem sie liegenbleiben könnten.
- n8n trägt die Abläufe, die zwischen Systemen vermitteln: Übersetzungs- und Anreicherungsläufe, geplante Rebuilds.
Ein Punkt, der über Zeitersparnis hinausgeht: Der Rebuild ist vom Inhalt entkoppelt. Wer im CMS etwas ändert, ändert nicht sofort die Website — es braucht einen Build. Das klingt nach einem Nachteil und ist ein Sicherheitsnetz: Zwischen Redaktion und Öffentlichkeit liegen Prüfschritte, die kaputtes HTML und tote Links abfangen, bevor sie jemand sieht.
Maschinenlesbar: die Seite für KI-Suche vorbereiten
Dieser Teil ist der jüngste und aus meiner Sicht der unterschätzteste. Immer mehr Anfragen erreichen Websites nicht über eine Ergebnisliste, sondern über ein Sprachmodell, das im Auftrag eines Nutzers liest. Solche Zugriffe führen kein JavaScript aus, klicken keine Filter und interpretieren kein Layout.
Drei Dinge folgen daraus, und alle drei sind umgesetzt:
Erstens: der Inhalt steht im HTML. Weil die Seiten statisch generiert sind, ist jeder Text ohne Skriptausführung vorhanden. Was für Geschwindigkeit gebaut wurde, zahlt sich hier ein zweites Mal aus.
Zweitens: eine llms.txt. Eine kurze, maschinenlesbare Beschreibung, was die
Seite ist, welche Sprachen es gibt, wie die Adressen aufgebaut sind und wo die
Hauptbereiche liegen — mit Links. Das ist kein Ranking-Trick, sondern eine
Landkarte für Leser, die keine Navigation sehen.
Drittens: eine echte API. Die Touren gibt es zusätzlich als saubere JSON-Endpunkte, pro Sprache eine Liste und pro Tour ein Detaildokument. Bewusste Entscheidungen darin:
- Nur buchbare Touren. Archivierte Routen bleiben auf der Website, aber außerhalb der API — ein Agent soll nur sehen, was man tatsächlich buchen kann.
- Drei Sprachen statt vier. Russisch ist Archivbestand, also nicht in der maschinenlesbaren Ausgabe.
- Ein einziger Abruf beim Bauen. Alle Endpunkte teilen sich denselben Datensatz, gecacht über den Build hinweg, statt pro Datei erneut abzufragen.
- Textfelder ohne HTML. Was ein Modell liest, soll Prosa sein, nicht Markup.
Das Ergebnis ist eine Website, die dreifach lesbar ist: als Seite für Menschen, als HTML für Suchmaschinen, als strukturiertes JSON für Agenten. Der Aufwand dafür war ein Bruchteil dessen, was das Design gekostet hat.
Second Brain: wie ein Projekt dieser Größe koordiniert bleibt
Der Teil, den man in Projektberichten selten sieht, und ohne den die Migration nicht funktioniert hätte. Über Monate hinweg trifft man hunderte Entscheidungen, von denen jede später eine Frage aufwirft: Warum eigentlich so? Wenn diese Antworten in Chatverläufen liegen, sind sie verloren.
Deshalb hat das Projekt eine Wissensbasis im Repository — reines Markdown, 126 Dateien, nach dem Muster einer LLM-Wiki. Ihr Aufbau ist absichtlich einfach:
- Ein Eingangskorb. Neue Dokumente, Exporte, Analysen landen in einem Ordner
IN/. Der Satz «verarbeite den Eingang» genügt, damit die Inhalte gelesen, einsortiert, verlinkt und archiviert werden. - Unveränderliche Quellen. Alles Rohmaterial — Datenbank-Dumps, Suchkonsolen-Exporte, Konkurrenz-Analysen — liegt separat und wird nie bearbeitet. Zitate zeigen darauf.
- Gepflegte Seiten mit Querverweisen. Domänenmodell, Migration, SEO, Technik, Wettbewerber, Entscheidungen, Glossar. Jeder Begriff bekommt beim ersten Auftreten eine eigene kurze Seite.
- Ein Entscheidungsjournal. Datum, Entscheidung, Begründung. Das ist der Teil, der ein halbes Jahr später den größten Wert hat.
- Ein Ingest-Log. Append-only, damit nachvollziehbar bleibt, wann welche Quelle eingearbeitet wurde.
Der praktische Nutzen zeigt sich in genau der Situation, in der dieser Text entstanden ist: Für den Rückblick musste ich nicht rekonstruieren, was passiert ist — die Zahlen, die Fehlschläge und die Begründungen standen dokumentiert da.
Und noch etwas, das mir wichtig ist zu erwähnen, weil es gegen die Idee einer Wissensbasis als Selbstzweck spricht: Die Wiki war an mehreren Stellen falsch. Beim Schreiben habe ich jede Zahl gegen den Code geprüft und Abweichungen gefunden — eine Umleitungsdatei mit 55 statt der dokumentierten 106 Regeln, 81 statt 59 Dedup-Paaren, 11.660 statt 6.971 Seiten, an einer Stelle sogar eine falsche CMS-Version im Einstiegsdokument. Nichts davon war dramatisch, aber es bestätigt eine Regel: Dokumentation ist ein Index, keine Wahrheit. Die Wahrheit steht im laufenden System, und wer eine Zahl veröffentlicht, prüft sie dort.
Was ich daraus mitnehme
Der Datenaudit ist die wertvollste Woche. Die Erkenntnis, dass Touren strukturierte Datensätze und keine Textwände waren, hat mehr Aufwand gespart als jede technische Optimierung danach. Wer sofort mit dem Import beginnt, baut Wochen auf einer falschen Annahme.
Herkunfts-IDs statt harter Zuordnungen. Jeder Datensatz trägt seine alte Nummer. Das macht Importe wiederholbar, Umleitungen trivial und Prüfungen gegen die Quelle möglich. Es ist der billigste Schritt mit dem größten Hebel.
Stille Grenzen sind gefährlicher als Fehler. Eine Umleitungsdatei, die ab Regel 120 schweigend aufhört zu wirken, hat mehr Sichtbarkeit gekostet als jeder sichtbare Bug. Alles, was ein System stillschweigend abschneidet, muss im eigenen Prozess eine Zahl bekommen.
Automatische Prüfungen fangen die eigenen Fehler. Der Sprung von 1.285 auf 80.231 HTML-Fehler war mein Fehler, nicht der von Joomla. Gefunden hat ihn kein Mensch, sondern eine Zahl in einem Automatisierungsschritt.
Nicht raten, wenn Daten fehlen. 145 Berichte ohne Tourzuordnung sind ein ehrlicher Zustand. Automatisch geratene Zuordnungen wären ein Verkaufsversprechen auf Basis einer Vermutung.
Was davon für kleine Betriebe übrig bleibt
Zwanzig Jahre Joomla und vier Sprachen klingen weit weg von einem Betrieb mit zehn Leuten. Das Muster darunter ist dasselbe, sobald eine Website älter als ein paar Jahre ist.
Drei Situationen, die in jedem kleinen Betrieb vorkommen:
- Das alte System bekommt keine Updates mehr. Ein CMS ohne Sicherheitspatches ist kein technisches Detail, sondern ein offenes Fenster. Der Weg nach draußen führt nicht über einen «Relaunch», sondern über einen Datenauszug — und der ist meist möglich, auch wenn niemand mehr weiß, wie das System funktioniert.
- Der Relaunch kostet Sichtbarkeit, nicht Geld. Die Rechnung für ein neues Design ist einmalig und kalkulierbar. Verlorene Rankings sind es nicht. Wer umzieht, braucht eine Abbildung jeder alten Adresse auf eine neue — und eine Prüfung, dass jede davon wirklich ankommt.
- Inhalte liegen in Textwänden. Preise, Termine und Leistungen mitten im Fließtext bedeuten, dass niemand sie auswerten kann und jede Änderung Handarbeit ist. Struktur ist kein Selbstzweck: Erst durch sie werden «alle Angebote ohne gültigen Preis» oder eine automatische Übersetzung überhaupt möglich.
Der Unterschied zum Fall hier ist der Umfang, nicht der Ansatz. Für einen kleinen Betrieb braucht es keine 85 Importskripte und keine vier Sprachen — aber dieselben vier Schritte: erst die Daten ansehen, dann strukturieren, dann jede alte Adresse abbilden, und am Ende automatische Prüfungen, die Fehler finden, bevor Kunden sie finden.
Wenn Sie eine Website haben, die älter ist als das letzte Update ihres Systems: Offen für eine Festanstellung in Data Analytics und Automatisierung? Kontakt aufnehmen.