Die Website lief seit 2018 unter Kirby CMS 2. Kirby ist ein PHP-basiertes CMS, welches auf Grundlage von Markdown-Dateien eine Website inkl. Suche erzeugt. Die Erstellung von Inhalten kann also ohne Nutzung einer speziellen CMS-Oberfläche in Markdown-Dateien geschehen, mit dem bevorzugten Editor (Typora ↗). Die Dateien werden anschließend auf den Server synchronisiert. Ich hatte mich damals für diese Struktur entschieden, da ich meinen Content nicht mehr in geschlossenen CMS-Silos ablegen möchte, aus denen er nur schwer wieder heraus zu bekommen ist. Markdown-Dateien sind nur Text-Dateien, mit denen man leicht alles machen kann, notfalls im Texteditor oder per Kommandozeile.
Kirby CMS 2 ist mittlerweile deprecated. Da ich serverseitige Abhängigkeiten verringern möchte soll eine statische Website betrieben werden, die aus Astro erzeugt wird. Astro arbeitet ebenso wie Kirby mit Markdown-Dateien im Dateisystem, sodass eine Migration recht einfach möglich sein sollte.
Außerdem wird dies mein erstes Projekt, welches ich stark gestützt von Claude AI durchführen werde für Beratung und Skript-Erstellung, ich werde also auch diesen Prozess mitdokumentieren, denn ich habe noch nie mit Astro gearbeitet.
Recherche zu Umsetzungsdetails#
Ich habe zunächst Claude die bisherige Dateistruktur erläutert und zwei Beispiel-Markdown-Dateien ergänzt. Claude hat daraus die relevanten Unterschiede ermittelt:
- Der numerische Präfix (
1-,4-) in den Ordnernamen entfällt. Astro kennt keine dateibasierte Navigationsreihenfolge.- Der Zeitstempel im Blog-Post-Ordnernamen (
20263012-thema) entfällt ebenfalls – das Datum wandert ins Frontmatter.post.mdunddefault.mdwerden beide zuindex.mdumbenannt.- Blog-Posts kommen in
src/content/blog/, Seiten insrc/content/pages/.
Astro verwendet außerdem eine andere Metadatenstruktur als Kirby in den Markdown-Dateien, was Claude ebenfalls direkt zusammen gefasst hat:
Tags werden aus einem Komma-getrennten String in ein YAML-Array umgewandelt. Aus
Tags: MacToLinux, Linux, Ubuntuwird eine Liste.Das
Text:-Feld wird zum eigentlichen Markdown-Body nach dem schließenden---. Alles, was in Kirby unterText:stand, steht in Astro einfach unterhalb des Frontmatters.Kirby-spezifische Bildfelder wie
Bweffect,Imagelayer,Layercoloretc. können je nach Bedarf ins Frontmatter übernommen oder weggelassen werden, wenn diese Effekte in Astro anders gelöst werden.Coverimagewird zucoverImageund bekommt einen relativen Pfad zum Bild im selben Ordner.Das
Type-Feld von Kirby entfällt, da die Unterscheidung in Astro über die Collection (blogvs.pages) erfolgt.
Am Ende hat Claude einen Überblick über die Migrationsschritte gegeben:
- Ordnerstruktur: Numerische Präfixe und Zeitstempel aus Ordnernamen entfernen; Inhalte in
src/content/blog/bzw.src/content/pages/ablegen.- Dateinamen:
post.mdunddefault.mdjeweils inindex.mdumbenennen.- Frontmatter: Kirby-Felder (
-----getrennt) in YAML-Frontmatter umwandeln; Tags als Array;Text:-Inhalt wird Body.- Schema:
src/content/config.tsmit Zod-Schema für beide Collections anlegen.- Navigation: Zeitbasierte und Tag-basierte Navigation über
getStaticPaths()in dynamischen Routen implementieren.- Automatisierung: Konvertierungsskript schreiben, das alle Post-Ordner durchläuft.
Anschließend diskutierte ich mit Claude über Bildablageorte:
- Bilder können direkt in den Ordner der jeweiligen Blog-Posts abgelegt und relativ referenziert werden. Dies ist relevant, da ein Bild, welches ich in Typora einfüge, genau so von Typora abgelegt wird.
- Alle alten Blog-Posts verwenden dagegen einen zentralen Bilderordner, der momentan über media.taptoplay.de referenziert wird. Hierfür soll es zukünftig einen lokalen zentralen Bilderordner geben oder die Bilder werden migriert und in den jeweiligen Blog-Ordner verschoben. Beides ist möglich.
Generell ist die Diskussion mit Claude über derartige Themen gut und zielführend:
Frage: Wie entscheidet Astro, wann ein Inhalt veröffentlicht werden darf. In Kirby war der Zeitstempel im Ordnernamen dafür der entscheidende Faktor.
Antwort: In Astro gibt es keinen automatischen Mechanismus, der auf Basis eines Datums entscheidet, ob ein Post veröffentlicht wird oder nicht. Die Entscheidung liegt vollständig beim Entwickler und wird über das Frontmatter gesteuert. Astro Theme Pure verwendet dafür zwei Mechanismen:
1. Das
draft-FeldDas einfachste Mittel: Ein Post mit
draft: truewird nur im Entwicklungsserver angezeigt, aber nicht in den produktiven Build eingeschlossen.yaml--- title: "Mein Artikel" draft: true # nicht veröffentlicht ---yaml--- title: "Mein Artikel" draft: false # veröffentlicht ---2. Das
publishDate-FeldDas Datum im Frontmatter wird für die Sortierung und Anzeige verwendet, hat aber keinen automatischen Einfluss auf die Veröffentlichung. Ein Post mit einem Datum in der Zukunft wird trotzdem gebaut und veröffentlicht – sofern
draftnicht auftruegesetzt ist.Konsequenz für deine Migration
Der Kirby-Mechanismus – Zeitstempel im Ordnernamen verhindert automatisch die Veröffentlichung zukünftiger Posts – entfällt in Astro. Du hast stattdessen zwei Optionen:
Entweder du nutzt ausschließlich das
draft-Feld und setzt alle noch nicht veröffentlichungsreifen Posts aufdraft: true. Das ist der einfachste Weg.Oder du implementierst eine datumsbasierte Filterung selbst, indem du beim Abrufen der Posts alle Einträge herausfilterst, deren
publishDatein der Zukunft liegt:typescriptconst posts = await getCollection('blog', ({ data }) => { return !data.draft && data.publishDate <= new Date(); });Diese Zeile kannst du überall dort einsetzen, wo Posts abgerufen werden – in der Blog-Übersicht, der Tag-Navigation und dem RSS-Feed.
Für die Migration empfehle ich die Kombination aus beidem:
draftfür bewusst zurückgehaltene Entwürfe, und die datumsbasierte Filterung als Absicherung für Inhalte, die ein zukünftiges Datum tragen.
Theme-Auswahl#
Auf Basis meiner Grundanforderungen (Ordnerstruktur, relative Bilder, Tag-Archiv, Monats-Archiv, statische OnSite-Suche) hatte Claude auch eine Theme-Vorauswahl getroffen und ich habe Claude mehrere Themes vergleichen lassen:

Vorgehensweise der Migration#
Der grobe Plan:
-
Astro mit Astro Theme Pure installieren. Die Installation soll von Claude angeleitet werden, da ich Astro nicht kenne.
-
Anschließend mit den Astro-Strukturen vertraut machen
-
Migration der bestehenden Daten
- Hierzu sollen die Bilder aus dem zentralen Verzeichnis in die Blog-Ordner kopiert werden
- Die Metadatenfelder müssen verändert werden
- Ebenso die Dateinamen und Ordnernamen
Claude soll für diese einzelnen Migrationsanforderungen einzelne kleine Skripte schreiben (oder Terminalbefehle nennen), anstelle ein großes Skript, damit Claude nicht bei Überarbeitungen evt. andere Stellen wieder zerbricht.
Installation Astro/Astro Theme Pure#
Aus dem git klonen, npm install, npm run dev. Easy.
Grafiken konsolidieren#
Ziel: Ein Blog-Post soll autark in seinem Ordner existieren, mit allen eingebundenen Grafiken.
Grafiken müssen migriert werden aus einem zentralen Ordner in den Ordner des jeweiligen Blog-Posts. Dateinamen müssen normalisiert werden, sodass das urlencoding innerhalb der Markdown-Datei entfällt. Unterordner-Strukturen aus dem zentralen Ordner sollen beibehalten und im Ordner des Blog-Posts gleichartig aufgebaut werden. Die Referenzen innerhalb der Markdown-Dateien müssen von der externen Datenhaltung media.taptoplay.de auf diesen neuen und relativen Pfad- und Dateinamen geändert werden.
Claude hat ein entsprechendes Skript generiert: Mit wenigen Iterationen, primär bezüglich der Dateinamen, war der Prozess schnell erledigt.
Kirby-spezifische Inhalte konvertieren#
Ziel: Kirby-Spezialfunktionen, die kein Standard-Markdown sind, sollen migriert werden zu Standard-Markdown.
Einige weitere zügig von Claude entworfene Skripte entfernen Kirby CMS-spezifische Funktionen, ersetzen sie durch normales Markdown und kopieren bei Bedarf Bilder in den Ordner des Blog-Posts:
(single-carousel: http://media.taptoplay.de/content_images/IMG_6594.JPG,http://media.taptoplay.de/content_images/IMG_5507.JPG,http://media.taptoplay.de/content_images/IMG_2806.JPG)plaintext(image: img_6135.jpg)plaintext(link: https://www.instagram.com/p/BiozrD9H9Gz/?taken-by=truhe2342 text: auf Instagram) plaintextKleinkram:
- Drei uralte Galerie-Artikel-Stummel in Kirbys eigenem Gallery-Format werden gelöscht. Der Aufwand zur Konvertierung ist größer als der Nutzwert.
Migration der Metadaten#
Ziel: Kirby-Metadaten umwandeln in Astro-Medaten und sonstige Metadaten löschen.
Hier wird es etwas hakliger, denn das Format von Kirby ist eher unsauber und auch nicht einheitlich verwendet:
Title: Titel
----
Text:
Hier steht InhaltmarkdownTitle: Titel
----
Text:
Hier steht Inhalt
----
coverImage: cover.pngmarkdownTitle: Titel
----
Text:
Hier steht Inhalt
----
Das war ein Strich im Inhalt. Kein Abbruch des Inhalts!
----
coverImage: cover.pngmarkdownClaude versuchte hier zunächst zu parsen und kappte Inhalte in der Mitte, was mir erst später in Stichprobentests auffiel. Ich habe Claude anschließend darauf geführt, mit festen Wertelisten zur Erkennung der Kombination aus Trennlinie und Metadatum zu arbeiten, um somit Start und Ende des Contents sauber zu erkennen. Außerdem hat nicht jede Datei den unteren Metadatenbereich, sodass wiederum manchmal das “Cover Image” fehlt. Weiterhin werden zwei unterschiedliche Datumsformate verwendet, die vereinheitlicht werden müssen.
Claude brauchte hier insgesamt mehrere Anläufe: Am Ende ist das Ziel erreicht, aber es dauerte mehrere Stunden, weil immer wieder bislang nicht bedachte Fälle in den historisch gewachsenen Dateien auftauchen.
Glücklicherweise hat Astro einen klar abgegrenzten Metadatenbereich im Header, sodass mir dies bei zukünftigen Migrationen (oder Theme-spezifischen Anpassungen) weniger Probleme bereiten wird.
Kleinkram:
- Alle Dateien von post.md nach index.md umbenennen
- Die Kirby-Zeitstempel aus den Ordnernamen entfernen
Erster Test mit Astro#
Nach Kopieren der Dateien in den Astro-Ordner wird das Terminal mit Fehlern geflutet, weil es noch Unsauberkeiten in den Dateien gibt:
-
In manchen Blog-Posts existieren Markdown-Dateien für Bilder (mit Bildbeschreibungen). Diese müssen gelöscht werden.
-
Das Limit für den Titel von 60 Zeichen muss im Astro-Theme deaktiviert werden. Claude erklärt wie.
Davon abgesehen existiert nun eine initiale Website:

Manuelle Nacharbeiten#
Es folgt, was man eigentlich vermeiden möchte: Die manuelle Prüfung aller Artikel. Zum Einen traue ich den Claude-Skripten nicht ganz, dass sie überall den Inhalt heile gelassen haben, zum Anderen habe ich in Stichproben immer wieder Fragmente uralter Sünden alter Infrastrukturen aus der Vor-Kirby-Zeit (Wordpress, Tumblr) entdeckt, die ich gerne endlich korrigieren möchte. Ich klicke folglich einmal jeden Artikel an und vergleiche ihn mit der aktuellen Site.
Dabei finde ich:
- Ein Metadatum fehlte in der Liste für Claude und musste nun manuell entfernt werden. Zum Glück nur aus einer Datei.
- Sechs Artikel waren leer, weil ihr Inhalt mit “Text: ” begann – ohne anschließenden Umbruch – und somit der Inhalt nicht erkannt wurde. Das waren auch die beiden einzigen “Fehler” durch die Arbeit von Claude. Beide hervorgerufen durch nicht vollständige Vorgaben von mir.
- Manche Seiten in Kirby lassen sich nicht aufrufen, in Astro aber durchaus.
- Bei nicht wenigen Seiten in Kirby fehlte Content unter einer horizontalen Linie. Kirby ist also in dasselbe Problem gelaufen wie Claude bei seinen ersten Versuchen. Der Content wird mit Astro wieder angezeigt.
- Einige Artikel enthalten mittlerweile ungültige Links zu Dateien aus Evernote und Dropbox. Diese Artikel wurden angepasst oder gelöscht.
Statische Generierung#
Nach Umstellung auf die “static” Generierung in astro.config.ts wird mit npm run build die Site statisch in den dist-Ordner geschrieben. Dabei wird ebenfalls ein statischer Suchindex erzeugt. Ein erster Test auf dem Staging-Webserver ist erfolgreich.
Feintuning#
- Die Startseite so umbauen, dass Blog-Posts präsenter sind
- Alternativ direkt auf /blog weiter leiten – dann muss aber auf der Blog-Startseite der Backlink ausgeblendet werden
- Die mitgelieferten Test-Seiten und Test-Blog-Posts aufräumen
- Die wenigen Kirby-Seiten manuell überführen
- Die Templates von nicht benötigten Funktionen entschlacken
- Prüfen auf fremdgeladene Inhalte von anderen Servern, DSE entsprechend anpassen
- Abhängigkeiten zum QRCode-Modul entfernen (da externe Quelle)
- Ausgabe der Description im Seitenheader entfernen (da meist redundant zum Titel)
- Redirects für manche der alten Seiten einrichten
- vercel deaktivieren, damit die Redirects als statische Seiten erzeugt werden. Auch wichtig, damit es überhaupt eine Startseite gibt, die auf /blog umleiten kann.
- Übersichtsseiten aus der Blog-Suche ausschließen
- Entwürfe von Blog-Posts sichten und manuell in die Astro-Struktur übernehmen (als draft markiert)
Bei den Konfigurationsthemen dieser Liste ist Claude eine große Hilfe und verläuft sich nur wenige Male in nicht sinnvollen Vorschlägen. Es gilt aber auch: Man sollte durchaus grob wissen, was man macht und nicht alles blind ausführen, was Claude vorschlägt.
Rollout auf Stage#
- Planung der Deployment-Methode (Sync des dist-Verzeichnisses)
rclone sync --transfers 3 --checkers 1 --retries 10 --retries-sleep 5s --low-level-retries 10 --exclude ".DS_Store" --exclude "._*" /source/folder ttp:/target/folder - Test der Seiten/Sitemaps/Feeds auf übersehene Konfigurationsfragmente mit falschen Pfaden/URLs
Rollout auf Prod#
- Das Document Root des Webservers leeren
- Skurille uralte Rewrite-Regeln entsorgen
- Sync des dist-Ordners
Offene Aufgaben#
Prio 1:
- heroImages auf maximale Höhe skalieren, da auch Hochformat-Bilder verwendet werden. ✔
- Artikel-Reihenfolge von mehreren pro Tag erschienenen Artikeln umsortieren (Uhrzeit mit angeben) ✔
Prio Z:
- Schriftart vergrößern oder ändern ✔
- Feed zeigt zumindest in Vivaldi broken images an, weil bei keinem gesetztem coverImage das Theme als Bild undefined in den Feed schreibt, anstelle keines Bildes. ✔
- Boxen weniger rund gestalten ✔
- Diesen hässlichen grünlichen Verlauf im Seitenhintergrund des Dark Mode entschärfen ✔
- Verläufe entfernen bzw. anpassen ✔
- Die Blog-Startseite setzt keinen Breakpoint für mein Smartphone und läuft zu breit. Ist nicht reproduzierbar am Desktop und alle anderen Inhalte sind responsiv.
- Theme auf Deutsch übersetzen
- Abhängigkeiten zu mediumZoom entfernen (da externe Quelle)
Zusammenfassung der Arbeiten durch Claude#
Kannst du mir zusammen fassen, was wir bislang gemacht haben?
Antwort:
Migrationsskripte#
Entwickelt wurden folgende Node.js-Skripte:
- migrate-assets.mjs – Bilder in Blog-Post-Ordner kopieren
- migrate-carousels.mjs – Kirby-Carousel-Syntax konvertieren
- migrate-images.mjs –
(image: datei.jpg)→ Markdown-Syntax - migrate-links.mjs – Kirby-Links → Markdown-Links
- migrate-legacy-images.mjs – alte Ultra-Fusion-URLs auf lokale Bilder umstellen
- find-kirby-shortcodes.mjs – verbliebene Kirby-Syntax aufspüren
- migrate-frontmatter.mjs – Kirby-Metadaten → Astro-Frontmatter
- migrate-image-field.mjs – mehrzeiliges
image:-Feld auf einzeilig reduzieren - remove-tag.mjs – Tag
dailytruheaus allen Dateien entfernen
Wichtige technische Lösungen#
- Separator-Problem: Kirby nutzt
----auch im Fließtext – gelöst durch eine feste Liste bekannter Metadaten-Schlüssel als Abbruchkriterium - publishDate statt pubDate als korrektes Feldname
- coverImage → heroImage-Übernahme inkl. Bugfix für Separator mit mehreren Leerzeilen
- Ordner-Umbenennung: Zeitstempel-Präfix entfernt, Kollisionsprüfung durchgeführt
- post.md → index.md Umbenennung
Astro-Konfiguration#
output: 'static'statt Vercel-Adapterimage.domainsfür externe Bilder ergänzt- Redirect von
/nach/blogsowie diverse alte URLs content/config.ts: Schema mitimage()-Helper undz.coerce.date()- Titel-Längenbeschränkung im Schema entfernt
Pagefind-Suchindex#
- Tag-Seiten (
/tags/**) vom Index ausgeschlossen viadata-pagefind-ignore="all" - Paginierungsseiten (
/blog,/blog/1,/blog/2etc.) ebenfalls ausgeschlossen - Normale Blog-Posts weiterhin indiziert
Deployment mit rclone#
- FTP-Remote
ttpkonfiguriert - Stabilitätsparameter:
--transfers 3 --retries 10 --retries-sleep 5s .DS_Storeund._*-Dateien ausgeschlossen- Routing-Problem durch fehlerhafte
.htaccessbehoben
Theme-Anpassungen#
- heroImage: Maximale Höhe auf
36.5chbegrenzt (entspricht 16:9 beimax-w-[65ch]) - heroImage-Verlauf: Transparenz endet früher damit mehr vom Bild sichtbar ist
- Schriftart: Google Fonts oder Systemschrift über CSS-Variable eingebunden
- Dark-Mode-Farbe:
--primaryauf dezentes Grau220 5% 60%geändert - Border-Radius: von
rounded-2xlaufrounded-mdreduziert - Back-Button: auf
/blogausgeblendet - RSS-Feed: Fehler bei fehlendem heroImage behoben
