MohandOussadi
Retour au blog
Next.js multi-tenant : servir 1 000 sites depuis une seule application

Next.js multi-tenant : servir 1 000 sites depuis une seule application

19 novembre 2024
3 min de lecture
Next.js
Architecture
SaaS

Imaginez une plateforme qui doit offrir à chacun de ses clients (courtiers, agences, franchisés) son propre site web, avec son nom de domaine, son logo, ses couleurs et son contenu. Déployer une application par client devient vite ingérable : mille clients, mille déploiements, mille mises à jour.

La solution que j'ai mise en place sur un projet de plateforme pour courtiers : une seule application Next.js multi-tenant, capable d'afficher le bon site en fonction du nom de domaine. Elle a permis de déployer automatiquement plus de 1 000 sites. On décortique l'architecture.

Le principe

Un « tenant » est un client de la plateforme. Toutes les requêtes arrivent sur la même application ; c'est le nom de domaine (l'en-tête Host) qui détermine quel tenant afficher.

Chaque domaine est redirigé par le middleware vers la même application, qui charge la configuration du tenant
Chaque domaine est redirigé par le middleware vers la même application, qui charge la configuration du tenant

Étape 1 : le middleware réécrit l'URL

Le middleware s'exécute avant chaque requête. Il lit le domaine et réécrit (sans rediriger) l'URL vers une route interne qui contient le domaine en paramètre. L'utilisateur voit toujours www.cabinet-dupont.fr/contact, mais Next.js sert /cabinet-dupont.fr/contact.

// middleware.ts
import { NextResponse, type NextRequest } from "next/server";

export const config = {
  // Ignore les assets et les routes API
  matcher: ["/((?!api|_next|.*\\..*).*)"],
};

export function middleware(req: NextRequest) {
  const host = req.headers.get("host")!.replace(/^www\./, "").toLowerCase();
  const url = req.nextUrl.clone();
  url.pathname = `/${host}${url.pathname}`;
  return NextResponse.rewrite(url);
}

Étape 2 : une route dynamique par domaine

Toutes les pages vivent sous un segment dynamique app/[domain]/. Chaque page commence par charger la configuration du tenant.

// app/[domain]/page.tsx
import { notFound } from "next/navigation";
import { getTenant } from "@/lib/tenants";

export default async function Home({ params }: { params: Promise<{ domain: string }> }) {
  const { domain } = await params;
  const tenant = await getTenant(domain);
  if (!tenant) notFound();

  return (
    <main style={{ "--brand": tenant.primaryColor } as React.CSSProperties}>
      <img src={tenant.logoUrl} alt={tenant.name} />
      <h1>{tenant.headline}</h1>
    </main>
  );
}

Étape 3 : la configuration par tenant

La configuration (nom, logo, couleurs, pages activées, coordonnées) est stockée en base et mise en cache. Le thème est injecté sous forme de variables CSS : les composants restent identiques pour tous les tenants, seules les valeurs changent.

  • Un seul design system, paramétré par des variables CSS.
  • Des sections activables : chaque tenant choisit les blocs affichés (témoignages, équipe, simulateur…).
  • Un contenu éditable depuis le back-office, sans redéploiement.

Étape 4 : performance et cache

Rendre chaque page à la volée pour mille sites coûterait cher. On combine le rendu statique incrémental (ISR) et l'invalidation ciblée : une page est générée à la première visite, mise en cache, puis régénérée uniquement quand le tenant modifie son contenu.

// Après une modification dans le back-office
import { revalidateTag } from "next/cache";

revalidateTag(`tenant:${domain}`);

Point crucial : la clé de cache doit toujours inclure le tenant. Un cache partagé par erreur entre deux domaines, et un client voit le site d'un autre.

Étape 5 : les domaines personnalisés

Chaque client pointe son domaine vers la plateforme (enregistrement CNAME ou A). Il faut ensuite automatiser l'émission des certificats TLS : les hébergeurs comme Vercel exposent une API d'ajout de domaines, sinon un reverse proxy comme Caddy gère Let's Encrypt automatiquement. Prévoyez aussi un sous-domaine par défaut (client.plateforme.com) pour la mise en ligne immédiate.

Les points de vigilance

  • L'isolation des données : chaque requête en base doit être filtrée par tenant. Centralisez ce filtre plutôt que de compter sur la discipline de chacun.
  • Le SEO par tenant : sitemap.xml, robots.txt et balises canoniques doivent être générés dynamiquement pour chaque domaine.
  • L'observabilité : ajoutez le tenant dans chaque log et chaque trace, sinon un bug « chez un client » devient impossible à reproduire.

Conclusion

Le multi-tenant transforme un problème d'exploitation (mille déploiements) en problème de conception (une application bien paramétrée). Avec un middleware, un segment dynamique, une configuration en cache et un design system piloté par des variables CSS, Next.js permet de servir des centaines de sites personnalisés avec une seule base de code.

Partager cet article