SaaS

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 branchVercel envDonnées
mainProductionDonnées réelles
stagingPreview stagingCopie anonymisée
devPreview autres branchesDonné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).