← Übersicht  ·  Skripte & Dateien  ·  cloud-host · Web

Statischer Blog

Blog, Doku oder Landingpage aus Markdown: Hugo oder Astro generiert HTML (Hypertext Markup Language), dann Cloudflare Pages (gratis, Domain + TLS, Git-Deploy) oder ein nginx-Container auf dem Server. Mit Schreiben, Bildern & Performance, Kommentaren (giscus), statischer Suche (Pagefind), Analytics ohne Cookies, CI (Continuous Integration)/CD (Continuous Delivery) und Migration von WordPress.

Stand: 30. August 2026 Hugo / Astro → Pages / nginx 0 €

Platzhalter einsetzen – nur im Browser

Generator wählen

HugoAstro11ty
SpracheGo (ein Binary)Node / JS (JavaScript) + TSNode / JS
Build 500 Seiten< 1 s~10–30 s~5–15 s
Interaktive Komponentennur JS von HandReact/Vue/Svelte „Islands“JS von Hand
Themesviele, teils älterweniger, modernerwenige
nimm es, wenn…reiner Text/Doku, Geschwindigkeit, kein Node-Setupdu willst hier und da eine React-Komponentedu magst JS und volle Kontrolle über jeden Schritt
schreibenMarkdown-Dateien im Git-Repo (content/ bzw. src/content/)
bauenhugo / npm run build → statisches HTML nach public/ bzw. dist/
deployenA Cloudflare Pages (Git-Push) · B nginx-Container · C Object-Storage-Bucket
Domainblog.seb4u.com – bei Pages ein Klick, bei nginx ein Traefik-Label

Diese 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

Laptop · PowerShell – Hugo
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
Alternativ · Astro
npm create astro@latest blog -- --template blog --typescript strict
cd blog && npm run dev                  # http://localhost:4321
Datei · content/posts/hallo-welt.md (Hugo Front Matter)
+++
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.jpg daneben – Bilder wandern mit dem Text, Pfade sind relativ.
  • draft = true → erscheint nur mit hugo server -D, nicht im Build. Terminplanung: date in der Zukunft + --buildFuture=false.
  • Code-Highlighting: Hugo bringt Chroma mit ([markup.highlight] in hugo.toml), Astro Shiki – beides ohne Client-JS.
  • RSS (Really Simple Syndication)/Atom: Hugo erzeugt /index.xml automatisch. 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

  1. Repo zu GitHub pushen (nursude/blog).
  2. dash.cloudflare.com Workers & Pages Create Pages Connect to Git → das Repo.
  3. Build: Framework-Preset Hugo, Build-Befehl hugo --gc --minify, Output public. Env-Var HUGO_VERSION = die aus hugo version. (Astro: npm run build, Output dist, NODE_VERSION.)
  4. Custom Domain: im Pages-Projekt Custom domainsblog.seb4u.com. Cloudflare legt den CNAME (Canonical Name) in deiner Zone selbst an.
Datei · static/_headers (bzw. public/_headers)
/*
  X-Frame-Options: DENY
  Referrer-Policy: strict-origin-when-cross-origin
/assets/*
  Cache-Control: public, max-age=31536000, immutable
Was du gratis bekommst

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.

Datei · /opt/stacks/blog/compose.yaml
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 } }
Datei · nginx.conf
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"; }
}
Laptop · bauen & hochladen
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.

Laptop
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> aus astro: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

WerkzeugModellAnmerkung
giscusGitHub Discussions als Backendkostenlos, kein Server, Kommentierer brauchen GitHub-Login – passt für ein Tech-Publikum
Cusdiswinziger self-hosted Dienstanonyme Kommentare, Moderation per Mail/Telegram, ein kleiner Container
issoself-hosted (Python + SQLite)Disqus-Ersatz, DSGVO-freundlich, etwas Pflege
keineKommentare per E-Mail / Mastodon-Antworten einbetten – am wenigsten Ballast
giscus im Post-Template
<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

WerkzeugwoAnmerkung
Cloudflare Web Analyticsgehostet, gratisein Script-Tag (oder automatisch bei Pages), keine Cookies, keine personenbezogenen Daten → meist kein Consent-Banner nötig
Plausible / Umamiself-hosted (kleiner Container)huebsches Dashboard, cookiefrei, volle Datenhoheit
GoatCountergehostet (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):

Datei · .github/workflows/site.yml
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

  1. In WordPress: Werkzeuge → Daten exportieren → Alle Inhalte → XML-Datei.
  2. npx wordpress-export-to-markdown → erzeugt Markdown-Dateien mit Front Matter und lädt die Bilder herunter.
  3. Inhalte durchgehen (Shortcodes, Galerien, Plugins-Ausgaben von Hand fixen), ins Hugo/Astro-Projekt.
  4. URLs (Uniform Resource Locators) behalten: pro Beitrag aliases = ["/2019/05/alter-slug/"] (Hugo) – erzeugt eine Weiterleitungs-Seite. Oder _redirects bei Pages.
  5. 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 bundle im Off-site-Backup.
  • Theme-Updates: git submodule update --remote bzw. npm update, lokal bauen und ansehen, dann pushen.
  • Kaputte Links monatlich: lychee lokal 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 + giscus0
nginx-Container auf bestehendem Server0 extra
+ Umami/Plausible self-hosted (Analytics-Dashboard)0 extra (ein Container)
Object-Storage-Website (große Seite)~6 (Bucket)
⌂ Cockpit