🏢 Multi-Tenant SaaS Platform
Host hundreds or thousands of customer websites under custom domains — all from a single Next.js deployment. Wildcard domains + Edge Middleware identify tenants, Edge Config stores branding, and ISR caching isolates content per tenant.
Why This Pattern?
🏗️ One Codebase, All Tenants
A single Next.js deployment serves hundreds or thousands of branded sites. No per-tenant hosting, no infrastructure multiplication.
🌐 Custom Domains at Scale
Vercel's Domains API adds/removes domains programmatically. Each tenant gets client-a.com pointing to your single deployment.
🎨 Instant Tenant Customization
Edge Config stores themes (colors, fonts, logos). Update a tenant's brand in seconds — no redeployment needed.
🔒 Tenant-Isolated Caching
ISR cache keys include the hostname. client-a.com never sees client-b.com's cached content. Security by architecture.
Architecture Layers
Domain Routing (Edge Middleware)
Matches incoming hostname (client-a.com, client-b.com) to a tenantId. Reads from Edge Config or KV for tenant identification. Sets x-tenant-id header for downstream use.
Tenant Config Store
Edge Config for themes, feature flags, and branding (near-zero latency). Database for full tenant profiles, billing, permissions.
Dynamic Rendering
Single Next.js codebase renders pages based on tenant config — theme colors, logos, fonts, content, features. ISR cache keys include hostname for tenant isolation.
CMS (Tenant-Scoped)
Shared headless CMS with tenant-scoped content. Each tenant manages their own pages via CMS dashboard.
Domain Management (Vercel API)
Programmatic custom domain assignment via Vercel's Domains API. Tenants add their domain → CNAME to Vercel → auto SSL.
Tenant Admin Dashboard
SSR authenticated dashboard for managing content, settings, branding, and analytics per tenant.
How Tenant Routing Works
// middleware.ts — runs on every request at the edge
import { NextResponse } from 'next/server';
import { get } from '@vercel/edge-config';
export async function middleware(request) {
const hostname = request.headers.get('host') || '';
// Look up tenant from Edge Config (< 1ms read)
const tenantConfig = await get(hostname);
if (!tenantConfig) {
return NextResponse.redirect(new URL('/not-found', request.url));
}
// Pass tenant info downstream via headers
const response = NextResponse.next();
response.headers.set('x-tenant-id', tenantConfig.id);
response.headers.set('x-tenant-theme', tenantConfig.theme);
return response;
}
// Adding a custom domain programmatically:
// POST https://api.vercel.com/v10/projects/{projectId}/domains
// { "name": "client-a.com" }
// → Customer adds CNAME: cname.vercel-dns.com
// → Vercel auto-provisions SSL⚠️ Critical: Cache Isolation
The #1 security concern:Without proper cache isolation, Tenant A can see Tenant B's content. ISR cache keys must include the hostname or tenantId.
❌ Wrong: No tenant in cache key
ISR page cached as /homepage → shared across all tenants
✅ Correct: Tenant in cache key
Middleware sets x-tenant-id header → Next.js includes it in cache key automatically
Customer Results
AI-powered website builder serving millions of small business sites on a single Vercel deployment. Custom domains via Domains API.
Developer documentation platform hosting thousands of custom docs domains. Each customer gets docs.their-company.com.
Developer blogging platform where each writer gets their own custom domain — all routed through one Vercel project.