Von WordPress zu Hugo: Wie BusinessCode seine Website komplett neu gedacht hat
Drei Stationen, ein Ziel
Die BusinessCode‑Website hat drei Stationen durchlaufen:
- WordPress beim Hosting‑Provider – mit einem Template‑System des Anbieters
- Hugo + Hextra – der erste Schritt zu einem statischen Site‑Generator, noch auf Bootstrap‑Basis
- Hugo + eigenes BCD‑Theme – Tailwind CSS, Bento‑Layout, Glassmorphism und DecapCMS
Jede Station hat Probleme der vorherigen gelöst – und neue Möglichkeiten eröffnet. In diesem Artikel beschreiben wir den gesamten Weg, die Entscheidungen dahinter und was Hugo in Kombination mit modernem Tooling so leistungsfähig macht.
Station 1: WordPress beim Hoster
Die alte Seite lief auf WordPress bei einem Hosting‑Provider. Design und Layout kamen über ein Template‑System des Anbieters – eine fertige Lösung, die auf den ersten Blick funktionierte, aber im Detail ständig Probleme machte:
- Bilder wurden falsch beschnitten – das Template‑System verwendete feste Seitenverhältnisse, die nicht zum Content passten
- Layout‑Instabilität – Spalten, Abstände und Schriftgrößen verhielten sich je nach Inhalt unvorhersehbar
- Gutenberg → Atomic Blocks – mit dem Wechsel des WordPress‑Editors zu Block‑basierten Systemen wurde die Seite zunehmend fragil; Layouts, die im alten Editor funktionierten, zerfielen im neuen
- Keine Versionierung – Änderungen an Inhalten und Templates waren kaum nachvollziehbar
- Kein CI/CD – Deployments waren FTP‑ oder Backup‑Szenarien
- Mehrsprachigkeit (DE/EN) über Plugin, im Detail oft brüchig
Die Seite sah zunehmend nicht mehr so aus, wie sie sollte – und jedes WordPress‑Update machte es schlimmer statt besser.
Station 2: Hugo + Hextra (Bootstrap)
Der erste Schritt weg von WordPress führte zu Hugo als statischem Site‑Generator mit dem Hextra‑Theme, das auf Bootstrap basiert.
Das löste die Kernprobleme:
- Statische Auslieferung – keine PHP‑Runtime, keine Datenbank, keine Sicherheitsupdates im laufenden Betrieb
- Git als Single Source of Truth – Inhalte und Templates endlich versioniert
- Schnelle Builds – Hugo generiert die gesamte Seite in Sekunden
Aber Hextra brachte eigene Einschränkungen mit:
- Bootstrap‑Overhead – das gesamte Framework wurde geladen, obwohl nur ein Bruchteil genutzt wurde
- Schwer anpassbar – Designänderungen erforderten das Überschreiben von Bootstrap‑Klassen und ‑Variablen
- Kein eigenes Design‑System – die Seite sah aus wie eine von vielen Bootstrap‑Seiten
- Dark Mode war nicht vorgesehen
Hextra war der richtige Schritt zum richtigen Zeitpunkt – aber für ein durchgängig eigenes Erscheinungsbild reichte es nicht.
Station 3: Eigenes Theme mit Tailwind CSS
Im zweiten Hugo‑Ansatz haben wir Hextra komplett abgelöst und ein eigenes Theme (BCD) auf Basis von Tailwind CSS gebaut – mit einem Bento‑Layout und Glassmorphism als durchgängiger Designsprache.
Warum Tailwind statt Bootstrap?
- Utility‑First – kein Überschreiben von Framework‑Klassen mehr, sondern direkte Gestaltung im Markup
- Tree‑Shaking – nur die genutzten Klassen landen im Build, das CSS ist winzig
- Dark Mode als Kernfeature – über die
class‑Strategie und CSS Custom Properties - Keine CDN‑Abhängigkeit – alles wird lokal kompiliert und ausgeliefert
Glassmorphism als Designsprache
Das zentrale visuelle Element ist ein durchgängiges Glassmorphism‑System:
.glass {
background: var(--glass-bg);
backdrop-filter: blur(20px) saturate(1.8);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
border-radius: 1.25rem;
}
Über CSS Custom Properties wechseln die Werte automatisch zwischen Light und Dark Mode:
:root {
--glass-bg: rgba(255, 255, 255, 0.6);
--glass-border: rgba(22, 22, 105, 0.08);
}
.dark {
--glass-bg: rgba(13, 13, 55, 0.55);
--glass-border: rgba(54, 152, 250, 0.12);
}
Diese .glass‑Klasse zieht sich durch die gesamte Seite: Navigation, Karten, Suchmodal, Blog‑Navigation – alles glasig, alles konsistent.
PostCSS‑Pipeline statt SCSS
Tailwind wird über Hugo’s eingebaute PostCSS‑Integration kompiliert:
{{/* head/css.html */}}
{{ $css := resources.Get "css/main.css" }}
{{ $css = $css | css.PostCSS }}
{{ if hugo.IsProduction }}
{{ $css = $css | minify | fingerprint }}
{{ end }}
Das Ergebnis: ein einziges, fingerprinted CSS‑File mit Subresource Integrity – ohne externe Abhängigkeiten.
WCAG‑Konformität
Barrierefreiheit ist direkt im Basis‑CSS verankert:
:focus-visible‑Styles für Tastaturnavigationprefers-reduced-motion‑Media‑Query deaktiviert Animationen- Semantisches HTML in allen Layouts und Partials
DecapCMS: Redaktionelles Arbeiten ohne WordPress
Ein statischer Site‑Generator allein reicht nicht – Redakteur:innen brauchen eine Oberfläche.
Dafür setzen wir DecapCMS (ehemals Netlify CMS) ein.
Wie es funktioniert
DecapCMS läuft als Single‑Page‑App unter /admin/ und spricht direkt mit dem GitLab‑Repository:
- Authentifizierung über GitLab OAuth mit PKCE‑Flow – kein separater Auth‑Server nötig
- Editorial Workflow – Änderungen erzeugen Merge Requests, die reviewed werden können
- Mehrsprachige Inhalte – DE und EN werden als separate Dateien im selben Bundle verwaltet
- Medien werden relativ zum Content‑Bundle gespeichert
Lokal entwickeln ohne Login
Damit Entwickler:innen DecapCMS lokal testen können, ohne sich bei GitLab einzuloggen, erkennt die Admin‑Seite automatisch localhost:
var isLocal = location.hostname === 'localhost'
|| location.hostname === '127.0.0.1';
if (isLocal) {
CMS.init({
config: {
local_backend: true,
backend: { name: 'git-gateway' }
}
});
}
make serve startet den Hugo‑Server und den DecapCMS‑Local‑Backend‑Proxy automatisch:
make serve
# ✅ DecapCMS local backend running on http://localhost:8081
# ✅ Hugo server running at http://localhost:1313
Kein manuelles Umschalten von Config‑Flags, kein separater Prozess.
Collections für Blog, News und Projekte
Die config.yml definiert Collections für alle Inhaltsbereiche:
- Blog und News – mit Feldern für Titel, Summary, Autor, Kategorien, Tags, Hero‑Bilder und externen Links
- Projekte – mit verschachtelten Ordnern (bis zu 3 Ebenen tief), Kategorie‑Select, Gewichtung und Icon‑Pfad
Redakteur:innen sehen im CMS genau die Felder, die im Frontmatter definiert sind – nicht mehr und nicht weniger.
Der Entwickler‑Workflow
Ein Kommando für alles
Das gesamte Entwicklungs‑Setup ist über ein Makefile abstrahiert:
make serve # Hugo + DecapCMS lokal starten
make stop # Alles stoppen
make test # E2E-Tests ausführen
make validate # Content-Validierung
make build # Produktions-Build
make new-blog SLUG=mein-artikel # Neuen Blog-Beitrag anlegen
Das Makefile erkennt automatisch, ob Podman, Docker oder eine native Hugo‑Installation vorhanden ist.
Damit haben alle im Team:
- dieselbe Hugo‑Version
- denselben Build‑Prozess
- identisches Verhalten wie in CI
GitLab CI/CD
Auf dem main‑Branch läuft automatisch:
- Content‑Validierung – prüft fehlende Übersetzungen, Icon‑Referenzen, Hero‑Bilder, Frontmatter‑Konsistenz
- Hugo‑Build – mit Minifizierung und Resource‑Caching
- Deployment auf GitLab Pages
Schlägt die Validierung fehl, wird nicht deployt. Auf Feature‑Branches baut Hugo mit --buildDrafts, damit Entwürfe vorab geprüft werden können.
Hugo‑Features, die den Unterschied machen
Responsive Bilder mit AVIF + WebP
Hugo verarbeitet Bilder zur Build‑Zeit. Unser responsive-picture‑Partial erzeugt ein vollständiges <picture>‑Element:
<picture>
<source type="image/avif" srcset="...400w, ...600w, ...800w, ...1200w" />
<source type="image/webp" srcset="...400w, ...600w, ...800w, ...1200w" />
<source srcset="...400w, ...600w, ...800w, ...1200w" />
<img src="fallback-800.webp" loading="lazy" decoding="async" />
</picture>
AVIF, WebP und Original‑Format werden in vier Breiten erzeugt – automatisch, für jedes Bild auf der Seite.
Lokale Suche mit Alpine.js
Die Volltextsuche läuft komplett clientseitig – kein externer Dienst, kein Server:
- Hugo generiert einen JSON‑Index aller Inhalte zur Build‑Zeit
- Alpine.js rendert das Suchmodal mit Live‑Ergebnissen
- Keyboard‑Shortcuts:
Ctrl+Shift+Kzum Öffnen, Pfeiltasten zum Navigieren - Ergebnisse sind nach Typ kategorisiert (Blog, News, Services, Projekte, Guides)
- Alle Labels kommen aus i18n‑Keys – die Suche funktioniert in jeder Sprache
Shortcodes für wiederkehrende Patterns
Statt Inhalte manuell zu formatieren, nutzen Redakteur:innen Hugo‑Shortcodes:
gallery/gallery-item– Bildergalerie mit Modal, Keyboard‑Navigation und responsiven Thumbnailshero-image– Floating Hero‑Bild mit Textumfluss und Captiontech-icon/tech-icons– Technologie‑ und Expertise‑Icons als SVG‑Gridcallout– Hervorgehobene Info‑Boxenbutton– Styled Links als Buttonsterminal– Terminal‑Output im Code‑Block‑Stilcustomer-logos– Kundenlogos ausdata/references/
SEO als fester Bestandteil
SEO ist kein Nachgedanke, sondern eingebaut:
seoTitle‑Frontmatter – erlaubt kürzere<title>‑Tags unabhängig vom Seitentitel- Smart Title‑Logik – ist
seoTitle + Site-Suffix> 60 Zeichen, wird der Suffix weggelassen - Hreflang‑Tags mit
x-defaultfür die Standardsprache - Structured Data (Schema.org) für Organization, Article, BreadcrumbList
- OpenGraph & Twitter Cards über Frontmatter‑Parameter
- SEO‑Optimizer‑Script – crawlt die Live‑Seite und meldet fehlende Meta‑Tags, zu lange Titel, fehlende Descriptions
Mehrsprachigkeit durchgängig
Hugo’s Mehrsprachigkeit ist kein Plugin, sondern Kernfunktion:
- Dateibasierte Übersetzung:
index.de.md,index.en.md - i18n‑Dateien für alle UI‑Strings (
i18n/de.yaml,en.yaml) - Automatische Sprachumschalter mit Länderflaggen
- Permalinks pro Sprache konfigurierbar (
/projekte/:slug/vs./projects/:slug/) - Content‑Validator prüft, ob alle Übersetzungen vorhanden und konsistent sind
29 E2E‑Tests mit Playwright
Die Frontend‑Qualität wird nicht per Augenschein geprüft, sondern automatisiert:
tests/e2e/
├── navigation.spec.ts # Hauptmenü, Submenüs, Mobile
├── search.spec.ts # Suchmodal, Ergebnisse, Keyboard
├── theme-switching.spec.ts # Dark/Light Mode
├── seo-compliance.spec.ts # Meta-Tags, H1, Hreflang
├── gallery.spec.ts # Bildergalerie mit Modal
├── blog-layout.spec.ts # Blog-Seiten, Pagination
├── contact.spec.ts # Kontaktformular, Honeypot
├── image-srcset.spec.ts # Responsive Bilder
├── rss-sitemap.spec.ts # RSS-Feed, Sitemap
├── ... und 20 weitere
Die Tests laufen containerisiert über Podman – lokal und in CI mit identischem Setup:
make test
# oder gezielt:
npm run test:podman:rebuild -- --project=chromium navigation.spec.ts
Performance‑Optimierung: Lighthouse 75 → 99
Neben Funktionalität und Design haben wir die Seite gezielt auf Web‑Performance optimiert – gemessen mit Google Lighthouse.
Ausgangslage
Ein erster Lighthouse‑Audit auf einer Blog‑Seite ergab:
| Kategorie | Score |
|---|---|
| Performance | 75 |
| Accessibility | 100 |
| Best Practices | 100 |
| SEO | 100 |
Der Hauptgrund für den niedrigen Performance‑Score: ein Cumulative Layout Shift (CLS) von 0.577 – fast das Sechsfache des empfohlenen Schwellwerts (< 0.1). Die gesamte Seite verschob sich sichtbar, sobald die Web‑Schrift geladen wurde.
Die drei Stellschrauben
1. Font‑Loading ohne Layout‑Shift
Das Problem: font-display: swap zeigt zuerst die Systemschrift und tauscht dann Titillium Web ein. Da beide Schriften unterschiedliche Metriken haben (Zeichenbreite, Ober‑ und Unterlängen), verschiebt sich der gesamte Seiteninhalt.
Die Lösung: ein metrisch angepasster Fallback‑Font, der exakt den Platz von Titillium Web einnimmt:
@font-face {
font-family: 'Titillium Web Fallback';
src: local('Arial'), local('Helvetica Neue'), local('sans-serif');
ascent-override: 106%;
descent-override: 34%;
size-adjust: 97%;
}
Zusätzlich werden die beiden meistgenutzten Schriftschnitte (Regular 400 und Semibold 600) per <link rel="preload"> vorab geladen, sodass der Swap in vielen Fällen gar nicht mehr sichtbar ist.
Ergebnis: CLS von 0.577 → 0.
2. Erstes Bild nicht lazy laden
Hugo’s Bildverarbeitung setzt standardmäßig loading="lazy" auf alle Bilder. Auf Blog‑Seiten ist das erste Bild aber oft above the fold und damit das Largest Contentful Paint (LCP) Element. Lazy Loading verzögert hier das Rendering.
Über einen Zähler im Render‑Hook bekommt das erste Bild im Content automatisch loading="eager", alle weiteren bleiben lazy:
{{ $imgCount := .Page.Store.Get "render-image-count" | default 0 }}
{{ .Page.Store.Set "render-image-count" (add $imgCount 1) }}
{{ $loading := cond (eq $imgCount 0) "eager" "lazy" }}
3. Pre‑Compression in der CI‑Pipeline
GitLab Pages liefert automatisch vorab komprimierte Dateien aus, wenn .br‑ oder .gz‑Varianten vorhanden sind. In der CI‑Pipeline werden nach dem Hugo‑Build alle Text‑Assets mit Brotli und Gzip komprimiert:
- find public -type f -regex '.*\.\(html\|css\|js\|svg\|json\)$' -exec gzip -f -k {} \;
- find public -type f -regex '.*\.\(html\|css\|js\|svg\|json\)$' -exec brotli -f -k {} \;
Ergebnis
| Metrik | Vorher | Nachher |
|---|---|---|
| Performance Score | 75 | 99 |
| CLS | 0.577 | 0 |
| Speed Index | 2.5 s | 2.4 s |
| LCP | 1.4 s | 1.5 s |
| FCP | 0.9 s | 0.9 s |
Was wir gewonnen haben
Für das Design
- Tailwind statt Bootstrap – utility‑first, kein Framework‑Override, winziges CSS
- Glassmorphism & Dark Mode – durchgängig, konsistent, über CSS Variables gesteuert
- Responsive Bilder – AVIF/WebP automatisch, kein manuelles Optimieren
- Zero CDN – Fonts, JS, CSS – alles lokal, datenschutzkonform
Für Redakteur:innen
- DecapCMS – vertraute Oberfläche zum Erstellen und Bearbeiten von Inhalten
- Editorial Workflow – Änderungen als Merge Requests, mit Review‑Option
- Lokales Preview –
make servestartet alles, inklusive CMS - Content‑Validator – fängt Fehler ab, bevor sie live gehen
Für Entwickler:innen
- Alles versioniert – Inhalte, Layouts, Tests, Validierungslogik
- Container‑Workflow – identische Umgebung lokal und in CI
- 29 E2E‑Tests – Regressions werden sofort sichtbar
- Ein
Makefile– ein Kommando für serve, test, build, validate
Für die Zukunft
- Neue Inhalte entstehen als Markdown‑Bundles mit klarer Struktur
- Neue Shortcodes erweitern die Möglichkeiten der Redaktion
- Die statische Auslieferung macht die Seite schnell, sicher und günstig im Betrieb
Fazit
Die BusinessCode‑Website hat drei Stationen durchlaufen – jede davon hat das Projekt weitergebracht:
- WordPress → Hugo: weg von einer fragilen, immer weiter zerfallenden Hosting‑Lösung hin zu statischer Auslieferung und Git als Single Source of Truth
- Hextra → eigenes BCD‑Theme: weg von Bootstrap‑Overhead hin zu Tailwind CSS mit Bento‑Layout und Glassmorphism
- Manuelles Editieren → DecapCMS + CI/CD: weg von FTP‑Deploys hin zu einem Review‑Workflow mit automatischer Validierung
Hugo hat sich über beide Iterationen als leistungsfähiger Baukasten erwiesen: Mehrsprachigkeit, Image Processing, Taxonomien und eine flexible Template‑Engine – alles out‑of‑the‑box. In Kombination mit Tailwind CSS für das Design, DecapCMS für die Redaktion und GitLab CI/CD für den Betrieb ist ein System entstanden, das modern aussieht, sich robust anfühlt und für alle Beteiligten transparent und nachvollziehbar ist.