Automatische Blog-Veröffentlichung mit Jekyll und GitHub Pages – Zero-Config Publishing für Entwickler

Einleitung: Warum überhaupt ein Blog?

Während der Entwicklung von BudgetBuddy habe ich ausführliche Blogposts über jeden Milestone geschrieben: OAuth-Integration mit Comdirect, die Rules Engine, Backend-API-Implementation. Diese Posts lagen als Markdown-Dateien im diary/posts/-Ordner – lesbar auf GitHub, aber nicht wirklich als Blog präsentiert.

Die Frage kam auf: Können wir diese Posts automatisch veröffentlichen? Idealerweise bei jedem git push, ohne zusätzlichen Aufwand, ohne Server-Wartung, und natürlich kostenlos.

Die Antwort: Ja, mit Jekyll und GitHub Pages. In diesem Post beschreibe ich, wie ich das in etwa 15 Minuten eingerichtet habe, welche Entscheidungen ich getroffen habe, und welche Alternativen ich betrachtet habe.

Ausgangslage: Was war bereits vorhanden?

Vor diesem Setup hatte ich bereits:

diary/
├── development.md          # Entwicklungstagebuch
└── posts/
    ├── 2025-11-29-integration-testing-ohne-ui.md
    ├── 2025-11-29-milestone-4-comdirect-integration.md
    ├── 2025-11-29-milestone-5-rules-engine.md
    └── 2025-11-29-milestone-6-backend-api-implementation.md

Vier ausführliche technische Blogposts, jeder zwischen 500 und 1000 Zeilen Markdown. Einige hatten bereits rudimentäres Front Matter (YAML-Metadaten am Anfang), andere nicht.

Herausforderung 1: Welches Static-Site-Generator-Tool?

Das Problem

Es gibt dutzende Static Site Generators: Jekyll, Hugo, 11ty, Gatsby, Next.js, Astro… Jeder hat Vor- und Nachteile. Für ein Entwickler-Blog mit Markdown-Dateien musste ich abwägen zwischen Features, Komplexität und Integration.

Optionen, die ich betrachtet habe

1. Jekyll (gewählt)

2. Hugo

3. Nur Markdown auf GitHub

Entscheidung: Jekyll

Für ein kleines Entwickler-Blog ist Jekyll perfekt. Die native GitHub Pages-Integration bedeutet: Weniger bewegliche Teile, weniger was kaputtgehen kann. Hugo wäre schneller, aber bei 4 Blogposts spielt Build-Zeit keine Rolle.

Rationale: Bei Infrastructure-Entscheidungen bevorzuge ich immer die Lösung mit den wenigsten Abhängigkeiten. Jekyll + GitHub Pages = ein Workflow-File, eine Config-Datei, fertig.

Herausforderung 2: Jekyll-Konventionen verstehen

Das Problem

Jekyll hat spezifische Konventionen, die man kennen muss:

Meine bestehenden Posts lagen in posts/ (ohne Unterstrich) und hatten teilweise kein oder unvollständiges Front Matter.

Die Lösung

Schritt 1: Ordner umbenennen

mv diary/posts diary/_posts

Der Unterstrich ist Jekylls Signal: “Das ist eine Collection, verarbeite diese Dateien.”

Schritt 2: Front Matter hinzufügen

Jeder Post braucht mindestens:

---
title: "Der Titel des Posts"
date: 2025-11-29
author: Claude
tags: [F#, Jekyll, Tutorial]
description: "Kurze Beschreibung für Previews"
---

Zwei meiner Posts hatten bereits Front Matter, zwei nicht. Ich habe alle vier vereinheitlicht:

# Vorher (Milestone 6):
# Milestone 6: Backend API Implementation...
# **Datum:** 2025-11-29

# Nachher:
---
title: "Milestone 6: Backend API Implementation  29 Endpoints..."
date: 2025-11-29
author: Claude
tags: [F#, Backend, Fable.Remoting, API-Design, Type Safety]
description: "Von isolierten Modulen zur vollständigen API..."
---

Warum description wichtig ist: Jekyll nutzt es für Post-Previews auf der Index-Seite. Ohne explizite Description wird der erste Absatz verwendet – oft nicht ideal.

Herausforderung 3: Die Jekyll-Konfiguration

Das Problem

Jekyll braucht eine _config.yml im Root des zu veröffentlichenden Ordners. Diese steuert Theme, Permalinks, Collections und mehr.

Die Lösung: Minimale, aber vollständige Konfiguration

# diary/_config.yml
title: BudgetBuddy Development Blog
description: Entwicklungstagebuch und technische Blogposts
author: Roman Sachse & Claude

# Theme - Minima ist einfach und sauber
theme: minima

# Permalinks ohne Datum in der URL
permalink: /posts/:title/

# Navigation
header_pages:
  - index.md
  - development.md

Architekturentscheidung: Warum permalink: /posts/:title/?

Standard-Jekyll-Permalinks enthalten das Datum: /2025/11/29/titel/. Das ist für News-Sites sinnvoll, für technische Tutorials nicht. Ein Post über “Jekyll Setup” ist auch in 2 Jahren noch relevant – das Datum in der URL suggeriert Veraltung.

Mit /posts/:title/ bekomme ich saubere URLs wie:

https://rommsen.github.io/BudgetBuddy/posts/automatische-blog-veroeffentlichung/

Herausforderung 4: Die Index-Seite

Das Problem

Jekyll braucht eine Startseite, die die Posts auflistet. Ohne eigene Index-Seite sieht man nur eine leere Seite.

Die Lösung

# diary/index.md
---
layout: home
title: Home
---

# BudgetBuddy Development Blog

Willkommen beim Entwicklungsblog von BudgetBuddy...

## Blogposts


### [Automatische Blog-Veröffentlichung mit Jekyll und GitHub Pages – Zero-Config Publishing für Entwickler](/BudgetBuddy/posts/automatische-blog-veroeffentlichung-github-pages/)
*29.11.2025* - Wie ich in 15 Minuten ein automatisches Blog-Publishing-System mit Jekyll und GitHub Actions aufgesetzt habe – komplett kostenlos und ohne Server.

### [Integration Testing ohne UI: Wie ich YNAB und Comdirect APIs testbar machte](/BudgetBuddy/posts/integration-testing-ohne-ui/)
*29.11.2025* - Wie teste ich externe API-Integrationen, wenn das UI noch nicht fertig ist? Mit F# Scripts und CLI-Tools.

### [Milestone 4: Comdirect API Integration – OAuth, Push-TAN und die Tücken von Banking-APIs](/BudgetBuddy/posts/milestone-4-comdirect-integration/)
*29.11.2025* - Implementierung einer sicheren Bankverbindung mit OAuth 2.0 und Push-TAN für die Comdirect API.

### [Milestone 5: Rules Engine - Automatische Kategorisierung mit F# Regex und Type Safety](/BudgetBuddy/posts/milestone-5-rules-engine/)
*29.11.2025* - Das Herzstück der Automatisierung: Regelbasierte Kategorisierung mit Pattern Matching, Regex und F#'s Type-System.

### [Milestone 6: Backend API Implementation – 29 Endpoints und die Kunst der Fehlerbehandlung in F#](/BudgetBuddy/posts/milestone-6-backend-api-implementation/)
*29.11.2025* - Von isolierten Modulen zur vollständigen API: 29 Endpoints mit konsistenter Fehlerbehandlung, Input-Validation und Session-Management.

### [Frontend Redesign Mobile First](/BudgetBuddy/posts/frontend-redesign-mobile-first/)
*30.11.2025* - 

### [Milestone 7: Frontend-Implementierung – Von der leeren Leinwand zur vollständigen Elmish-App](/BudgetBuddy/posts/milestone-7-frontend-implementation/)
*30.11.2025* - 

### [Von CSS-Chaos zu F# Design System: Eine Typensichere UI-Komponentenbibliothek aufbauen](/BudgetBuddy/posts/neon-design-system-implementierung/)
*30.11.2025* - 

### [Bug-Jagd: Wenn die YNAB API nicht das liefert, was man erwartet](/BudgetBuddy/posts/ynab-category-groups-bug-fix/)
*30.11.2025* - 

### [Von 1000 Zeilen Monolith zu modularen MVU-Komponenten – Frontend-Refactoring in F#/Elmish](/BudgetBuddy/posts/mvu-component-refactoring/)
*30.11.2025* - 

### [Ein Neon-Design-System planen – Vom DaisyUI-Standard zur eigenen visuellen Identität](/BudgetBuddy/posts/neon-design-system-planung/)
*30.11.2025* - 

### [Neon Design System: Phase 3 & 4 – Der letzte Schliff](/BudgetBuddy/posts/neon-design-system-phase-3-4-abschluss/)
*04.12.2025* - 

### [Milestone 13: Duplicate Detection – Wie ich doppelte Bank-Transaktionen in YNAB verhindere](/BudgetBuddy/posts/milestone-13-duplicate-detection/)
*05.12.2025* - 

### [Milestone 14: Split Transactions – Wenn eine Transaktion in mehrere Kategorien gehört](/BudgetBuddy/posts/milestone-14-split-transactions/)
*06.12.2025* - 

### [Milestone 15: Polish & Testing - Der finale Schliff](/BudgetBuddy/posts/milestone-15-polish-und-testing/)
*06.12.2025* - 

### [Test-Isolation: Warum meine Persistence-Tests 236 Rules in die Production-DB geschrieben haben](/BudgetBuddy/posts/test-isolation-in-memory-sqlite/)
*07.12.2025* - 

### [Debugging-Marathon: YNAB-Integration, Race Conditions und die Tücken von JSON-Serialisierung](/BudgetBuddy/posts/debugging-marathon-ynab-integration/)
*08.12.2025* - 

### [Test-Coverage und Datenbereinigung: Von 220 auf 279 Tests und die Jagd nach unsichtbaren Zeichen](/BudgetBuddy/posts/test-coverage-und-datenbereinigung/)
*09.12.2025* - 

### [UI-Optimierungen: Kompakte Listen, Searchable Selects und Optimistic UI](/BudgetBuddy/posts/ui-optimierungen-fuer-transaktionen-und-regeln/)
*09.12.2025* - 

### [Transparente Duplicate Detection: Warum zeigt mir BudgetBuddy Duplikate an?](/BudgetBuddy/posts/transparente-duplicate-detection/)
*11.12.2025* - 

### [UI Performance Revolution: Von 15 Sekunden auf 18ms und eine neue SearchableSelect-Komponente](/BudgetBuddy/posts/ui-performance-revolution-und-searchable-selects/)
*11.12.2025* - 

### [Docker-Operations: Encryption-Bug, Deploy-Script und Comdirect Connection Test](/BudgetBuddy/posts/docker-ops-comdirect-test-encryption/)
*15.12.2025* - 

### [Form Validation UX und minimalistisches Dashboard Redesign](/BudgetBuddy/posts/form-validation-ux-und-dashboard-redesign/)
*15.12.2025* - 

### [Frontend Architecture Refactoring: 8 Milestones zur besseren Codequalität](/BudgetBuddy/posts/frontend-architecture-refactoring/)
*15.12.2025* - 

### [BudgetBuddy Architektur-Guide: Ein F# Full-Stack Deep Dive](/BudgetBuddy/posts/budgetbuddy-architektur-guide/)
*16.12.2025* - 

### [Der versteckte Bug: Wie ein Format-Mismatch zu Duplikaten führte](/BudgetBuddy/posts/import-id-format-mismatch-bug/)
*16.12.2025* - 

### [Payee-Feature: ComboBox mit Gruppierung und External Links](/BudgetBuddy/posts/payee-combobox-gruppierung-und-external-links/)
*29.12.2025* - 

### [DaisyUI raus, Prototyp rein: Eine CSS-Framework-Migration in einer F# Elmish App](/BudgetBuddy/posts/daisyui-removal-prototype-alignment/)
*22.03.2026* - 

Der Trick mit reversed: Jekyll sortiert Posts standardmäßig neueste-zuerst. Für ein Entwicklungstagebuch macht chronologische Reihenfolge (älteste zuerst) mehr Sinn – man liest die Geschichte von Anfang an. reversed dreht die Sortierung um.

Herausforderung 5: GitHub Actions Workflow

Das Problem

GitHub Pages kann Jekyll automatisch bauen, aber nur wenn die Dateien im Repository-Root oder in /docs liegen. Meine Dateien liegen in /diary – ich brauche einen custom Build.

Die Lösung

# .github/workflows/deploy-diary.yml
name: Deploy Diary to GitHub Pages

on:
  push:
    branches: ["main"]
    paths:
      - 'diary/**'  # Nur bei Änderungen im diary-Ordner
  workflow_dispatch:  # Manueller Trigger möglich

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/configure-pages@v5
      - uses: actions/jekyll-build-pages@v1
        with:
          source: ./diary      # Hier liegt unser Content
          destination: ./_site
      - uses: actions/upload-pages-artifact@v3

  deploy:
    environment:
      name: github-pages
      url: $
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/deploy-pages@v4

Wichtige Entscheidungen:

  1. paths: ['diary/**'] – Der Workflow läuft NUR wenn sich Dateien im diary/-Ordner ändern. Spart CI-Minuten und verhindert unnötige Deploys bei Backend-Änderungen.

  2. source: ./diary – Sagt Jekyll, wo die Dateien liegen. Standard wäre Repository-Root.

  3. Zwei-Job-Struktur – Build und Deploy sind getrennt. Das ist GitHub’s empfohlenes Pattern für Pages-Deployments und ermöglicht besseres Debugging.

  4. workflow_dispatch – Erlaubt manuelles Auslösen des Workflows über die GitHub-UI. Nützlich zum Testen.

Herausforderung 6: Einmalige Aktivierung auf GitHub

Das Problem

GitHub Pages muss einmalig in den Repository-Settings aktiviert werden. Es gibt zwei Modi:

Die Lösung

Da wir einen custom Workflow haben, muss “GitHub Actions” als Source gewählt werden:

  1. Repository Settings → Pages
  2. Source: GitHub Actions
  3. Speichern

Warum nicht “Deploy from a branch”?

Dieser Modus erwartet fertig gebaute HTML-Dateien in einem Branch (meist gh-pages) oder im /docs-Ordner. Wir wollen aber, dass GitHub unsere Markdown-Dateien mit Jekyll verarbeitet – dafür brauchen wir den Actions-Modus.

Das Ergebnis

Nach dem Setup sieht die Struktur so aus:

BudgetBuddy/
├── .github/workflows/
│   └── deploy-diary.yml    # Automatischer Build & Deploy
├── diary/
│   ├── _config.yml         # Jekyll-Konfiguration
│   ├── index.md            # Blog-Startseite
│   ├── development.md      # Entwicklungstagebuch
│   └── _posts/             # Blogposts (umbenannt von posts/)
│       ├── 2025-11-29-integration-testing-ohne-ui.md
│       ├── 2025-11-29-milestone-4-comdirect-integration.md
│       ├── 2025-11-29-milestone-5-rules-engine.md
│       └── 2025-11-29-milestone-6-backend-api-implementation.md
└── ... (restlicher Code)

Workflow:

  1. Ich schreibe einen neuen Post in diary/_posts/YYYY-MM-DD-titel.md
  2. git add . && git commit && git push
  3. GitHub Actions baut automatisch
  4. Post ist live unter https://rommsen.github.io/BudgetBuddy/posts/titel/

Statistiken:

Lessons Learned

1. Jekyll-Konventionen sind strikt

Der Unterstrich bei _posts ist kein Vorschlag – ohne ihn findet Jekyll die Posts nicht. Gleiches gilt für das Dateinamenformat. Ich habe erst nach dem Umbenennen verstanden, warum das wichtig ist.

2. Front Matter ist essentiell

Ohne Front Matter behandelt Jekyll Dateien als statische Assets, nicht als Posts. Selbst ein minimales ---\n--- reicht, aber explizite Metadaten (title, date, description) machen alles schöner.

3. Die offizielle GitHub Actions sind robust

Statt eigene Jekyll-Installation zu scripten, nutze ich actions/jekyll-build-pages@v1. Das ist gewartet, getestet, und funktioniert. Bei DevOps-Tooling: Immer die offiziellen Actions bevorzugen.

4. path-Filter sparen CI-Zeit

Ohne paths: ['diary/**'] würde der Workflow bei jedem Push laufen – auch bei Backend-Änderungen. Der Filter ist simpel aber wichtig für Effizienz.

Fazit

Von “Markdown-Dateien im Git-Repo” zu “Automatisch veröffentlichter Blog” in 15 Minuten und ~50 Zeilen Konfiguration. GitHub Pages + Jekyll ist für Entwickler-Blogs eine hervorragende Lösung:

Für komplexere Blogs mit mehr Customization wäre Hugo oder Astro eine Überlegung wert. Für ein Entwicklungstagebuch ist Jekyll perfekt.

Key Takeaways für Neulinge

  1. Jekyll + GitHub Pages = Zero-Config Publishing: Wenn du Markdown-Dateien hast und einen Blog willst, ist das die schnellste Lösung. Keine Server, keine Kosten, ein Workflow-File.

  2. Konventionen vor Konfiguration: Jekyll hat strikte Konventionen (_posts/, Front Matter, Dateinamen). Lerne sie einmal, dann funktioniert alles automatisch. Gegen Konventionen zu kämpfen kostet mehr Zeit als sie zu verstehen.

  3. Automatisierung lohnt sich früh: Das Setup hat 15 Minuten gedauert. Jeder zukünftige Post wird mit null Extra-Aufwand veröffentlicht. Bei Infrastructure-Investment: Je früher, desto mehr Return.