
Next.js multi-tenant : servir 1 000 sites depuis une seule application
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.
É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.txtet 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.