Markdown-Blog (mehrsprachig, getaggt)
Ein Blog, dessen Inhalt in Dateien und nicht in Datenbankzeilen liegt. Das gesamte Design dreht sich um eine Idee:
Die Identität eines Beitrags ist sein translationKey, nicht sein Slug. Slugs werden für SEO lokalisiert und unterscheiden sich pro Sprache; der Schlüssel macht aus drei Dateien einen Artikel und ermöglicht hreflang, den Sprachumschalter, verwandte Beiträge und sprachübergreifende Weiterleitungen. Wenn Sie dieses eine Feld korrekt setzen, ist alles andere nur noch eine Frage der Nachschlageoperationen.
Geschrieben von dem Ingenieur, der dieses Modul bereits ausgeliefert hat. Die frühere Implementierung, gegen die es geprüft wurde, war ein mehrsprachiger, statisch generierter Marketing-Blog mit Sitemap und SEO-Oberfläche. Die folgenden Fakten und Regeln sind das, was die Content-Layer-Suite und der Build verifizieren; references/provenance.md enthält die Aufzeichnung.
Wann verwenden
- Inhalt wird als Markdown im Repository erstellt und mit der App bereitgestellt
- Mehr als eine Sprache, mit unterschiedlichen Slugs pro Sprache
- Tag-Seiten, verwandte Beiträge, Feeds oder hreflang sind im Umfang enthalten
- Sie möchten alles zur Build-Zeit statisch generieren
Wann NICHT verwenden
| Stattdessen | Verwenden Sie |
|---|---|
| Ein gehostetes CMS ist die Wahrheitsquelle | das SDK des CMS; behalten Sie nur references/content-model.md |
| Eine Dokumentationsseite / Hilfe-Center (Kategorien, Seitenleiste, Suche) | eine Hilfe-Center-Fähigkeit – anderes Navigationsmodell |
| Ein einsprachiger Blog mit <10 Beiträgen | fs + gray-matter inline; dies ist Overhead |
| Rendern von benutzereingereichtem Markdown | einen bereinigenden Renderer; siehe den XSS-Hinweis in references/rendering.md |
Architektur
content/blog/<locale>/<localized-slug>.md # Frontmatter + Body
|
v
parseFrontmatter --YAML, fällt bei fehlerhaften Anführungszeichen auf einen Zeilenparser zurück
|
v
getAllPosts(locale) <-- in der Produktion pro Sprache memoisiert
|
+--> translationKey ----> Alternativen / hreflang / Sprachumschalter
+--> tags -> Tag-SLUG ---> Tag-Seiten (der Slug ist die Identität, nicht das Label)
+--> related (EN-Slugs) -> verwandte Beiträge, mit EN-Fallback
|
v
/blog /blog/[slug] /blog/tag/[slug] sitemap.xml feed.xml
Kritische Fakten
- Beziehungen werden einmal in der Standardsprache als Slugs der Standardsprache angegeben. Eine übersetzte Datei kann
relatedganz weglassen und erhält trotzdem die richtigen verwandten Beiträge, aufgelöst übertranslationKey, sodass jede Übersetzung denselben Bereich mit verwandten Beiträgen rendert. Der Fallback-Test sichert dies. - Die Identität eines Tags ist sein Slug, nicht sein Label.
"KI-Agenten"und"KI agenten"führen zum selben Slug und damit zur selben URL. Gruppieren Sie nach Slug und gleichen Sie Beiträge nach Slug ab, sodass alle Rechtschreibvarianten auf einer einzigen Tag-Seite erscheinen. Zwei Tests sichern dies. gray-matterwirft einen Fehler bei einem doppelt-an-geführten YAML-Skalar, der rohe Anführungszeichen enthält – typografische Anführungszeichen in einem übersetzten Auszug tun dies. Ohne einen Fallback-Parser stoppt der Build an dieser einen Datei; der zeilenweise Fallback hält den Build am Laufen. Gegen die Bibliothek verifiziert und durch einen Test abgedeckt.gray-mattercached ein fehlgeschlagenes Parsen als leeres Ergebnis. Es schreibt seinen Cache-Eintrag vor dem Parsen, sodass der zweite und jeder weitere Parse einer Datei, die einen Fehler warf,{}zurückgibt – kein Fehler, kein Frontmatter. Rufen Sie immermatter(contents, {})auf; jedes Optionsobjekt deaktiviert den Cache.- Das Laden ist O(Beiträge x Sprachen) pro Seite, wenn Sie nicht memoieren. Memoisieren Sie pro Sprache, sodass jede Inhaltsdatei einmal pro Build gelesen wird; das Auflösen von Alternativen in
generateMetadataführt sonst zu einer 71-fachen Dateilese-Verstärkung, gemessen an der früheren Implementierung. getPostSlugsmuss.mdfiltern. Der Filter verhindert, dass eine versehentliche.DS_Storezu einem.DS_Store.md-Leseversuch und einemENOENTwird. Ein Test sichert dies.- Tag-Seiten haben keine sprachübergreifende Identität. Tags sind sprachspezifischer Freitext, daher muss der Sprachumschalter auf einer Tag-URL auf das Blog-Index zurückfallen, anstatt eine URL zu bauen, die 404 ergibt.
Harte Regeln
Verwenden Sie niemals den Slug für sprachübergreifende Nachschlageoperationen. Slugs werden absichtlich lokalisiert.
translationKeyist die Verbindungsspalte; eine Slug-Übereinstimmung über Sprachen hinweg ist ein Zufall, keine Beziehung.
Lassen Sie
getPostBySlugniemals implizit über die Sichtbarkeit von Entwürfen entscheiden. Filtern Sie Entwürfe im Loader;dynamicParams = falseverbirgt sie nur als Nebeneffekt und hört damit auf, sobald das Flag sich ändert. Der Entwurfs-Test sichert den Filter.
Rendern Sie niemals eine Konstante wie "5 Min. Lesezeit". Berechnen Sie sie aus dem Body; es kostet ein
split, und der Lesezeit-Test sichert dies.
Leiten Sie niemals die URL einer Tag-Seite aus dem Label zur Lesezeit und das Label aus der URL zur Renderzeit ab, ohne auf Kollisionen zu prüfen. Dieser Rundgang ist nur verlustfrei, solange jedes Label eindeutig slugt.
Schnellstart
- Legen Sie den Locale-Vertrag und das Frontmatter-Schema fest – content-model.md
- Bauen Sie den Loader (memoisiert,
.md-gefiltert, Fallback-Parser) – content-loader.md - Verdrahten Sie Locales, Alternativen und sprachübergreifende Weiterleitungen – i18n-and-routing.md
- Fügen Sie Tag-Gruppierung und Tag-Seiten hinzu – tags.md
- Bauen Sie die drei Routen, Metadaten und JSON-LD – pages-and-seo.md
- Rendern Sie Body und Titelbilder – rendering.md
- Fügen Sie das Validierungsskript hinzu und lesen Sie die Betriebslücken – operations.md
- Führen Sie die Testdaten und Tests aus – testing.md
Portieren Sie dies in eine bestehende App? Füllen Sie zuerst die Nahtstellentabelle in adaptation.md aus – das dauert zehn Minuten und spart eine Umbenennung.
Referenzverzeichnis
| Szenario | Auslösende Schlüsselwörter | Referenz |
|---|---|---|
| Frontmatter-Felder, Entwürfe, Übersetzungsschlüssel, Beziehungen | frontmatter, schema, translationKey, draft, related, excerpt | content-model.md |
| Dateien lesen, Caching, YAML-Fehler, Lesezeit | gray-matter, fs, cache, memoize, ENOENT, YAMLException, parse | content-loader.md |
| Locales, hreflang, Alternativen, Links in falscher Sprache | i18n, locale, hreflang, alternates, canonical, language switcher, redirect | i18n-and-routing.md |
| Tag-URLs, Kollisionen, dünne Tag-Seiten | tag, slug, diacritics, collision, tag page, taxonomy | tags.md |
| Die Routen, Metadaten, JSON-LD, Sitemap, RSS | page.tsx, generateStaticParams, generateMetadata, dynamicParams, sitemap, RSS, feed, BlogPosting | pages-and-seo.md |
| Markdown zu React, Codeblöcke, Titelbilder | react-markdown, remark-gfm, syntax highlighting, cover, motif, accent, XSS | rendering.md |
| Inhalte validieren, Build-Kosten, Editor-Workflow | validation, CI check, build time, drafts, authoring, operator | operations.md |
| Testdaten und die erfolgreiche Testsuite | test, fixture, vitest, node:test, regression, assert | testing.md |
| Einpassen in eine Host-App: Nahtstellen, Umbenennungen, Prüfung | adapt, port, host, seam, rename, CMS, integrate | adaptation.md |
| Das Audit-Ledger: Was geändert, beibehalten und hinzugefügt wurde im Vergleich zur früheren Implementierung | provenance, deviations, fidelity, audit, ledger | provenance.md |