Format des fichiers Markdown du blog
Chaque article du blog est un fichier content/blog/<slug>.md, lu par lib/blog.ts côté public
(rendu statique de /blog et /blog/[slug]) et par lib/github-content.ts côté admin (lecture
live via l'API GitHub pour /admin). Voir docs/adr/0011-blog-admin-architecture.md pour la
décision d'architecture derrière ce choix (Markdown versionné dans le repo, pas de base de
données).
Nom de fichier = slug
Le nom du fichier (sans l'extension .md) est l'identifiant de l'article, utilisé dans son URL
publique (/blog/<slug>) et dans l'admin (/admin/posts/<slug>). Renommer le fichier change
donc son URL.
Frontmatter
Chaque fichier commence par un bloc YAML (délimité par ---), parsé par gray-matter :
---
title: "Titre de l'article"
date: "2026-09-11"
updatedAt: "2026-09-11"
tags: ["ia", "autoformation"]
excerpt: "Résumé affiché dans la liste des articles et dans les métadonnées OG."
status: "draft"
pinnedOrder: 1
---
Le corps de l'article, en Markdown, après le second `---`.
| Champ | Type | Obligatoire | Détail |
| ------------- | ------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| title | string | oui | Titre affiché partout (liste, article, metadata <title>). |
| date | string (YYYY-MM-DD) | oui | Date de publication. Sert aussi de tri (getAllPosts() trie par date décroissante). |
| updatedAt | string (YYYY-MM-DD) | non | Mise à jour par l'admin à chaque enregistrement ; retombe sur date si absent. |
| tags | string[] | non | Vide par défaut. Affichés en badges sur /blog et /blog/[slug] (voir issue #23). |
| excerpt | string | non | Vide par défaut. Utilisé comme description sur la liste et dans les métadonnées Open Graph. |
| status | "draft" \| "published" | non | draft par défaut si absent. Seuls les articles published sont rendus publiquement et inclus dans app/sitemap.ts — un brouillon reste en 404 sur son URL publique même si le fichier existe déjà. |
| pinnedOrder | number | non | Absent par défaut (non épinglé). Les articles portant ce champ s'affichent toujours en premier sur /blog, triés par pinnedOrder croissant ; le reste suit par date décroissante comme d'habitude. Un badge « Épinglé » s'affiche alors sur /blog et /blog/[slug]. |
Le corps du fichier (tout ce qui suit le frontmatter) est du Markdown brut, rendu via
react-markdown — le même pipeline que docs/ (ce fichier inclus), sans
dangerouslySetInnerHTML.
Différence avec docs/
docs/*.md (ces pages-ci) n'a pas de frontmatter : son titre est extrait du premier # titre du
fichier par une regex (lib/docs.ts). Ça suffit pour de la documentation libre, mais un article
de blog a besoin de champs structurés (status, tags, date) qu'une regex ne peut pas fournir
de façon fiable — d'où le frontmatter YAML dédié pour content/blog/.