Generator wählen
| Hugo | Astro | 11ty | |
|---|---|---|---|
| Sprache | Go (ein Binary) | Node / JS (JavaScript) + TS | Node / JS |
| Build 500 Seiten | < 1 s | ~10–30 s | ~5–15 s |
| Interaktive Komponenten | nur JS von Hand | React/Vue/Svelte „Islands“ | JS von Hand |
| Themes | viele, teils älter | weniger, moderner | wenige |
| nimm es, wenn… | reiner Text/Doku, Geschwindigkeit, kein Node-Setup | du willst hier und da eine React-Komponente | du magst JS und volle Kontrolle über jeden Schritt |
content/ bzw. src/content/)hugo / npm run build → statisches HTML nach public/ bzw. dist/blog.seb4u.com – bei Pages ein Klick, bei nginx ein Traefik-LabelDiese Anleitung nimmt Hugo als Beispiel (Astro-Befehle in Klammern) und empfiehlt Weg A für einen Blog – kein Server, kostenlos, mit Vorschau-Deployments je Pull Request.
1 Projekt anlegen
winget install Hugo.Hugo.Extended # "extended" fuer SCSS + Bildverarbeitung
hugo new site blog && cd blog
git init
git submodule add https://github.com/<theme-repo> themes/mein-theme
"theme = 'mein-theme'" | Add-Content hugo.toml
hugo new content posts/hallo-welt.md
hugo server -D # http://localhost:1313, -D zeigt Entwuerfe
npm create astro@latest blog -- --template blog --typescript strict
cd blog && npm run dev # http://localhost:4321
+++
title = "Hallo Welt"
date = 2026-08-30
draft = false
tags = ["meta"]
+++
Erster Absatz. Ganz normales **Markdown**.
2 Schreiben
- Ein Ordner pro Beitrag (Page Bundle):
content/posts/hallo/index.md+cover.jpgdaneben – Bilder wandern mit dem Text, Pfade sind relativ. draft = true→ erscheint nur mithugo server -D, nicht im Build. Terminplanung:datein der Zukunft +--buildFuture=false.- Code-Highlighting: Hugo bringt Chroma mit (
[markup.highlight]inhugo.toml), Astro Shiki – beides ohne Client-JS. - RSS (Really Simple Syndication)/Atom: Hugo erzeugt
/index.xmlautomatisch. Astro nutzt@astrojs/rss. In den<head>verlinken. - Shortcodes (Hugo) / MDX-Komponenten (Astro) für wiederkehrende Bausteine (Hinweiskasten, YouTube-Einbettung ohne Tracking…).
3 Weg A: Cloudflare Pages
- Repo zu GitHub pushen (
nursude/blog). - → das Repo.
- Build: Framework-Preset Hugo, Build-Befehl
hugo --gc --minify, Outputpublic. Env-VarHUGO_VERSION= die aushugo version. (Astro:npm run build, Outputdist,NODE_VERSION.) - Custom Domain: im Pages-Projekt Custom domains →
blog.seb4u.com. Cloudflare legt den CNAME (Canonical Name) in deiner Zone selbst an.
/*
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
/assets/*
Cache-Control: public, max-age=31536000, immutable
TLS (Transport Layer Security), globales CDN (Content Delivery Network), unbegrenzte Requests, Vorschau-Deployment je Branch/PR
(<branch>.blog.pages.dev), Rollback per Klick, _redirects für
Weiterleitungen, Web Analytics (Abschnitt 8). 500 Builds/Monat reichen locker.
4 Weg B: nginx auf dem Server
Wenn der Blog zum bestehenden Setup gehört (Traefik ist schon da) oder du keine externe Abhängigkeit willst. Der Blog ist dann einfach ein weiterer Stack nach dem Traefik-Label-Muster aus Mehrere Dienste auf einem Server.
services:
blog:
image: nginx:1.27-alpine
restart: unless-stopped
volumes:
- /opt/sites/blog:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks: [ web ]
labels:
- traefik.enable=true
- traefik.http.routers.blog.rule=Host(`blog.seb4u.com`)
- traefik.http.routers.blog.entrypoints=websecure
- traefik.http.routers.blog.tls.certresolver=le
networks: { web: { name: web, external: true } }
server {
listen 80;
root /usr/share/nginx/html;
gzip on; gzip_types text/css application/javascript image/svg+xml;
location / { try_files $uri $uri/ $uri.html /404.html; }
location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; }
}
hugo --gc --minify
rsync -az --delete ./public/ deploy@<server>:/opt/sites/blog/
# oder: .\scripts\deploy.ps1 blog -Source ./public (geschuetzter-projekt-host)
DNS (Domain Name System): blog als A/CNAME auf den
Server (oder den Cloudflare Tunnel).
5 Weg C: aus einem Bucket
Bei einer sehr großen Seite (viele GB Assets): den Build in einen Object-Storage-Bucket als statische Website, Cloudflare (proxied CNAME) davor für TLS + Domain + Caching. Für einen normalen Blog ist das Overkill – Pages liefert Assets ohnehin vom CDN.
hugo --gc --minify
rclone sync ./public hos:seb4u-blog --checksum
# Bucket oeffentlich lesbar + s3 website-config, dann blog. CNAME (proxied) auf die Website-URL
6 Bilder & Performance
- Beim Build verkleinern: Hugo
resources.Get+.Resize "800x webp q80", Astro<Image>ausastro:assets– nie das 4000-px-Original ausliefern. - Responsive: mehrere Grössen +
srcset, moderne Formate (WebP/AVIF) mit JPEG-Fallback,loading="lazy"außer beim ersten Bild. - Kein riesiger Hero: die Startseite unter ~300 KB gesamt halten. Lighthouse (in Chrome DevTools) / PageSpeed als Kontrolle, Ziel 95+.
- Schriften: selbst hosten (Google-Fonts-Datei ins Repo),
font-display: swap, nur die Schnitte, die du nutzt. - Kein Client-JS, wo es nicht sein muss – das ist der Grund, warum statisch schnell ist.
7 Kommentare & Suche
Kommentare
| Werkzeug | Modell | Anmerkung |
|---|---|---|
| giscus | GitHub Discussions als Backend | kostenlos, kein Server, Kommentierer brauchen GitHub-Login – passt für ein Tech-Publikum |
| Cusdis | winziger self-hosted Dienst | anonyme Kommentare, Moderation per Mail/Telegram, ein kleiner Container |
| isso | self-hosted (Python + SQLite) | Disqus-Ersatz, DSGVO-freundlich, etwas Pflege |
| keine | – | Kommentare per E-Mail / Mastodon-Antworten einbetten – am wenigsten Ballast |
<script src="https://giscus.app/client.js"
data-repo="nursude/blog"
data-repo-id="R_k..." data-category="Comments" data-category-id="DIC_..."
data-mapping="pathname" data-theme="preferred_color_scheme" crossorigin="anonymous" async></script>
Suche
Pagefind ist statisch – nach dem Build läuft
npx pagefind --site public und erzeugt einen Suchindex + UI (User Interface), der im Browser
läuft, kein Server. Für OSS-Doku: Algolia DocSearch (gratis für offene Projekte).
8 Analytics ohne Cookies
| Werkzeug | wo | Anmerkung |
|---|---|---|
| Cloudflare Web Analytics | gehostet, gratis | ein Script-Tag (oder automatisch bei Pages), keine Cookies, keine personenbezogenen Daten → meist kein Consent-Banner nötig |
| Plausible / Umami | self-hosted (kleiner Container) | huebsches Dashboard, cookiefrei, volle Datenhoheit |
| GoatCounter | gehostet (gratis für Nicht-Kommerz) / self-hosted (ein Binary) | minimalistisch, ein Script-Tag |
Cookiefrei + keine personenbezogenen Daten = in aller Regel kein Cookie-Banner. Impressum & Datenschutzerklärung trotzdem (siehe Grundausstattung).
9 CI/CD
Bei Weg A baut Cloudflare selbst bei jedem Push – nichts zu tun. Für Weg B/C ein Workflow (Details: CI/CD-Anleitung):
on: { push: { branches: [ main ] } }
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { submodules: recursive }
- uses: peaceiris/actions-hugo@v3
with: { hugo-version: latest, extended: true }
- run: hugo --gc --minify
- run: npx -y pagefind --site public # optionale Suche
# Weg B: ueber Tailscale zum Server (kein offener Port)
- uses: tailscale/github-action@v3
with: { oauth-client-id: '${{ secrets.TS_OAUTH_CLIENT_ID }}', oauth-secret: '${{ secrets.TS_OAUTH_SECRET }}', tags: tag:ci }
- run: |
printf '%s\n' "${{ secrets.SSH_KEY }}" > k && chmod 600 k
rsync -az --delete -e "ssh -i k -o StrictHostKeyChecking=accept-new" \
public/ deploy@<server>.tailXXXX.ts.net:/opt/sites/blog/
Kaputte Links im Build
prüfen: lychee ./public als eigener Schritt – scheitert der, ist ein
Link tot.
+ Migration von WordPress
- In WordPress: Werkzeuge → Daten exportieren → Alle Inhalte → XML-Datei.
npx wordpress-export-to-markdown→ erzeugt Markdown-Dateien mit Front Matter und lädt die Bilder herunter.- Inhalte durchgehen (Shortcodes, Galerien, Plugins-Ausgaben von Hand fixen), ins Hugo/Astro-Projekt.
- URLs (Uniform Resource Locators) behalten: pro Beitrag
aliases = ["/2019/05/alter-slug/"](Hugo) – erzeugt eine Weiterleitungs-Seite. Oder_redirectsbei Pages. - WordPress-Server danach abbauen (WordPress-Anleitung).
Wann bleiben? Wenn Redakteure ohne Git arbeiten, du Plugins/Shop/Formulare brauchst, oder Kommentare zentral sind. Statisch lohnt bei „ich schreibe, es soll schnell und wartungsarm sein“.
+ Betrieb
- Der Inhalt ist in Git – das ist das Backup. Repo bei
GitHub + ein
git bundleim Off-site-Backup. - Theme-Updates:
git submodule update --remotebzw.npm update, lokal bauen und ansehen, dann pushen. - Kaputte Links monatlich:
lycheelokal oder im CI. - Kein Server zu patchen (Weg A), keine Datenbank, keine Angriffsfläche – das ist der eigentliche Gewinn.
+ Kosten
Preise sind Richtwerte. Hetzner hat 2026 zweimal erhöht (zuletzt 15. Juni: CX/CAX +30–40 %, CPX/CCX über 100 %). Aktuelle Server-Preise, alle Nebenposten, ein Rechner und Beispielrechnungen stehen zentral in Kosten & Budget.
| Variante | €/Monat |
|---|---|
| Cloudflare Pages + Domain (hast du) + CF Web Analytics + giscus | 0 |
| nginx-Container auf bestehendem Server | 0 extra |
| + Umami/Plausible self-hosted (Analytics-Dashboard) | 0 extra (ein Container) |
| Object-Storage-Website (große Seite) | ~6 (Bucket) |