Un CMS pour Astro sans renoncer au statique

Comment laisser un client publier sur un site Astro sans passer en rendu serveur, sans lui donner accès au dépôt, et sans qu'une panne du CMS ne mette le site à terre.

Un site Astro qui sort en statique se déploie sur un CDN, coûte presque rien à héberger et tient une pointe de trafic sans rien faire. Le jour où le client veut changer un tarif, tout se complique : soit on lui ouvre le dépôt Git, soit on passe le site en rendu serveur, soit on reçoit le mail « tu peux me changer ça ? » pour la troisième fois du mois.

Aucune de ces trois options n’est bonne. Voici comment on a tranché.

Pourquoi les solutions habituelles coincent

Git-based (Decap, Tina). L’éditeur commite dans le dépôt. Techniquement élégant, mais le client hérite d’un compte GitHub, d’un flux de travail qu’il ne comprend pas, et la moindre erreur de fusion devient un problème pour lui. Surtout, le contenu et le code partagent le même historique : une restauration de contenu touche au dépôt.

Headless classique (Strapi, Sanity, Prismic). Bon produit, mais le plancher payant se situe entre 10 et 18 $ par mois et par projet. Sur un parc de vingt sites vitrine facturés 40 € de maintenance mensuelle, la marge disparaît. Les locales supplémentaires sont presque toujours un palier tarifaire de plus.

Rendu serveur. On perd exactement ce pour quoi on avait choisi Astro : le site devient une application à surveiller, avec un temps de réponse, un cache à penser et une facture d’hébergement qui suit le trafic.

Ce qu’on fait à la place

Le principe tient en une phrase : le CMS ne sert jamais le site, il sert le build.

  1. L’éditeur travaille dans une interface web, sur son téléphone s’il le veut.
  2. Quand il clique sur Publier, l’API fige l’état du contenu dans un fichier JSON immuable : un instantané, poussé sur un CDN.
  3. L’API déclenche ensuite le build chez l’hébergeur du site (Vercel, Netlify, Cloudflare, GitHub Actions ou un serveur à soi).
  4. Le build lit l’instantané et sort le site statique, comme d’habitude.

Le contenu voyage donc du CMS vers le site au moment du build, et jamais à la requête d’un visiteur. Le site final n’a aucune dépendance vers le CMS : il ne l’appelle pas, ne le connaît pas, et n’a pas besoin qu’il soit allumé.

La conséquence qui compte

Si le CMS tombe, les sites publiés restent en ligne. Ils sont statiques, ils sont sur un CDN, il n’y a rien à tomber.

Mieux : l’instantané étant sur un CDN et non dans l’API, un build lancé pendant une panne du CMS aboutit quand même. C’est un test qu’on fait volontairement, en éteignant l’API pour vérifier qu’un déploiement passe encore.

C’est la différence entre un CMS dont le site dépend, et un CMS dont le site se sert.

Côté développeur : le schéma reste dans le code

Le modèle de contenu est déclaré dans un fichier TypeScript du projet, pas cliqué dans une interface :

import { defineCms, fields } from '@menestrel/fields';

export default defineCms({
  collections: {
    services: {
      label: { fr: 'Prestations', en: 'Services' },
      fields: {
        title: fields.text({ required: true, localized: true }),
        excerpt: fields.textarea({ localized: true }),
        price: fields.number(),
        cover: fields.image(),
      },
    },
  },
});

Une commande synchronise ce schéma vers le serveur, qui génère les formulaires d’édition correspondants. Le schéma vit donc dans la revue de code, dans les branches et dans l’historique Git, comme le reste du projet. Ce qui n’y vit pas, c’est le contenu : le texte du client n’a rien à faire dans un dépôt.

Côté rendu, le contenu arrive par un loader Content Layer standard, donc getCollection() et le typage fonctionnent comme avec n’importe quelle source Astro.

Ce que ça ne fait pas

Par honnêteté, les limites du modèle :

Ces trois limites sont des choix, pas des retards de développement. Elles sont ce qui permet de garder le site statique, l’hébergement à quelques centimes et le modèle mental simple.

Pour essayer

La documentation détaille l’installation, et le starter met un projet debout en une commande :

npm create menestrel@latest

Le plan gratuit couvre un site complet, deux langues comprises, sans carte bancaire.

Retour au blog