Aller au contenu

Decap CMS : architecture et mise en production

Decap CMS ajoute au site une interface d’édition, accessible à l’adresse /_cms/. Le _ initial, comme pour les fichiers /_astro/ du site, évite tout conflit avec une section de la documentation : un dossier admin/ ou cms/ dans src/content/docs/ produirait des pages à la même adresse que l’interface. Les rédacteurs y modifient les pages à l’aide de formulaires et d’un éditeur visuel ; chaque enregistrement devient un commit sur le dépôt Git.

Rédacteur ──► /_cms/ (Decap CMS) ──► commit sur Bitbucket ──► Jenkins : npm run build ──► déploiement

Cette page décrit le fonctionnement technique de Decap et sa mise en production. Pour l’utilisation au quotidien, voir Éditer avec Decap CMS.

« Headless » ne veut pas dire « sans back-end »

Section intitulée « « Headless » ne veut pas dire « sans back-end » »

Un CMS headless (« sans tête ») gère le contenu mais ne produit pas le site : l’affichage est confié à un autre outil. Ici, Decap modifie des fichiers .md, et c’est Astro qui en fait des pages. C’est l’inverse d’un CMS traditionnel comme WordPress, qui stocke le contenu et génère les pages.

Headless ne signifie pas pour autant « sans serveur » : beaucoup de CMS headless ont un back-end complet. Ce qui distingue Decap, c’est d’être basé sur Git :

CMS traditionnel (WordPress) CMS headless « API » (Strapi, Directus…) CMS headless « Git » (Decap)
Produit les pages du site Oui Non Non
Où est stocké le contenu Base de données Base de données Fichiers dans le dépôt Git
Serveur d’application Oui (PHP) Oui (Node.js…) Non
Historique des modifications Limité Variable Celui de Git (commits)

Decap est une application JavaScript chargée par la page /_cms/, un fichier statique comme les autres. Une fois chargée, elle s’exécute entièrement dans le navigateur du rédacteur. Son « back-end », c’est l’API de Bitbucket, qu’elle appelle directement :

  • pour afficher la liste des pages, elle lit le contenu des dossiers du dépôt ;
  • pour ouvrir une page, elle télécharge le fichier .md ;
  • pour enregistrer, elle crée un commit contenant le fichier modifié (et l’image téléversée, le cas échéant).

Il n’y a donc ni serveur d’application, ni base de données à installer, sauvegarder ou mettre à jour. Toutes les données sont dans le dépôt Git.

Navigateur du rédacteur
└─ Decap CMS (/_cms/)
│
├── lecture des fichiers, commits ────────────────────► API Bitbucket (api.bitbucket.org)
│
└── connexion seulement ──► serveur OAuth (/auth) ────► Bitbucket

Pour appeler l’API de Bitbucket, Decap a besoin d’un jeton d’accès au nom du rédacteur. Bitbucket ne le délivre qu’en échange d’un secret propre à l’application, et un secret ne peut pas figurer dans une page web, où n’importe qui pourrait le lire. Un petit serveur intermédiaire, le serveur OAuth, détient ce secret et n’intervient qu’à deux moments :

  1. À la connexion : il échange le code d’autorisation fourni par Bitbucket contre un jeton, et le transmet au navigateur.
  2. À l’expiration du jeton : il le renouvelle.

Le contenu des pages ne passe jamais par lui. Il ne stocke rien, n’a pas de base de données, et peut être redémarré à tout moment : c’est un simple relais d’authentification. Sa mise en place est décrite plus bas, dans Mise en production.

En développement, npm run cms lance decap-server, qui joue le rôle de Bitbucket : au lieu d’appeler l’API distante, Decap lit et écrit directement les fichiers du disque. Ce serveur ne sert qu’en local ; il n’est jamais déployé.

En local En production
Où Decap lit et écrit Fichiers du disque, via decap-server Dépôt Bitbucket, via son API
Connexion Aucune (bouton « Se connecter ») Compte Bitbucket, via le serveur OAuth
Résultat d’un enregistrement Fichier modifié, sans commit Commit sur le dépôt
Fichier Rôle
public/_cms/index.html Charge l’application Decap CMS, et déclare le composant « Encart » (voir plus bas)
public/_cms/config.yml Configuration : dépôt Bitbucket, collections, champs des formulaires, dossier des images
deploy/oauth/server.mjs Petit serveur de connexion OAuth entre Decap et Bitbucket (production uniquement)
deploy/nginx-auth.conf, deploy/docker-compose.yml Mise en service du site et du serveur OAuth sur un même domaine

En local, Decap peut travailler directement sur les fichiers du disque, sans Bitbucket ni connexion. Il faut deux terminaux :

Fenêtre de terminal
npm run dev # le site, sur http://localhost:4321
npm run cms # le serveur local de Decap (port 8082), qui lit et écrit dans src/content/docs/

Puis ouvrir http://localhost:4321/_cms/index.html et cliquer sur « Se connecter ».

Les modifications sont écrites immédiatement dans src/content/docs/ et visibles sur le site de développement. En local, aucun commit n’est créé : c’est à vous de les faire.

Le CMS présente une collection par section du site et par langue : Guides et Guides (EN), Comprendre Astro & Starlight et sa version (EN), etc. Les collections anglaises pointent vers src/content/docs/en/<section>/. La collection CMS a en plus les champs de la fiche (site officiel, GitHub, licence, modèle, Bitbucket). Les nouvelles pages créées depuis le CMS apparaissent automatiquement dans la barre latérale, puisque celle-ci est autogénérée.

Seuls les fichiers .md sont gérés. Les pages .mdx (accueil, pages utilisant des composants) n’apparaissent pas dans le CMS et restent modifiées dans le code : l’éditeur de Decap ne sait pas manipuler les imports et composants MDX.

Le formulaire de chaque page reprend les champs du frontmatter :

Champ du formulaire Champ du frontmatter Remarque
Nom du fichier fichier Obligatoire ; sert à nommer le fichier à la création
Titre title Obligatoire
Description description Facultatif ; omis du fichier s’il est vide
Barre latérale → Ordre sidebar.order Position dans le menu
Barre latérale → Masquer du menu sidebar.hidden
Brouillon draft Page exclue du site publié
Contenu (corps de la page) Éditeur visuel ou Markdown brut

Le nom du fichier (et donc l’URL) vient du champ Nom du fichier (slug: "{{fields.fichier}}" dans config.yml), et non du titre : sinon une page créée dans une collection française aurait un nom français, que sa traduction devrait reprendre. Le champ est contrôlé par une expression régulière (minuscules, chiffres et tirets). Le champ fichier est aussi enregistré dans le frontmatter : il est déclaré dans src/content.config.ts, sans effet sur le site.

Une image insérée depuis l’éditeur est enregistrée dans src/assets/images/ et référencée par un chemin relatif (../../../assets/images/photo.webp). Elle est donc optimisée par Astro comme toute autre image.

L’éditeur « Texte enrichi » de Decap convertit la page en interne puis la réécrit à l’enregistrement. Les essais réalisés sur ce site (y compris sur cette page) ont montré :

Élément Comportement
Titres, listes, liens, gras, blocs de code Conservés à l’identique
Encarts Starlight (:::note…) Affichés comme des blocs « Encart » avec un petit formulaire (type, titre, contenu), et conservés
HTML (<div class="…">, <details>) Affiché en texte brut dans l’éditeur, mais conservé
Tableaux Conservés, colonnes réalignées
Listes à puces Conservées, - remplacé par *
Description longue Coupée sur deux lignes dans le frontmatter (YAML équivalent)
Ligne vide après le frontmatter Supprimée (sans effet sur le rendu)
Syntaxe Markdown tapée au clavier (**gras**) Échappée (\*\*gras\*\*) : dans l’éditeur visuel, on utilise les boutons de la barre d’outils

Le panneau d’aperçu, à droite de l’éditeur, n’utilise pas le style du site : c’est un aperçu du contenu, pas de la mise en page finale.

Comme expliqué dans L’exception : la connexion, la connexion à Bitbucket passe par un petit serveur qui détient le secret de l’application. C’est le rôle de deploy/oauth/server.mjs (une centaine de lignes, sans dépendance). Il est servi sur le même domaine que le site, sous /auth, via Nginx :

Adresse Rôle
GET /auth Redirige le rédacteur vers la page d’autorisation de Bitbucket
GET /auth/callback Reçoit le code de Bitbucket, l’échange contre un jeton, et le transmet à Decap
POST /auth/refresh Renouvelle le jeton lorsqu’il expire (Bitbucket fait tourner les refresh tokens)

Le rédacteur ne voit qu’une fenêtre « Se connecter avec Bitbucket », puis il est connecté.

Dans Bitbucket : Workspace settings → OAuth consumers → Add consumer.

  • Callback URL : https://doc.exemple.com/auth/callback (adresse publique du site + /auth/callback)
  • Permissions : Account → Read, Repositories → Write, et Pull requests → Write si le workflow éditorial est activé

Bitbucket fournit alors une Key et un Secret.

Fenêtre de terminal
cp deploy/.env.exemple deploy/.env

Puis renseigner BITBUCKET_KEY, BITBUCKET_SECRET et SITE_ORIGIN (ex. https://doc.exemple.com). Le fichier deploy/.env contient un secret : il est exclu de Git.

backend:
name: bitbucket
repo: mon-espace/mon-depot # espace de travail/dépôt
branch: main
base_url: https://doc.exemple.com
auth_endpoint: auth
base_url: https://doc.exemple.com # oui, une seconde fois, à la racine

Sur AWS (solution retenue) : une petite instance créée par OpenTofu (deploy/aws/) héberge Caddy, qui sert le site en HTTPS et relaie /auth vers le serveur OAuth. Jenkins compile et dépose le site à chaque push sur main (Jenkinsfile, task deploy). Les secrets Bitbucket sont des variables OpenTofu, et non un fichier .env. La procédure complète est dans deploy/aws/README.md.

Avec Docker, sur un serveur existant :

Fenêtre de terminal
npm run build
docker compose -f deploy/docker-compose.yml up -d

Le service site (Nginx) sert dist/ et relaie /auth vers le service oauth. Le HTTPS est alors ajouté devant, par un reverse proxy ou un load balancer.

Chaque rédacteur doit avoir un compte Bitbucket avec un droit d’écriture sur le dépôt.

  • Workflow éditorial : avec publish_mode: editorial_workflow dans config.yml, chaque modification crée une branche et une pull request Bitbucket, au lieu d’un commit direct sur main. Le CMS affiche alors un tableau « Brouillon / En relecture / Prêt » ; la publication correspond à la fusion de la pull request.
  • Version de Decap : l’interface est chargée depuis le CDN unpkg (decap-cms@^3.16.3). Pour ne dépendre d’aucun service externe, on peut copier le fichier decap-cms.js dans public/_cms/.
  • Documentation officielle : decapcms.org/docs.