Un CMS headless qui épouse Next.js au lieu de le combattre
Le mode brouillon, l'ISR, l'App Router et la couche de rendu. Ce qui compte vraiment quand vous branchez un CMS headless à Next.js — et les trois façons dont ça tourne mal.
L'autocomplétion de Google sur ce sujet est étonnamment constante sur ce que les gens veulent. Les trois suggestions qui remontent le plus :
best headless cms for next js
free headless cms for nextjs
nextjs headless cms open source
Gratuit, open source, et spécifiquement pour Next.js. Cet article traite de ce qui distingue les candidats une fois que vous avez cette liste restreinte — car la différence qui compte n'est pas le SDK, c'est l'endroit où se trouve le contenu quand la requête arrive.
#Deux architectures, et l'unique question qui les sépare
Payload s'exécute au sein de l'application Next.js elle-même. Vous obtenez une API locale qui contourne entièrement HTTP, la configuration sous forme de code, et un seul déploiement. Une excellente expérience développeur pour un produit unique.
Le coût, c'est le couplage : le CMS et le site web partagent un déploiement, un runtime et une unité de mise à l'échelle. Un pic de trafic sur le site public est un pic sur l'outil qu'utilisent vos éditeurs, et changer de framework frontend n'est plus un projet frontend.
À côté de votre application. Strapi, Directus, Sanity, corpusctl. Le CMS est un service séparé. Vous récupérez le contenu par le réseau, ce qui signifie que vous êtes désormais responsable d'une décision de mise en cache.
Cette décision de mise en cache, c'est toute l'intégration :
Quand un visiteur demande une page, d'où vient le contenu — et quel âge a-t-il le droit d'avoir ?
Tout le reste de cet article découle de votre réponse.
Récupérer à chaque requête. Simple et correct, et votre CMS est désormais sur le chemin critique de chaque chargement de page. Sa latence est votre TTFB et son indisponibilité est la vôtre. Convenable pour un tableau de bord d'administration, inadapté pour un site marketing.
ISR avec une fenêtre de temps.revalidate: 60 et le contenu est périmé d'une minute au plus. Ça fonctionne, et cela signifie que les éditeurs regardent une horloge après publication, et que chaque expiration de fenêtre est un défaut de cache que quelqu'un paie.
Pré-compiler, servir depuis le cache. Le contenu est rendu au moment de la publication et servi comme un artefact statique. Le plus rapide et le moins cher — tant que quelque chose gère l'invalidation lorsque le contenu change.
La plupart des équipes commencent à la première, passent à la deuxième quand la facture ou la latence font mal, et atteignent la troisième en branchant la revalidation à la demande sur un webhook.
#Le problème d'invalidation, et comment ne pas l'avoir
La troisième option est la bonne, et sa partie difficile est l'invalidation du cache : la page est une copie d'un calcul, et le calcul pourrait changer à tout moment.
corpusctl supprime le problème plutôt que de le gérer, en rendant l'adresse elle-même immuable.
La publication lance le pipeline de compilation : le contenu est validé, rendu et écrit dans un fichier adressé par hachage dans le stockage objet, puis le manifeste est rafraîchi. Le client résout le slug vers un fragment de manifeste localement, puis récupère cette adresse depuis le cache de périphérie nginx.
Parce que l'adresse est un hachage de contenu :
Une même adresse ne peut jamais renvoyer un contenu différent.
On peut la mettre en cache pendant trente jours en toute sécurité.
La publication écrit une nouvelle adresse ; rien n'a besoin d'être purgé.
Deux requêtes HTTP, toutes deux depuis le cache. Le client de lecture ne porte aucun jeton, car il n'y a rien à autoriser : le contenu est déjà public et déjà compilé.
Le manifeste est fragmenté, de sorte qu'un espace de dix mille entrées télécharge tout de même un petit fichier plutôt qu'un index qui grossit.
#Aperçu des brouillons : la partie que tout le monde branche en dernier et regrette
Les éditeurs s'attendent à voir le travail non publié. Dans Next.js, cela signifie une route qui valide un secret, active le mode brouillon et redirige.
C'est aussi une faille de sécurité dans la plupart des implémentations artisanales. Deux erreurs, toutes deux courantes :
Comparer le secret avec ===. La comparaison de chaînes s'interrompt au premier caractère différent, ce qui divulgue sa longueur et sa position par le timing. Utilisez une comparaison à temps constant.
Rediriger vers ce que dit slug.?slug=https://evil.example.com transforme votre point de terminaison d'aperçu en redirection ouverte sur votre propre domaine — utile pour l'hameçonnage, et un scanner la trouvera.
@corpusctl/next gère les deux :
La comparaison du secret est à temps constant, les URL absolues, les chemins relatifs au protocole et les astuces à barre oblique inversée sont rejetés, et la cible de redirection est la valeur confirmée par le CMS — non celle fournie par la requête. decision.reason est une valeur stable, lisible par une machine, que vous pouvez mapper vers votre propre libellé dans n'importe quelle langue.
Récupérer le contenu est l'affaire d'un après-midi. Le transformer en balisage stylisé est le projet, et c'est là que la plupart des adoptions de CMS headless s'enlisent.
Si la bibliothèque cliente livre un balisage à parti pris, vous passez ce temps à le surcharger. Si elle ne livre rien, vous passez ce temps à le construire — mais au moins vous construisez plutôt que vous ne luttez.
corpusctl adopte délibérément la seconde position. Les moteurs de rendu de blocs sont livrés avec zéro style par défaut — pas une seule ligne de CSS. Chaque type de bloc est surchargeable. Un type de bloc inconnu est ignoré proprement plutôt que de lever une erreur, de sorte qu'un modèle de contenu qui prend de l'avance sur le front-end se dégrade au lieu de casser une page.
L'arbre de rendu du cœur est indépendant du framework ; les adaptateurs React, Vue et Next.js sont de fines couches par-dessus. La règle sous-jacente : si le rendu d'un type de bloc exige du code spécifique à un framework, l'abstraction est au mauvais endroit.
Même avec du contenu pré-compilé, vous voulez généralement que le front-end sache que quelque chose a changé — pour rafraîchir une liste, préchauffer une route, ou vider un cache en aval.
corpusctl déclenche des webhooks lors de la publication, de la dépublication, de la mise à jour et de la suppression. Les livraisons attendent dans une file durable avec backoff exponentiel et jitter, de sorte qu'une heure d'indisponibilité du récepteur ne perd aucun événement. Les charges utiles sont signées par HMAC avec un horodatage, et l'URL du point de terminaison est vérifiée contre le SSRF avant même d'être appelée — les adresses réseau internes sont rejetées à l'enregistrement.
Payload — un seul produit Next.js, configuration sous forme de code, à l'aise avec l'auto-hébergement. Notez que Cloud n'accepte pas de nouveaux projets à la suite de l'acquisition par Figma.
Strapi — la plus grande communauté de loin (~2 800 questions sur Stack Overflow), auto-hébergement gratuit. Strapi Cloud facture par projet.
Sanity — la meilleure expérience d'édition de la catégorie, GROQ, et une tarification par siège.
corpusctl — des lectures immuables pré-compilées sans compteur, plusieurs projets depuis un seul panneau, des moteurs de rendu sans style. Communauté plus petite ; pas de place de marché ; le cache de périphérie est mono-région.
Contenu rendu uniquement côté client. Vous avez choisi Next.js pour le rendu côté serveur, puis récupéré le contenu dans useEffect. Les moteurs de recherche voient une coquille vide.
Aperçu branché après la bascule. Les éditeurs découvrent le premier jour qu'ils ne peuvent pas voir leurs propres brouillons. Branchez-le en premier.
Aucune décision de mise en cache prise. Chaque affichage de page touche le CMS, et la facture ou la latence apparaît au deuxième mois.
Ce que « open source » coûte réellement dans un CMS headless
Open source signifie rarement libre à exécuter. Licences, ventes additionnelles cloud et coût vérifié de six CMS headless populaires — avec les chiffres tirés de leurs propres pages.