Back

Pourquoi nous avons créé notre propre CMS headless

Published by January 21, 2025 · Reading time 7 minutes · Created by Cod'Hash Team

Le constat : les CMS ne répondent pas aux besoins réels

Après des années à travailler avec différents CMS pour nos clients — WordPress, Strapi, Contentful, Sanity — un constat s'impose : aucune solution ne répond vraiment aux besoins de nos projets.

Ce n'est pas une critique gratuite. Ce sont des constats concrets, issus de vrais projets de production.

Les problèmes rencontrés

WordPress : une base de données relationnelle utilisée comme un système de fichiers. Chaque plugin ajoute des dizaines de tables. Le moindre article multilingue devient un casse-tête entre WPML, Polylang ou TranslatePress. Résultat ? Des performances catastrophiques et une dette technique qui s'accumule.

Strapi : prometteur sur le papier, mais la réalité est différente. La gestion des relations complexes devient vite un enfer. Le système de permissions manque de granularité. Et surtout, la migration de v3 à v4 a cassé tellement de projets que beaucoup ont préféré tout réécrire.

Contentful & Sanity : excellents pour du contenu structuré, mais leur modèle de pricing explose dès que vous avez un vrai trafic. $500/mois pour gérer 10 blogs d'entreprise ? Vous payez pour des features dont vous n'avez pas besoin.

Le vrai problème ? Ces CMS ne sont pas conçus pour les besoins spécifiques des agences et des SaaS modernes.

Ce dont nous avions vraiment besoin

En analysant nos 15 derniers projets clients, des patterns clairs se sont dégagés :

Une API REST simple et prévisible

Pas besoin de GraphQL complexe avec 50 champs optionnels. Nos équipes frontend veulent :

  • GET /api/v1/blog/articles → liste des articles
  • GET /api/v1/blog/articles/[slug] → un article spécifique
  • POST /api/v1/blog/articles → créer un article

C'est tout. Des endpoints clairs, une authentification par API key, et ça fonctionne. Pas de documentation de 200 pages à lire.

Le multilingue natif, pas en plugin

Quand un client nous demande un blog FR/EN/DE, on ne veut pas :

  • Installer un plugin qui ajoute 12 tables
  • Gérer des slugs qui se télescopent
  • Debugger pourquoi /fr/article renvoie la version anglaise

Notre solution ? Chaque article a des translations natives. Un slug par langue. Des métadonnées SEO par langue. Le tout dans une structure de données propre et prévisible.

// Structure réelle de notre API
{
  "id": "article-123",
  "status": "PUBLISHED",
  "translations": [
    {
      "language": "fr",
      "slug": "pourquoi-cms-headless",
      "title": "Pourquoi un CMS headless ?",
      "seoTitle": "CMS Headless : Notre Solution sur-mesure",
      "published": true
    },
    {
      "language": "en",
      "slug": "why-headless-cms",
      "title": "Why a headless CMS?",
      "seoTitle": "Headless CMS: Our Custom Solution",
      "published": true
    }
  ]
}

La multi-organisation sans friction

Nos clients veulent souvent gérer plusieurs blogs. Mais avec les CMS classiques, ça veut dire :

  • Une installation par blog (WordPress)
  • Un projet par environnement (Contentful à $500/mois × N)
  • Des migrations de données à chaque nouveau client

Notre approche ? Un SaaS multi-tenant. Chaque organisation a son blog, ses API keys, son équipe, sa facturation. Un seul déploiement. Une seule base de données. Une seule codebase.

Les permissions granulaires

Un CMS moderne doit gérer des équipes. Voici ce qu'on a implémenté :

  • OWNER : gestion complète (articles, membres, facturation)
  • EDITOR : création, modification, publication d'articles
  • WRITER : création et modification, mais pas de publication

Chaque membre peut avoir des permissions sur les API keys :

  • blog:read — lecture seule
  • blog:write — création/modification
  • blog:publish — publication d'articles
  • blog:manage — gestion complète

Pas de rôles prédéfinis rigides. Des permissions composables.

Le SEO intégré, pas ajouté après coup

Générer un sitemap XML ? C'est un endpoint :

GET /api/v1/blog/sitemap.xml

Un flux RSS ? Même logique :

GET /api/v1/blog/rss.xml

Métadonnées Open Graph, temps de lecture estimé, images optimisées, canonical URLs — tout ça est géré nativement. Pas besoin d'installer Yoast ou un équivalent.

Notre stack technique

Nous ne sommes pas partis de zéro. Nous avons choisi des technologies éprouvées :

Next.js 15 + React 19 + TypeScript

  • Server Components par défaut (moins de JavaScript côté client)
  • App Router pour une structure claire
  • Turbopack pour des builds rapides

PostgreSQL + Prisma

  • Base relationnelle solide
  • Migrations versionnées
  • Schema modulaire (un fichier .prisma par feature)

better-auth

  • Organisations natives
  • Magic links pour l'authentification
  • Intégration Stripe pour la facturation

Validation et types

  • Zod pour la validation des données
  • react-hook-form pour les formulaires
  • Type-safety de bout en bout

API REST documentée

  • Endpoints cohérents (/api/v1/blog/*)
  • Validation automatique avec Zod
  • Gestion d'erreurs structurée

Les features qui changent tout

Analytics intégrées

Chaque vue d'article est trackée avec :

  • La langue consultée
  • Le pays d'origine (sans tracker invasif)
  • Les articles tendance en temps réel

Endpoint :

GET /api/v1/blog/analytics

Recherche full-text native

Recherche dans le titre, l'extrait et le contenu. Filtres par catégorie, tag, statut. Tri par date ou popularité.

GET /api/v1/blog/search?q=headless&category=dev&sort=popular

Upload et gestion de médias

UploadThing pour l'upload. Tracking des dimensions, poids, utilisation. Alt text pour l'accessibilité.

API keys avec scopes

Créez des clés API avec des permissions précises :

{
  "name": "Frontend Production",
  "permissions": ["blog:read"],
  "expiresAt": "2026-01-21"
}

Les résultats concrets

Depuis 6 mois de développement et 3 mois en production :

Performance

  • API REST : <100ms de réponse moyenne
  • Génération de sitemap : <200ms pour 1000 articles
  • Recherche full-text : <150ms

Développement

  • Un nouveau client = 5 minutes (création d'organisation)
  • Intégration frontend = 1 jour (vs 1 semaine avec Strapi)
  • Migration de contenu = script automatisé

Coûts

  • Une instance Vercel = tous les clients
  • PostgreSQL managé = $25/mois
  • Pas de frais par projet ou par utilisateur

Ce que nous avons appris

Ce qui fonctionne

Le headless est la bonne approche. Nos clients veulent du Next.js, du Nuxt, du Astro. Ils ne veulent pas d'un thème WordPress.

La simplicité d'API paie. Pas de GraphQL complexe. Des endpoints REST clairs. Une documentation de 2 pages.

Le multilingue natif est essentiel. Ce n'est pas un plugin. C'est au cœur du data model.

Ce qui reste à améliorer

La gestion des médias. Pour l'instant, c'est fonctionnel mais basique. Nous prévoyons :

  • Compression automatique d'images
  • Génération de srcsets responsive
  • Stockage S3 avec CDN

Les webhooks. Actuellement, il faut poll l'API. Nous ajoutons :

  • Webhooks sur création/modification d'articles
  • Invalidation automatique de cache
  • Intégration avec Vercel/Netlify pour rebuild

L'éditeur de contenu. Tiptap fonctionne, mais nous voulons :

  • Blocs réutilisables (code, citations, callouts)
  • Preview en temps réel
  • Suggestions de liens internes

Conclusion : une solution taillée pour nos besoins

Nous n'avons pas créé ce CMS par ego technique. Nous l'avons créé parce que aucune solution existante ne répondait à nos besoins réels :

  • APIs REST simples et prévisibles
  • Multilingue natif sans plugin
  • Multi-tenant SaaS avec facturation intégrée
  • Permissions granulaires par rôle
  • SEO et analytics intégrés
  • Performance optimale (Next.js 15, React Server Components)

Ce CMS n'est pas pour tout le monde. Si vous voulez un site vitrine avec 10 pages statiques, WordPress fera l'affaire.

Mais si vous construisez :

  • Un SaaS avec un blog multilingue par client
  • Une plateforme de contenu avec équipes distribuées
  • Un hub de documentation technique
  • Un système de publication programmatique via API

Alors vous rencontrerez les mêmes limites que nous. Et vous comprendrez pourquoi nous avons fait ce choix.


Vous voulez discuter de votre projet ou en savoir plus ? Contactez-nous.