Einen MDX-Blog mit Next.js selbst bauen - So ist dieser Blog entstanden
Wie dieser Blog ohne CMS, nur mit Markdown-Dateien und dem Next.js 16 App Router entstanden ist. Ordnerstruktur, MDX-Setup und SEO, erklärt am echten Code.
Das Wichtigste
Beiträge liegen als .mdx-Dateien unter src/content/posts und werden mit @next/mdx gerendert, ganz ohne CMS. Der Dateiname ist die URL, und statt Frontmatter liefert ein meta-Export Liste und Metadaten.
Auf dieser Seite
Dieser Blog, den Sie gerade lesen, hat weder eine Datenbank noch ein Admin-Panel. Jeder Beitrag ist eine einzige Markdown-Datei. Hier zeige ich genau, wie er entstanden ist.
Warum dateibasiert
Blog-Tools gibt es viele, aber für Solo-Entwickler sind die meisten überdimensioniert.
- CMS: bringt Admin-Oberfläche, Integrationen und Kosten mit sich
- Notion-Anbindung: bequem, aber bei Rendering, Geschwindigkeit und Anpassungen stößt man an Grenzen
- Markdown-Dateien: Beiträge werden mit dem Code versioniert, kosten nichts, und
git pushist die Veröffentlichung
Ich habe mich für die dritte Option entschieden. Sie hat die geringste Reibung.
Ordnerstruktur
Aufs Wesentliche reduziert sieht sie so aus:
src/
├── app/
│ ├── page.tsx # 홈 - 글 목록
│ ├── posts/[slug]/page.tsx # 글 상세
│ └── sitemap.ts # /sitemap.xml
├── content/posts/*.mdx # ← 글은 여기에만 추가
└── lib/posts.ts # 목록·메타 로딩
Der Dateiname wird zur URL. hello.mdx ist unter /posts/hello erreichbar.
MDX-Setup
Um MDX im App Router zu nutzen, kommt @next/mdx dazu. In Next 16 ist Turbopack der Standard, deshalb müssen remark/rehype-Plugins als String-Namen übergeben werden (Funktionsreferenzen funktionieren nicht).
const withMDX = createMDX({
options: {
remarkPlugins: ["remark-gfm"], // 표·취소선 지원
rehypePlugins: ["rehype-slug"], // 제목 앵커
},
})
meta-Export statt Frontmatter
Üblicherweise schreibt man Titel und Datum in den ----Block (Frontmatter) am Anfang der Markdown-Datei. Da MDX aber JavaScript enthalten kann, gibt es einen saubereren Weg: einfach ein Objekt exportieren.
export const meta = {
title: "글 제목",
date: "2026-07-06",
category: "dev",
}
## 본문 시작
Die Listenseite importiert jede Datei dynamisch und liest nur dieses meta aus.
const mod = await import(`@/content/posts/${slug}.mdx`)
return mod.meta // 본문 렌더 없이 메타만
Keine Parsing-Bibliothek, kein separater Index nötig.
SEO von Anfang an
Wenn der Blog Besucher bringen soll, gehört SEO nicht ans Ende, sondern an den Anfang. Ich habe von Beginn an Metadaten pro Beitrag, Open Graph, strukturierte JSON-LD-Daten sowie sitemap.xml, robots.txt und rss.xml eingebaut. Dieses Thema behandle ich ausführlicher im nächsten Beitrag.
Fazit
- Ein Beitrag ist eine
.mdx-Datei, der Dateiname ist die URL @next/mdx+ Plugins als Strings (Turbopack)export const metastatt Frontmatter- SEO ab dem ersten Commit
Mit genau dieser Struktur steht auch Ihr Blog in 30 Minuten. Ich sammle auf diese Weise weiter Geschichten zu meinen Produkten wie Welle des Tages.
Häufige Fragen
Warum MDX-Dateien statt eines CMS?
Die Beiträge werden zusammen mit dem Code versioniert, es kostet nichts, und Deployen heißt Veröffentlichen. Für Solo-Entwickler ist das der Weg mit der geringsten Reibung. Bilder, Code und Komponenten lassen sich außerdem frei einbetten.
Wie entsteht die Beitragsliste ohne Frontmatter?
Jede .mdx-Datei exportiert oben ein meta-Objekt. Die Listenseite importiert die Dateien dynamisch und liest nur dieses meta aus. Eine separate Parsing-Bibliothek ist nicht nötig.
Ähnliche Beiträge
- 💻 Entwicklung
Wenn ein Bucket zwei Besitzer hat, gewinnt der, der zuletzt apply ausführt
Ich fügte eine einzige S3-lifecycle-Regel hinzu, und terraform apply schlug fehl. Die Ursache: eine Struktur, in der zwei Ressourcen jeweils die lifecycle-Konfiguration desselben Buckets besitzen. S3 lifecycle arbeitet nicht auf Regel-, sondern auf Dokumentebene - der letzte Gewinner löscht die Regeln der Gegenseite stillschweigend.
- 💻 Entwicklung
Die Tests waren grün, aber es wurde nie eine Benachrichtigung verschickt
Der Code, der Jobs in die Queue legte, schlug vom ersten Tag an jedes Mal fehl. Der Fehler wurde verschluckt, und die Tests blieben dank Mocks grün. Die Geschichte, wie ein einziger Doppelpunkt drei Features lautlos getötet hat.
- 💻 Entwicklung
Der Alarm sagte, die DB sei tot. Die DB war nie tot
Innerhalb eines Tages stapelten sich vier CRITICAL-Alarme. 504, Prisma P2028, ein 500er im Admin und 'DATABASE-Dienst ausgefallen'. Ich öffnete zuerst die RDS-Metriken: 21 Stunden lang lag die CPU bei maximal 19.7%, alles gesund. Langsam war nicht die DB, sondern ein Roundtrip, der fünfundzwanzigmal über den Atlantik ging. Und den Ausfall-Alarm hatte der Health Check selbst erzeugt.