SaaS Platform multi-tenant : de 0 à 8 clients en production avec Next.js
Architecture d'une plateforme qui sert 22 services modulaires (blog, CRM, booking, formulaires…) à 8 clients en production — comment j'ai résolu l'isolation des données, le SDK TypeScript publié et le déploiement sans downtime.
Contexte
Depuis 2024, je développe des sites pour des artisans, thérapeutes et PME avec Next.js. Rapidement, le même problème revenait : chaque client avait besoin d'un blog, d'un système de devis, de formulaires de contact, d'un booking. Je réimplémentais les mêmes choses.
Ma réponse : une SaaS Services Platform — une plateforme centrale qui expose ces services en API, et que n'importe quel site peut consommer via un SDK TypeScript.
Aujourd'hui : 22 services modulaires, 8 clients en production, un SDK publié sur le registre npm privé GitLab.
Architecture multi-tenant
Le cœur du système : chaque client est un Site dans la base de données. Chaque ressource (Post, Quote, Contact…) appartient à un Site.
model Site {
id String @id @default(cuid())
domain String @unique
name String
config Json // modules activés, thème, options
posts Post[]
contacts Contact[]
quotes Quote[]
// ...
}
model Post {
id String @id @default(cuid())
siteId String
site Site @relation(fields: [siteId], references: [id])
// ...
}
L'isolation est au niveau applicatif, pas au niveau base de données. Un seul cluster Neon PostgreSQL, une seule app Next.js, mais chaque requête API filtre systématiquement par siteId.
Pourquoi pas une DB par tenant ? 8 clients → 8 branches Neon gratuites possible, mais la maintenance devient vite lourde (migrations, monitoring). L'isolation applicative est suffisante pour ce cas.
Les 22 services
Blog → Posts, catégories, tags, SEO
CRM → Contacts, prospects, pipeline
Devis → Quotes, items, PDF, statuts
Booking → Slots, réservations, emails de confirmation
Forms → Formulaires configurables, submissions
Newsletter → Listes, campagnes, désabonnement RGPD
Social → Posts réseaux sociaux (DB-only pour l'instant)
Analytics → Événements custom, vues de pages
Auth → Sessions utilisateur, rôles par site
...
Chaque service est une feature isolée : un dossier src/features/blog/, des routes API dédiées, des actions Prisma séparées.
Le SDK TypeScript
Pour que les sites clients puissent consommer la plateforme facilement, j'ai publié un SDK :
import { createSaasClient } from '@saas-platform/sdk'
const client = createSaasClient({
apiUrl: 'https://saas.benydev.fr',
siteId: process.env.SAAS_SITE_ID!,
})
// Blog
const posts = await client.blog.getPosts({ limit: 3, lang: 'fr' })
// CRM
const contacts = await client.crm.getContacts({ status: 'active' })
// Devis
const quote = await client.quotes.createQuote({ ... })
Le SDK est publié sur le registre npm GitLab privé. La CI/CD GitLab le rebuild et publie automatiquement à chaque tag semver.
Déploiement : Vercel + Neon + GitLab CI
Chaque environnement a sa branche Neon :
| Neon branch | Vercel env | Données |
|---|---|---|
main | Production | Données réelles |
staging | Preview staging | Copie anonymisée |
dev | Preview autres branches | Données de dev |
La règle critique : vercel env pull sans --git-branch staging remonte la prod DB en .env.local. J'ai créé un script pnpm env:pull:staging qui force la bonne branche.
Les migrations Prisma : prisma migrate deploy sur main au deploy, prisma db push en dev (Neon ne supporte pas les shadow databases pour migrate dev).
Ce que j'ai appris
1. L'isolation applicative tient à l'échelle
8 clients, aucun incident de fuite de données. La clé : middleware de validation du siteId sur chaque route API, et helpers Prisma qui injectent systématiquement le filtre.
2. Le SDK change la DX
Sans SDK, chaque site client faisait des fetch manuels avec gestion d'erreur répétée. Le SDK centralise ça. Les clients (sites Next.js) ont juste à importer et appeler.
3. Versionner l'API dès le début
J'ai ajouté /api/v1/ trop tard. Résultat : migration douloureuse de 8 sites pour adapter les URLs. Maintenant tout est versionné dès le départ.
Résultats
- 8 clients actifs, 22 services modulaires
- Déploiement moyen d'un nouveau site client : 2-4h (contre 2-4 jours avant)
- Zéro incident de fuite de données cross-tenant
- SDK TypeScript publié et utilisé par les sites clients
- Un paysagiste IDF : +65 devis générés en 1 an via le service Quotes
Le mono-repo reste la prochaine étape (actuellement SDK + Platform + sites clients en repos séparés).