« Headless » est devenu un argument de vente. On le colle sur à peu près n'importe quel projet web, comme si découpler le contenu de son affichage était toujours un progrès. La plupart du temps c'est faux : tu ajoutes des serveurs, des points de panne et des heures d'ops pour un blog que trois fichiers Markdown auraient servi sans broncher. Mais il existe des cas où le découplage n'est pas une mode, c'est juste la bonne réponse. Un projet client pour lequel j'ai monté l'architecture en est un bon exemple.
Le besoin était double dès le départ. D'un côté un site public multilingue, vitrine des fiches, qui doit charger vite et se référencer correctement. De l'autre une interface de gestion, le « manager », où l'équipe administre les données métier. Deux usages, deux audiences, deux rythmes de mise à jour. La vraie question n'était pas « headless ou pas », elle était : comment ces deux applications partagent les mêmes données sans se marcher dessus.
Un backend, deux fronts
La réponse tient en une phrase : un seul backend, deux frontends Next.js distincts. Strapi joue le CMS headless, PostgreSQL stocke tout, et les deux apps consomment la même API. Le site public lit les fiches, les pages de contenu et leurs traductions. Le manager lit et écrit les données de gestion. Même source de vérité, deux consommateurs qui n'ont pas à se connaître.
Ce que ça évite, c'est la duplication du modèle. Si la définition d'une « fiche » vivait à deux endroits, une fois dans le site et une fois dans le manager, il faudrait la maintenir deux fois. Et un jour les deux versions divergent, toujours au pire moment. Là, le modèle est défini une seule fois dans Strapi, les deux fronts en héritent. Tu ajoutes un champ, il apparaît dans l'API, les deux apps peuvent le lire. Point.
Côté Next, lire ces données depuis un Server Component reste direct. Pas de client lourd, un fetch et la réponse JSON de Strapi.
// app/[locale]/fiches/page.tsx (Server Component)
async function getFiches(locale: string) {
const res = await fetch(
`${process.env.STRAPI_URL}/api/fiches?locale=${locale}&populate=photos`,
{
headers: { Authorization: `Bearer ${process.env.STRAPI_TOKEN}` },
next: { revalidate: 60 },
}
);
if (!res.ok) throw new Error(`Strapi a répondu ${res.status}`);
const { data } = await res.json();
return data;
}Le revalidate met les fiches en cache côté Next et les rafraîchit en arrière-plan : le visiteur ne paie jamais l'aller-retour vers Strapi en temps réel, et l'éditeur voit sa modification apparaître sans qu'on rebuild quoi que ce soit. Le if (!res.ok) n'est pas décoratif. Une API distante, ça tombe, ça renvoie un 500, ça change de schéma le jour d'une migration. Mieux vaut lever une erreur franche que servir une page à moitié vide en silence.
L'édition sans passer par le développeur
Le deuxième gain est moins technique, mais c'est lui qui change la vie au quotidien : l'équipe du client édite son contenu sans m'appeler. Strapi génère une interface d'administration à partir du modèle de données. Ajouter une fiche, corriger une description, publier une traduction, tout ça se fait dans le back-office, pas dans une pull request.
Pour un dev, c'est presque contre-intuitif. On aime garder le contenu en fichiers versionnés, du Markdown bien rangé dans le repo, parce que c'est propre et que ça passe par la revue de code. Sauf que le jour où c'est une équipe non-technique qui doit publier plusieurs fois par semaine, le repo devient un goulot d'étranglement, et toi le goulot avec. Strapi met ce pouvoir entre leurs mains et me sort du chemin critique. C'est exactement ce que je veux : qu'on n'ait plus besoin de moi pour changer une virgule.
Le multilingue suit la même logique. Le plugin i18n de Strapi gère les traductions au niveau de l'entrée, chaque fiche existe dans plusieurs langues, et le front réclame la bonne avec un simple ?locale=. C'est l'API qui porte le multilingue, le code du site ne fait que demander la langue courante.
API-first, le pari du long terme
Troisième raison, plus stratégique : tout transite par l'API. Aujourd'hui ce sont deux fronts web. Si demain le client veut une app mobile pour ses équipes sur le terrain, ou un portail dédié à un autre public, il tape la même API et ne reconstruit rien côté données.
Je le dis avec prudence, parce que c'est l'argument qu'on sur-vend le plus. « API-first, tu pourras tout brancher plus tard » : ce « plus tard » n'arrive souvent jamais, et on a payé la complexité d'avance pour un futur qui ne vient pas. La différence sur ce projet, c'est que le deuxième consommateur existait dès le premier jour. Le manager n'était pas une hypothèse de roadmap, il était dans le périmètre. L'API ne servait pas un besoin imaginé « au cas où », elle servait deux consommateurs réels tout de suite. C'est toute la nuance entre une architecture qui anticipe un vrai besoin et une qui se complique pour le plaisir du schéma au tableau blanc.
Ce que le découplage coûte vraiment
Maintenant la part honnête, celle que les articles qui vendent le headless oublient de mentionner. Découpler, c'est multiplier les pièces. Au lieu d'une application à déployer et surveiller, tu en as quatre qui doivent tourner ensemble : Strapi, PostgreSQL, le front public, le front manager, sans compter l'hébergement et les sauvegardes de tout ça. Chaque pièce a ses logs, ses mises à jour, sa façon de tomber. La surface d'ops n'a rien à voir avec celle d'un monolithe.
L'authentification et les permissions deviennent un sujet à part entière. Le site public lit en anonyme, ou via un token en lecture seule bien cadenassé. Le manager, lui, écrit, donc il faut une vraie auth, des rôles, des permissions par collection. Strapi fournit la mécanique, mais la configurer correctement, fermer ce qui doit l'être et vérifier qu'un token public ne peut pas écrire, c'est du travail qui n'existe tout simplement pas dans une app monolithique où l'accès se contrôle au même endroit que le rendu.
L'i18n ajoute sa propre couche. Gérer les langues au niveau de l'API, c'est pratique, mais ça veut dire penser le fallback quand une traduction manque, garder les entrées synchronisées entre les langues, et tester chaque parcours dans chaque locale. Rien d'insurmontable, mais ce sont des heures qui s'accumulent et qu'on ne facture jamais assez.
Quand je ne le ferais pas
Si on était venu me voir pour un site vitrine de cinq pages avec un formulaire de contact, je n'aurais jamais sorti Strapi. Un Next monolithique avec le contenu en fichiers aurait fait le travail, ou carrément un WordPress si le client tient à éditer lui-même, pour une fraction du coût et de la maintenance. Le headless ne se justifie pas par l'élégance du diagramme. Il se justifie par un besoin concret : plusieurs frontends, une équipe contenu qui doit être autonome, ou une API destinée à durer et à grandir. Coche au moins une de ces cases et le découplage te rend un vrai service. N'en coche aucune et tu t'es offert un backend à maintenir pour rien.
Ce projet cochait deux cases sur trois dès le premier jour, deux fronts et une équipe qui édite. C'est pour ça que je referais exactement la même architecture sans hésiter. Et ce blog que tu lis là ? Justement non. Ces articles sont de simples fichiers MDX dans le dépôt du portfolio, aucun Strapi, aucun Postgres derrière. La bonne archi, ce n'est pas la plus impressionnante, c'est celle qui colle au besoin. Et le besoin d'un blog perso tient dans un dossier de fichiers.
Si tu hésites entre un CMS headless et quelque chose de plus simple pour ton projet, parlons-en.
