LabCodeHub öffentliche Ansicht
Anmelden
Küpper / Quildrop öffentlich

fix: new blog post

R Rüdiger Küpper <rpr@9it.de> committete am 19.02.2026 03:07
f3faa1b1dbe0f82e48773178aa234ee9c0406770
3 geänderte Datei(en) +147 −0
Kontextzeilen: 0 1 2 3 10
hinzugefügt content/2025-06-19-quilldrop-mein-eigenes-blog-cms.md
+108 −0 Datei ansehen
@@ -0,0 +1,108 @@
1 +
---
2 +
title: 'QuillDrop - Mein eigenes Blog-CMS ist fertig'
3 +
date: 2025-06-19 10:00:00
4 +
author: ruediger
5 +
tags: [Go, Blog, CMS, QuillDrop, Open-Source, Self-Hosted]
6 +
categories:
7 +
  - Internet
8 +
  - Projekte
9 +
preview: "Nach etlichen CMS- und Static-Site-Generator-Abenteuern mit Jekyll, Hugo und InkProject habe ich mein Blog jetzt komplett auf mein eigenes CMS migriert: QuillDrop. Geschrieben in Go, ohne Frameworks, ohne Datenbank - ein einziges Binary für alles."
10 +
draft: false
11 +
top: false
12 +
type: post
13 +
hide: false
14 +
toc: true
15 +
---
16 +
17 +
# QuillDrop - Mein eigenes Blog-CMS ist fertig
18 +
19 +
Jekyll, Octopress, Hugo, InkProject und dann noch mein eigenes CMS in Ruby - die Liste der Systeme, auf denen dieser Blog im Laufe der Jahre gelaufen ist, ist lang. Jetzt kommt ein neues dazu, aber dieses Mal ist es anders. Dieses Mal habe ich das Ding komplett selbst geschrieben: **QuillDrop**.
20 +
21 +
## Warum schon wieder ein neues CMS?
22 +
23 +
Die Kurzfassung: Weil keines genau das gemacht hat, was ich wollte - und gleichzeitig nichts, was ich nicht wollte.
24 +
25 +
Hugo ist großartig, aber die Template-Sprache ist eine Welt für sich und das Debugging bei Problemen kann schnell frustrierend werden. Mein vorheriges Ruby-basiertes CMS hat funktioniert, war aber mit der Zeit zu komplex geworden. Was ich wollte, war simpel: Markdown-Dateien schreiben, ein Binary starten, fertig. Keine Node-Module, keine Build-Pipeline, keine externe Datenbank.
26 +
27 +
Also habe ich QuillDrop gebaut.
28 +
29 +
## Was ist QuillDrop?
30 +
31 +
QuillDrop ist ein Blog-CMS, geschrieben in Go. Ein einzelnes Binary, das zwei Dinge kann:
32 +
33 +
- **`quilldrop serve`** startet einen lokalen HTTP-Server zum Entwickeln und Vorschauen
34 +
- **`quilldrop generate`** generiert eine komplett statische Website für das Deployment
35 +
36 +
Das war es. Keine Plugins, keine Themes zum Installieren, kein Package-Manager. Die Templates sind direkt im Binary eingebettet (`go:embed`), das CSS ist Vanilla CSS, das JavaScript ist Vanilla JavaScript. Die einzigen Go-Dependencies sind Goldmark für Markdown-Rendering und ein YAML-Parser für die Konfiguration.
37 +
38 +
## Die komplette Migration
39 +
40 +
Der Blog ist jetzt vollständig migriert. Alle Posts, alle statischen Seiten, alle Bilder, alle Funktionen die vorher schon da waren - alles läuft jetzt auf QuillDrop. Und das sind nicht wenige:
41 +
42 +
### Posts und Inhalte
43 +
44 +
Alle bestehenden Markdown-Posts wurden 1:1 übernommen. Das YAML-Frontmatter ist kompatibel mit Hugo, sodass die Migration im Grunde nur ein Kopieren der Dateien war. Cover-Bilder, Tags, Kategorien, Entwürfe - alles wird unterstützt.
45 +
46 +
### Statische Seiten
47 +
48 +
Seiten wie "Über mich" und die Projektseiten (VM-Tracker, QuillDrop selbst) liegen als Markdown im `sites/`-Verzeichnis. Verschachtelte Verzeichnisse werden automatisch erkannt.
49 +
50 +
### Navigation mit Dropdown-Menüs
51 +
52 +
Die komplette Navigation wird über die `config.yaml` konfiguriert. Dropdown-Menüs für Unterseiten wie bei "Projekte" sind kein Problem - einfach `children` definieren und fertig.
53 +
54 +
### Tags und Kategorien
55 +
56 +
Tags hatte das vorherige System auch, Kategorien sind neu dazugekommen. Beides wird aus dem Frontmatter gelesen und bekommt eigene Übersichtsseiten unter `/tags/` und `/categories/`.
57 +
58 +
### Dark/Light Theme
59 +
60 +
Der Dark Mode ist Standard, aber es gibt einen Toggle für ein helles Theme. Die Auswahl wird im Browser gespeichert und bleibt nach einem Reload erhalten.
61 +
62 +
### Pagination
63 +
64 +
Bei mittlerweile über 80 Posts ist eine Pagination essentiell. QuillDrop zeigt eine intelligente Seitennummerierung mit Ellipsis: `1 ... 10 11 12 13 14 ... 23`. Die erste Seite wird SEO-freundlich von `/page/1/` auf `/` umgeleitet.
65 +
66 +
### RSS Feed
67 +
68 +
Der RSS Feed liegt unter `/index.xml` - die gleiche URL wie vorher, damit bestehende Abonnenten nichts umstellen müssen.
69 +
70 +
### Volltextsuche
71 +
72 +
Das war eines der Features, die ich vom vorherigen Blog unbedingt mitnehmen wollte. Die Suche funktioniert komplett client-seitig: Beim Generieren wird eine `search-index.json` erstellt, die dann im Browser durchsucht wird. Lazy Loading sorgt dafür, dass der Index erst geladen wird, wenn die Suche geöffnet wird. Unterstützt werden mehrere Suchbegriffe (UND-Verknüpfung), und mit `Ctrl+K` kann man die Suche direkt öffnen.
73 +
74 +
### Inhaltsverzeichnis
75 +
76 +
Posts mit `toc: true` im Frontmatter bekommen ein automatisch generiertes Inhaltsverzeichnis. Das TOC erkennt H1-, H2- und H3-Überschriften und rückt relativ ein.
77 +
78 +
### Artikel-Navigation
79 +
80 +
Am Ende jedes Posts gibt es jetzt Links zum neueren und älteren Artikel. Beim neuesten Post wird nur "Älterer Artikel" angezeigt, beim ältesten nur "Neuerer Artikel".
81 +
82 +
## Technik unter der Haube
83 +
84 +
Der Stack ist bewusst minimal gehalten:
85 +
86 +
- **Go** mit der Standard Library für HTTP-Server und Templates
87 +
- **Goldmark** für Markdown-Rendering mit GFM, Emoji-Support und Syntax-Highlighting über Chroma
88 +
- **Vanilla CSS** mit Custom Properties für das Theming
89 +
- **Vanilla JavaScript** für Theme-Toggle, Suche und TOC-Generierung
90 +
- **Keine Datenbank** - das Dateisystem ist die einzige Datenquelle
91 +
92 +
Die gesamte Konfiguration läuft über eine einzige `config.yaml`. Keine versteckten Konfigurationsdateien, keine Environment-Variables die man vergessen könnte.
93 +
94 +
## Dual-Mode: Entwicklung und Produktion
95 +
96 +
Für die lokale Entwicklung starte ich `quilldrop serve` und sehe sofort jede Änderung im Browser. Für das Deployment auf den Webserver läuft `quilldrop generate` und spuckt statische HTML-Dateien aus, die direkt auf Nginx, Apache oder einem CDN liegen können.
97 +
98 +
Das ist das Schöne an einem Static Site Generator: Die fertige Seite ist nur noch HTML, CSS und ein bisschen JavaScript. Kein PHP, kein Ruby, kein Node.js auf dem Server. Einfach Dateien ausliefern.
99 +
100 +
## Fazit
101 +
102 +
Nach Jahren mit verschiedenen CMS-Systemen läuft der Blog jetzt auf meiner eigenen Software. Alles ist migriert, alle Funktionen sind da und das System macht genau das, was es soll - nicht mehr und nicht weniger.
103 +
104 +
Der Code ist Open Source und auf [GitHub](https://github.com/ruedigerp/quilldrop) [^verfügbar]. Wer einen schnellen, minimalistischen Blog ohne Overhead sucht, kann sich QuillDrop gerne anschauen.
105 +
106 +
> Write. Save. Published.
107 +
108 +
[^verfügbar]: Noch nicht, das kommt die Tage noch. Es gibt ein Update, so bald das Repo öffentlich ist.
\ No newline at end of file
geändert internal/content/parser.go
+1 −0 Datei ansehen
@@ -31,4 +31,5 @@ func init() {
31 31
		goldmark.WithExtensions(
32 32
			extension.GFM,
33 +
			extension.Footnote,
33 34
			highlighting.NewHighlighting(
34 35
				highlighting.WithStyle("dracula"),
geändert static/css/style.css
+38 −0 Datei ansehen
@@ -1344,4 +1344,42 @@ body {
1344 1344
}
1345 1345
1346 +
/* Footnotes */
1347 +
.post-content .footnotes {
1348 +
    margin-top: 3rem;
1349 +
    padding-top: 1.5rem;
1350 +
    border-top: 1px solid var(--border);
1351 +
    font-size: 0.88rem;
1352 +
    color: var(--text-secondary);
1353 +
}
1354 +
1355 +
.post-content .footnotes hr {
1356 +
    display: none;
1357 +
}
1358 +
1359 +
.post-content .footnotes ol {
1360 +
    padding-left: 1.5rem;
1361 +
}
1362 +
1363 +
.post-content .footnotes li {
1364 +
    margin-bottom: 0.5rem;
1365 +
}
1366 +
1367 +
.post-content sup a.footnote-ref {
1368 +
    color: var(--accent);
1369 +
    text-decoration: none;
1370 +
    font-weight: 600;
1371 +
    padding: 0 2px;
1372 +
}
1373 +
1374 +
.post-content sup a.footnote-ref:hover {
1375 +
    text-decoration: underline;
1376 +
}
1377 +
1378 +
.post-content .footnote-backref {
1379 +
    color: var(--accent);
1380 +
    text-decoration: none;
1381 +
    margin-left: 0.3rem;
1382 +
}
1383 +
1346 1384
/* Video elements */
1347 1385
.post-content video {