Nachdem die neue Webseite nun online ist, möchte ich in diesem Beitrag einen etwas ausführlicheren Blick hinter die Kulissen geben. Der Wechsel von WordPress zu Hugo war kein spontaner Umzug, sondern ein bewusstes technisches Upgrade, bei dem ich nicht nur Inhalte migriert, sondern den kompletten Unterbau meines Blogs modernisiert habe.
Gerade weil ich hier auch in Zukunft längere und technischere Beiträge veröffentlichen möchte, war mir eine Plattform wichtig, die schnell, transparent und gut wartbar ist. Genau an dieser Stelle hat sich Hugo für mich als deutlich bessere Basis erwiesen.
Was ist Hugo eigentlich?
Hugo ist ein sogenannter Static Site Generator. Vereinfacht gesagt bedeutet das: Die Webseite wird nicht bei jedem Seitenaufruf dynamisch aus Datenbank, Plugins und Theme-Dateien zusammengesetzt, sondern beim Build-Prozess fertig als statische HTML-, CSS- und JavaScript-Dateien erzeugt.
Das hat mehrere Vorteile:
- Die Seite ist sehr schnell, weil keine Datenbankabfragen und kein PHP-Rendering zur Laufzeit notwendig sind.
- Die Hosting-Anforderungen sind deutlich geringer.
- Die technische Struktur ist klarer und leichter nachvollziehbar.
- Inhalte, Templates und Assets lassen sich sehr gut versionieren.
- Viele typische Wartungsprobleme klassischer CMS-Systeme entfallen vollständig.
WordPress ist ohne Frage ein mächtiges System und für viele Einsatzszenarien absolut sinnvoll. Für meinen persönlichen Blog war es mit der Zeit aber schlicht zu schwergewichtig geworden. Ich wollte weniger Administrationsaufwand, weniger bewegliche Teile und deutlich mehr Kontrolle über den eigentlichen technischen Aufbau.
Warum Hugo für meinen Blog besser passt als WordPress
Der größte Unterschied liegt für mich gar nicht nur in der Geschwindigkeit, sondern im gesamten Arbeitsmodell.
Bei WordPress lebt vieles direkt im Backend: Inhalte, Medien, Plugins, Theme-Anpassungen und oft auch kleinere technische Sonderlösungen. Das funktioniert bequem, führt aber mit der Zeit auch dazu, dass Inhalte und Technik stärker miteinander verknüpft sind, als mir lieb ist.
Mit Hugo ist das anders. Hier liegen meine Inhalte ganz normal im Projektverzeichnis und lassen sich wie Quellcode behandeln. Ein Blog-Post ist am Ende nichts anderes als eine Markdown-Datei mit etwas Front Matter. Das wirkt zunächst unspektakulär, ist für mich aber ein riesiger Vorteil:
- Beiträge lassen sich direkt im Editor schreiben und überarbeiten.
- Änderungen sind sauber in Git nachvollziehbar.
- Inhalte können zusammen mit Layout und Konfiguration versioniert werden.
- Ich bin nicht von einem CMS-Backend abhängig.
- Ein Backup der Webseite ist im Grunde gleichzeitig auch ein Backup der Inhalte.
Gerade für technische Beiträge finde ich das ideal. Markdown ist angenehm leichtgewichtig, schnell geschrieben und perfekt für Überschriften, Listen, Code-Blöcke, Zitate oder Bilder geeignet. Statt Inhalte in einem Webformular zu pflegen, arbeite ich jetzt direkt in Dateien, die sich hervorragend in meinen normalen Entwicklungsworkflow integrieren.
Der Start: Neues Hugo-Projekt und erstes Design
Zunächst habe ich ein neues Hugo-Projekt erstellt und ein wenig Zeit in ein mögliches Design investiert. Mir war wichtig, nicht einfach nur Inhalte zu übertragen, sondern der Seite auch technisch und optisch eine neue Grundlage zu geben.
Hugo eignet sich dafür sehr gut, weil es auf statische Dateien setzt, schnell baut und sich angenehm mit Markdown, Templates und einer klaren Ordnerstruktur kombinieren lässt. Dadurch konnte ich früh damit beginnen, Layout, Navigation und Seitenaufbau gezielt auf meine Anforderungen zuzuschneiden.
Über 350 Beiträge lassen sich nicht per Hand migrieren
Da ich bereits über 350 Beiträge in WordPress erstellt hatte, war ein manuelles Kopieren der Inhalte keine realistische Option. Schon allein das Übernehmen von Texten, Bildern, Metadaten und URLs hätte sehr viel Zeit gekostet und wäre gleichzeitig fehleranfällig gewesen.
Deshalb war schnell klar, dass ich einen automatisierten Weg brauche, um die vorhandenen Inhalte in eine Form zu bringen, die Hugo direkt weiterverarbeiten kann.
Mein eigener WordPress-Hugo-Exporter
Ich habe mir daher ein kleines WordPress-Hugo-Exporter-Plugin geschrieben. Wichtig dabei: Dieses Plugin ist bewusst auf meine eigene Seite und meinen Content zugeschnitten. Es ist also kein universelles Migrationswerkzeug für beliebige WordPress-Webseiten, sondern eher ein sehr pragmatisches Hilfsmittel für genau meinen Aufbau, meine Inhalte und meine Hugo-Struktur.
Gerade das war aber ein Vorteil. Statt ein allgemeines Tool zu bauen, konnte ich den Exporter exakt auf meine Anforderungen anpassen:
- Export in eine Hugo-kompatible Ordnerstruktur
- Erzeugung von
index.mdpro Beitrag - Download von Beitragsbildern direkt in den jeweiligen Post-Ordner
- Umwandlung von Inline-Bildern in meine vorhandenen Hugo-Shortcodes
- Übernahme von Tags, Kategorien, Slug und Veröffentlichungsdatum
- Vorbereitung von internen Links für die neue Seitenstruktur
Für jeden Beitrag wurden also Inhalte als Markdown-Datei, alle verwendeten Bilder sowie ein passendes Front Matter erzeugt. Genau das war der entscheidende Schritt, um aus den WordPress-Inhalten ein Format zu machen, das sich in Hugo ohne großen manuellen Aufwand weiterverwenden lässt.
Was ich am Exporter außerdem bewusst schlicht gehalten habe: Der Export läuft ein Beitrag pro Request und ohne ZIP-Erstellung. Das klingt zunächst unspektakulär, macht das Ganze aber robuster. Gerade bei großen Datenmengen wollte ich lieber eine stabile und nachvollziehbare Verarbeitung als einen “Alles auf einmal”-Ansatz, der irgendwann an Timeouts oder Speichergrenzen scheitert.
Ein paar technische Details aus dem Exporter
Ein zentrales Detail war die Zielstruktur für jeden exportierten Beitrag. Der Exporter erzeugt pro Post einen eigenen Ordner mit Datumspräfix und Slug. Das passt sehr gut zu Hugo-Leaf-Bundles mit index.md und zugehörigen Ressourcen im selben Verzeichnis.
$post_date_prefix = get_post_time('Y-m-d', false, $post);
$folder_name = $post_date_prefix . '_' . $slug;
$post_dir = trailingslashit($run_dir) . $folder_name;
Das Ergebnis ist eine Struktur, die sich später sehr angenehm weiterverarbeiten lässt: pro Beitrag ein Ordner, darin die Markdown-Datei und alle zugehörigen Bilder. Genau diese Trennung macht spätere Nacharbeit deutlich einfacher.
Ein weiteres wichtiges Stück war die Erzeugung des Front Matter. Der Exporter nimmt sich die Metadaten aus WordPress und übersetzt sie in das Format, das Hugo direkt versteht:
$fm[] = 'title: "' . $title . '"';
$fm[] = 'date: "' . $date . '"';
$fm[] = 'draft: ' . ($post->post_status === 'publish' ? 'false' : 'true');
$fm[] = 'author: "Thomas Sebastian Jensen (https://www.tsjdev-apps.de)"';
$fm[] = 'header_image: "' . $this->yaml_escape($header_image_rel) . '"';
Gerade dieser Schritt spart später enorm viel Handarbeit. Denn wenn Titel, Datum, Status, Tags, Kategorien und URL bereits sauber im Front Matter stehen, landet ein Beitrag fast fertig in Hugo.
Das fertige Ergebnis sieht dann zum Beispiel so aus:
---
title: "Wie habe ich meine Webseite von WordPress nach Hugo migriert?"
date: "2026-04-01T15:00:00+02:00"
draft: false
author: "Thomas Sebastian Jensen (https://www.tsjdev-apps.de)"
header_image: "images/header_code.png"
tags:
- "Hugo"
- "Migration"
- "Website"
- "WordPress"
categories:
- "Webentwicklung"
url: "wie-habe-ich-meine-webseite-von-wordpress-nach-hugo-migriert"
seo_title: "Wie habe ich meine Webseite von WordPress nach Hugo migriert | tsjdev-apps.de"
seo_description: "In diesem Beitrag zeige ich, wie ich meine Webseite von WordPress nach Hugo migriert habe - vom neuen Projekt über einen eigenen Exporter bis zum finalen Feinschliff im Layout."
---
Genau an dieser Stelle sieht man sehr gut, wie der Exporter nicht nur Rohdaten aus WordPress herauszieht, sondern ein Ergebnis erzeugt, das in Hugo direkt sinnvoll weiterverwendet werden kann.
Die wichtigsten Aufgaben davon sind:
title: Der Titel des Beitrags.date: Das Veröffentlichungsdatum.draft: Steuert, ob ein Beitrag schon veröffentlicht werden soll oder noch im Entwurfsmodus bleibt.header_image: Legt das Bild für den Kopfbereich des Beitrags fest.tagsundcategories: Helfen dabei, Inhalte thematisch zu gruppieren.url: Definiert die gewünschte URL des Beitrags.seo_titleundseo_description: Verbessern die Darstellung in Suchmaschinen und beim Teilen in sozialen Netzwerken.
Gerade bei einer Migration ist das Front Matter besonders wichtig, weil es Struktur in die Inhalte bringt und viele Informationen enthält, die vorher in WordPress an anderer Stelle verwaltet wurden.
Besonders hilfreich fand ich auch die Behandlung von Bildern. Inline-Bilder aus WordPress wurden nicht einfach nur als HTML stehen gelassen, sondern beim Export nach Möglichkeit heruntergeladen und in meine Hugo-Lightbox-Shortcodes umgebaut. So musste ich später nicht hunderte Beiträge anfassen, nur um die Bilddarstellung an mein neues System anzupassen.
Ein interessantes Detail dabei ist, dass der Exporter mehrere mögliche Bildquellen berücksichtigt, also nicht nur src, sondern bei Bedarf auch Attribute wie data-src oder Einträge aus srcset:
$src = trim((string)$img->getAttribute('src'));
if ($src !== '') {
$candidates[] = $src;
}
foreach (['data-src', 'data-lazy-src', 'data-original', 'data-actualsrc'] as $attr) {
$value = trim((string)$img->getAttribute($attr));
if ($value !== '') {
$candidates[] = $value;
}
}
Das hilft besonders bei älteren WordPress-Inhalten oder Plugins, die Bilder nicht immer einheitlich ausgeben.
Ebenso wichtig war für mich die Behandlung von Code-Blöcken. Der Exporter schaut sich <pre>- und <code>-Elemente an, erkennt wenn möglich die verwendete Sprache und erzeugt daraus Markdown-Code-Fences. Das ist gerade bei älteren technischen Beiträgen sehr wertvoll, weil sauber formatierte Code-Snippets in Hugo deutlich besser lesbar sind als ungefilterter HTML-Export.
Der Kern davon sieht so aus:
if ($language === 'markup') {
$language = 'xml';
}
$text = rtrim($text, "\n");
return "\n\n```" . $language . "\n" . $text . "\n```\n\n";
Das ist kein riesiger Codeblock, aber genau solche kleinen Transformationen verbessern das Ergebnis der Migration spürbar.
Ein weiteres Beispiel, das sich auch für andere Projekte eignen kann, ist die Verarbeitung interner Links. Statt alte WordPress-Links einfach zu übernehmen, baut der Exporter intern eine Zuordnung auf, damit bestehende Verlinkungen später in die neue Hugo-Struktur übersetzt werden können. Genau solche kleinen Helfer machen bei einer größeren Migration am Ende einen großen Unterschied.
Dafür wird zunächst eine interne Zuordnung auf Basis von Datum, Slug und Zielpfad aufgebaut:
$date_prefix = get_post_time('Y-m-d', false, $post);
$year = get_post_time('Y', false, $post);
$folder_name = $date_prefix . '_' . $slug;
$relref = '/posts/' . $year . '/' . $folder_name . '/index.md';
$this->internal_link_map[$normalized] = $relref;
Später kann der Exporter bestehende interne WordPress-Links dann in passende Hugo-relref-Verweise überführen. Gerade bei vielen verlinkten Altbeiträgen spart das sehr viel Nacharbeit.
So konnte ich die migrierten Beiträge strukturiert exportieren und anschließend direkt in mein Hugo-Projekt übernehmen.
Bloggen als Markdown-Datei statt CMS-Backend
Einer der Punkte, die ich an der neuen Lösung besonders schätze, ist die Art, wie neue Inhalte entstehen. Ein neuer Beitrag ist jetzt im Kern einfach eine Datei wie index.md, ergänzt um Bilder und etwas Front Matter. Kein Login ins Backend, kein visueller Editor, keine Plugin-Magie im Hintergrund.
Gerade für mich als Entwickler fühlt sich das deutlich natürlicher an. Ich schreibe einen Beitrag direkt im Projekt, kann ihn lokal rendern, Änderungen in Git ansehen und bei Bedarf auch später sehr gezielt nachbearbeiten. Das ist nicht nur angenehm, sondern auch erstaunlich robust.
Ein typischer Einstieg in einen neuen Beitrag sieht am Ende ungefähr so aus:
---
title: "Mein neuer Beitrag"
date: "2026-04-01T18:00:00+02:00"
draft: false
tags:
- "Hugo"
categories:
- "Entwicklung"
---
Hier beginnt der eigentliche Inhalt.
Diese Arbeitsweise gibt mir viel mehr Ruhe beim Schreiben. Der Content bleibt langfristig lesbar, portabel und unabhängig von einem bestimmten System. Wenn ich später Layout, Theme oder Build-Prozess ändere, bleiben meine Inhalte trotzdem einfach strukturierte Dateien.
Shortcodes als praktische Erweiterung
Ein weiterer großer Vorteil von Hugo ist das Konzept der Shortcodes. Damit lassen sich wiederverwendbare Bausteine entwickeln, die in Markdown-Beiträgen sehr einfach eingebunden werden können. Gerade das ist für mich ein sehr angenehmer Mittelweg: Ich schreibe Inhalte in Markdown, kann bei Bedarf aber trotzdem zusätzliche Funktionen oder spezielle Darstellungen einbauen.
Statt überall HTML zu duplizieren oder umständliche Sonderfälle in Beiträge einzubauen, reicht dann oft eine kompakte Shortcode-Schreibweise. Das hält den eigentlichen Text sauber und sorgt gleichzeitig dafür, dass technische Logik zentral an einer Stelle bleibt.
Mein Lightbox-Shortcode als Beispiel
Ein schönes Beispiel dafür ist mein Lightbox-Shortcode. Bilder lassen sich damit im Beitrag sehr schlank einbinden, öffnen sich beim Anklicken aber in einer größeren Ansicht. Gerade bei Screenshots oder technischen Abbildungen ist das sehr praktisch, weil Bilder im Lesefluss kompakt bleiben und trotzdem in voller Größe betrachtet werden können.
Im Beitrag sieht die Einbindung dann zum Beispiel so aus:
{{< lightbox src="hugo-website.png" >}}
Für mich ist das deutlich angenehmer als Bilder jedes Mal manuell mit zusätzlichem HTML, Klassen oder JavaScript-Markup zu versehen. Ich konzentriere mich im Beitrag auf den Inhalt, während der Shortcode die technische Umsetzung übernimmt. Hier ist nun der eigentliche Code zu dem Lightbox-Shortcode.
{{ $src := .Get "src" }}
{{ $alt := .Get "alt" | default "" }}
{{ $caption := .Get "caption" }}
{{ $class := .Get "class" | default "" }}
{{ $resource := .Page.Resources.GetMatch $src }}
{{ $imageURL := $src }}
{{ with $resource }}
{{ $imageURL = .RelPermalink }}
{{ end }}
<figure class="lightbox-figure {{ $class }}">
<button class="lightbox-trigger"
type="button"
data-lightbox-src="{{ $imageURL }}"
data-lightbox-alt="{{ $alt }}" {{ with $caption }}
data-lightbox-caption="{{ . }}"{{ end }}>
<img src="{{ $imageURL }}" alt="{{ $alt }}" loading="lazy">
</button>
{{ with $caption }}<figcaption>{{ . }}</figcaption>{{ end }}
</figure>
Zusätzlich ist das auch für die Wartung hilfreich: Wenn ich das Verhalten oder die Darstellung der Lightbox ändere, muss ich nicht jeden Beitrag anfassen. Ich passe den Shortcode zentral an und alle betroffenen Inhalte profitieren automatisch davon.
Genau solche kleinen Helfer zeigen für mich sehr gut, warum Hugo so angenehm flexibel ist. Es ist bewusst leichtgewichtig, aber trotzdem nicht starr. Wenn etwas fehlt, kann ich es mir in einer Form bauen, die exakt zu meiner Webseite passt.
Beiträge übernehmen und nachbearbeiten
Anschließend habe ich die exportierten Inhalte in meine Hugo-Seite kopiert und bin die Beiträge noch einmal Stück für Stück durchgegangen. Trotz automatisiertem Export war dabei noch etwas Nacharbeit notwendig. Ein Exporter nimmt einem viel Arbeit ab, aber er ersetzt nicht den letzten redaktionellen und technischen Blick auf das Ergebnis.
Gerade Tabellen, Code-Snippets und einzelne Formatierungen mussten an einigen Stellen noch einmal sauber überarbeitet werden, damit die Darstellung im neuen System wirklich stimmig ist. Vor allem ältere Inhalte profitieren davon, wenn man sie bei einer Migration noch einmal mit einem frischen Blick prüft.
Das war auch der Punkt, an dem sich die Kombination aus automatisiertem Export und manueller Qualitätskontrolle bewährt hat: Die Grundstruktur kam schnell und zuverlässig aus dem Plugin, der Feinschliff kam anschließend direkt in Hugo.
Feinschliff am Layout
Danach habe ich noch ein wenig Zeit ins Layout gesteckt und es immer weiter optimiert. Dazu gehörten kleinere Design-Entscheidungen, Anpassungen bei der Darstellung einzelner Elemente und natürlich auch Features wie ein Dark Mode.
Gerade diese Phase hat viel Spaß gemacht, weil hier aus einer funktionierenden Migration Schritt für Schritt wieder eine eigene, moderne Webseite geworden ist.
Deployment über GitHub
Ein Punkt, den ich an der neuen Lösung ebenfalls sehr schätze, ist der Deployment-Prozess. Die komplette Webseite liegt inzwischen in einem privaten GitHub-Repository.
Sobald ich Änderungen auf main pushe, wird automatisch eine neue Version gebaut und deployed.
Das bedeutet für mich ganz konkret:
- Ich pflege Inhalte und Code an einer zentralen Stelle.
- Änderungen sind versioniert und jederzeit nachvollziehbar.
- Der Build läuft automatisiert statt manuell.
- Neue Inhalte sind in wenigen Minuten live verfügbar.
Gerade für einen Blog ist das ein enorm angenehmer Workflow. Ich schreibe oder überarbeite einen Beitrag, committe die Änderungen, pushe nach main und kurze Zeit später ist der neue Stand online. Kein manuelles Hochladen per FTP, kein Einloggen in ein Backend, kein händisches Veröffentlichen einzelner Seiten.
Diese Automatisierung passt perfekt zu Hugo, weil statische Seiten sich sehr gut in moderne Build- und Deployment-Pipelines integrieren lassen. Damit fühlt sich die Pflege der Webseite insgesamt eher wie Softwareentwicklung an und genau das ist für mich ein großer Pluspunkt.
Fazit
Und hier sind wir nun mit einer neuen Webseite, die nicht nur schnell, sondern auch leicht wartbar ist. Die Migration von WordPress nach Hugo war zwar mit etwas Aufwand verbunden, aber sie hat sich absolut gelohnt.
Mit einem neuen Hugo-Projekt, einem eigenen Exporter, etwas Nacharbeit an den Inhalten, praktischen Shortcodes und einem automatisierten Deployment über GitHub konnte ich meinen Blog auf eine moderne technische Basis stellen.
Für mich ist Hugo nicht einfach nur ein anderes System als WordPress, sondern die deutlich passendere Lösung für genau diese Art von Webseite. Ich schreibe meine Beiträge heute als Markdown-Dateien, kann technische Hilfsmittel wie den Lightbox-Shortcode sauber integrieren und veröffentliche neue Inhalte mit einem sehr schlanken Workflow.
Genau so soll sich ein technischer Blog für mich anfühlen.