Tap to Play!

Back

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.md und default.md werden beide zu index.md umbenannt.
  • Blog-Posts kommen in src/content/blog/, Seiten in src/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, Ubuntu wird eine Liste.

Das Text:-Feld wird zum eigentlichen Markdown-Body nach dem schließenden ---. Alles, was in Kirby unter Text: stand, steht in Astro einfach unterhalb des Frontmatters.

Kirby-spezifische Bildfelder wie Bweffect, Imagelayer, Layercolor etc. können je nach Bedarf ins Frontmatter übernommen oder weggelassen werden, wenn diese Effekte in Astro anders gelöst werden. Coverimage wird zu coverImage und bekommt einen relativen Pfad zum Bild im selben Ordner.

Das Type-Feld von Kirby entfällt, da die Unterscheidung in Astro über die Collection (blog vs. pages) erfolgt.

Am Ende hat Claude einen Überblick über die Migrationsschritte gegeben:

  1. Ordnerstruktur: Numerische Präfixe und Zeitstempel aus Ordnernamen entfernen; Inhalte in src/content/blog/ bzw. src/content/pages/ ablegen.
  2. Dateinamen: post.md und default.md jeweils in index.md umbenennen.
  3. Frontmatter: Kirby-Felder (-----getrennt) in YAML-Frontmatter umwandeln; Tags als Array; Text:-Inhalt wird Body.
  4. Schema: src/content/config.ts mit Zod-Schema für beide Collections anlegen.
  5. Navigation: Zeitbasierte und Tag-basierte Navigation über getStaticPaths() in dynamischen Routen implementieren.
  6. 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-Feld

Das einfachste Mittel: Ein Post mit draft: true wird nur im Entwicklungsserver angezeigt, aber nicht in den produktiven Build eingeschlossen.

---
title: "Mein Artikel"
draft: true   # nicht veröffentlicht
---
yaml
---
title: "Mein Artikel"
draft: false  # veröffentlicht
---
yaml

2. Das publishDate-Feld

Das 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 draft nicht auf true gesetzt 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 auf draft: true. Das ist der einfachste Weg.

Oder du implementierst eine datumsbasierte Filterung selbst, indem du beim Abrufen der Posts alle Einträge herausfilterst, deren publishDate in der Zukunft liegt:

const posts = await getCollection('blog', ({ data }) => {
  return !data.draft && data.publishDate <= new Date();
});
typescript

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: draft fü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:

Vergleichstabelle

Vorgehensweise der Migration#

Der grobe Plan:

  1. Astro mit Astro Theme Pure installieren. Die Installation soll von Claude angeleitet werden, da ich Astro nicht kenne.

  2. Anschließend mit den Astro-Strukturen vertraut machen

  3. Migration der bestehenden Daten

    1. Hierzu sollen die Bilder aus dem zentralen Verzeichnis in die Blog-Ordner kopiert werden
    2. Die Metadatenfelder müssen verändert werden
    3. 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) 
plaintext

Kleinkram:

  • 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 Inhalt
markdown
Title: Titel
----
Text:

Hier steht Inhalt
----
coverImage: cover.png
markdown
Title: Titel
----
Text:

Hier steht Inhalt
----
Das war ein Strich im Inhalt. Kein Abbruch des Inhalts!
----
coverImage: cover.png
markdown

Claude 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:

Blog-Startseite mit Artikeln

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 dailytruhe aus 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-Adapter
  • image.domains für externe Bilder ergänzt
  • Redirect von / nach /blog sowie diverse alte URLs
  • content/config.ts: Schema mit image()-Helper und z.coerce.date()
  • Titel-Längenbeschränkung im Schema entfernt

Pagefind-Suchindex#

  • Tag-Seiten (/tags/**) vom Index ausgeschlossen via data-pagefind-ignore="all"
  • Paginierungsseiten (/blog, /blog/1, /blog/2 etc.) ebenfalls ausgeschlossen
  • Normale Blog-Posts weiterhin indiziert

Deployment mit rclone#

  • FTP-Remote ttp konfiguriert
  • Stabilitätsparameter: --transfers 3 --retries 10 --retries-sleep 5s
  • .DS_Store und ._*-Dateien ausgeschlossen
  • Routing-Problem durch fehlerhafte .htaccess behoben

Theme-Anpassungen#

  • heroImage: Maximale Höhe auf 36.5ch begrenzt (entspricht 16:9 bei max-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: --primary auf dezentes Grau 220 5% 60% geändert
  • Border-Radius: von rounded-2xl auf rounded-md reduziert
  • Back-Button: auf /blog ausgeblendet
  • RSS-Feed: Fehler bei fehlendem heroImage behoben