Next.js 15 Multi-Tenant Architecture: Wildcard Subdomains, Custom Domains, and Edge Rewrites

A comprehensive engineering guide to building enterprise multi-tenant platforms in Next.js 15: wildcard DNS, custom vanity domains, Cloudflare for SaaS SSL, and Edge middleware rewrites.
Next.js 15 Multi-Tenant Architecture: Wildcard Subdomains, Custom Domains, and Edge Rewrites
Platforms like Shopify, Notion, Hashnode, Webflow, and Vercel share a signature architectural capability: Multi-Tenant Domain Routing.
When a customer creates an account on your platform, they get an instant sub-domain (e.g., acme.yourplatform.com). Furthermore, enterprise customers can connect their own vanity apex domains (e.g., blog.acme.com or acmestore.com) with zero downtime, automatic SSL certificate provisioning, and instantaneous routing to their isolated tenant workspace.
Crucially, you do not deploy hundreds of separate Next.js application servers for each tenant.
Instead, a single, unified Next.js 15 App Router codebase handles millions of distinct custom domains dynamically via Edge Middleware URL Rewriting and Cloudflare for SaaS TLS termination.
In this deep architectural guide, we break down how to design, route, secure, and deploy an enterprise multi-tenant platform in Next.js 15 based on platforms engineered at MojoStudio.
1. The Multi-Tenant Routing Architecture
+-----------------------------------------------------------------------------------------+
| Next.js 15 Multi-Tenant Edge Routing Architecture |
+-----------------------------------------------------------------------------------------+
[User Visits: acme.yourplatform.com OR custom-domain.com]
|
v
+-----------------------------------------------------------------+
| Cloudflare for SaaS Edge (SSL / TLS Termination & WAF) |
| - Custom SSL Cert verified via CNAME |
| - Forwards original 'Host' header to Next.js Origin |
+-----------------------------------------------------------------+
|
v
+-----------------------------------------------------------------+
| Next.js 15 Edge Middleware (middleware.ts) |
| 1. Extract hostname: 'acme.yourplatform.com' |
| 2. Redis Edge Lookup: Hostname -> Tenant ID 'tenant_9842' (<2ms)|
| 3. Internal URL Rewrite: /sites/tenant_9842/[...path] |
+-----------------------------------------------------------------+
|
v
+-----------------------------------------------------------------+
| App Router Dynamic Route Folder: app/sites/[site]/page.tsx |
| - Renders isolated tenant branding, theme tokens & database rows|
+-----------------------------------------------------------------+2. Setting Up Domain Infrastructure (Wildcard DNS & SSL for SaaS)
To support unlimited dynamic domains, your DNS and TLS termination layer must be automated:
1. Wildcard DNS Configuration
In your DNS provider (Cloudflare, AWS Route53), configure a wildcard CNAME record:
Type: CNAME
Name: *
Target: cname.yourplatform.com
TTL: Auto / Proxied2. Automated Custom Domain SSL via Cloudflare for SaaS
When a tenant adds a custom domain (e.g., portal.customer.com), your backend calls the Cloudflare API to provision an on-demand SSL certificate:
// services/cloudflareDomains.ts
export async function registerTenantCustomDomain(customHostname: string) {
const response = await fetch(
`https://api.cloudflare.com/client/v4/zones/${process.env.CLOUDFLARE_ZONE_ID}/custom_hostnames`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
hostname: customHostname,
ssl: { method: "http", type: "dv" },
}),
}
);
const data = await response.json();
return data.result; // Returns CNAME and TXT verification records to show the tenant!
}The tenant points their CNAME record to cname.yourplatform.com, and Cloudflare automatically issues an SSL certificate within 90 seconds.
3. High-Speed Edge Middleware Implementation (middleware.ts)
Because middleware.ts runs on every single incoming HTTP request, querying a traditional relational database (like PostgreSQL) inside middleware will cause latency spikes and exhaust database connection pools.
The solution is to cache hostname-to-tenant mappings inside an ultra-fast Edge Redis (Upstash Redis):
// middleware.ts
import { NextResponse, type NextRequest } from "next/server";
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
export const config = {
matcher: [
/*
* Match all paths except static files, favicon, images, and API routes
*/
"/((?!api/|_next/static|_next/image|favicon.ico).*)",
],
};
export async function middleware(req: NextRequest) {
const url = req.nextUrl;
const hostname = req.headers.get("host")?.toLowerCase() || "";
// 1. Define Primary Platform Domains
const appDomain = process.env.NEXT_PUBLIC_ROOT_DOMAIN || "yourplatform.com";
const isRootDomain = hostname === appDomain || hostname === `www.${appDomain}`;
// 2. If visiting the marketing home page, serve standard app/page.tsx
if (isRootDomain) {
return NextResponse.next();
}
// 3. Resolve Tenant Subdomain or Custom Domain via Edge Redis (<2ms)
let tenantSlug: string | null = null;
if (hostname.endsWith(`.${appDomain}`)) {
// Wildcard Subdomain: 'acme.yourplatform.com' -> 'acme'
tenantSlug = hostname.replace(`.${appDomain}`, "");
} else {
// Custom Vanity Domain: 'portal.customer.com' -> Lookup in Redis
tenantSlug = await redis.get<string>(`custom_domain:${hostname}`);
}
if (!tenantSlug) {
// Hostname not registered in platform
return NextResponse.rewrite(new URL("/404-tenant-not-found", req.url));
}
// 4. Rewrite URL Internally to Multi-Tenant Route Directory
// Rewrites https://acme.yourplatform.com/about -> /sites/acme/about
return NextResponse.rewrite(
new URL(`/sites/`{tenantSlug}`{url.pathname}${url.search}`, req.url)
);
}4. Next.js App Router Directory Structure for Multi-Tenancy
Organize your App Router directory to cleanly separate platform marketing, user authentication, and tenant sites:
app/
├── (marketing)/ # Primary Marketing Site (yourplatform.com)
│ ├── page.tsx
│ └── pricing/page.tsx
├── (app)/ # SaaS Admin App (app.yourplatform.com)
│ ├── dashboard/page.tsx
│ └── settings/domains/page.tsx
├── sites/ # DYNAMIC MULTI-TENANT SITES
│ └── [site]/ # Matched via Middleware Rewrite!
│ ├── layout.tsx # Tenant-Specific Theme & Layout
│ ├── page.tsx # Tenant Homepage (acme.yourplatform.com)
│ └── blog/
│ └── [slug]/page.tsx
└── middleware.ts # The Edge Traffic ControllerImplementing the Multi-Tenant Dynamic Page (app/sites/[site]/page.tsx):
// app/sites/[site]/page.tsx
import { notFound } from "next/navigation";
import db from "@/lib/db";
import { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: Promise<{ site: string }>;
}): Promise<Metadata> {
const { site } = await params;
const tenant = await db.getTenantBySlug(site);
if (!tenant) return {};
return {
title: `${tenant.name} — Official Portal`,
description: tenant.description,
};
}
export default async function TenantHomePage({
params,
}: {
params: Promise<{ site: string }>;
}) {
const { site } = await params;
const tenant = await db.getTenantWithProducts(site);
if (!tenant) notFound();
return (
<div style={{ "--primary-color": tenant.brandColor } as React.CSSProperties}>
<header className="p-6 border-b">
<h1 className="text-3xl font-bold">{tenant.name}</h1>
</header>
<main className="max-w-6xl mx-auto py-12 px-4">
<p className="text-lg text-neutral-400">{tenant.tagline}</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
{tenant.products.map((p: any) => (
<div key={p.id} className="p-4 border rounded-xl">
<h3>{p.title}</h3>
<p>${p.price}</p>
</div>
))}
</div>
</main>
</div>
);
}5. Local Development Testing with *.localhost:3000
Testing wildcard subdomains locally is seamless in Next.js:
- In modern Chrome/Firefox browsers, any subdomain on
localhost(e.g.,acme.localhost:3000orstore.localhost:3000) automatically resolves to127.0.0.1. - To test custom vanity domains locally, add entries to your operating system
/etc/hostsfile:
127.0.0.1 custom-domain.test
127.0.0.1 client-portal.testConclusion: Building Scalable Multi-Tenant Platforms
A modern multi-tenant domain architecture transforms a standard SaaS product into an enterprise platform that gives customers total brand ownership.
By pairing Cloudflare for SaaS for automated SSL, Upstash Redis edge caching for sub-2ms domain lookups, and Next.js 15 App Router middleware rewrites, engineering teams can serve millions of custom tenant domains from a single, maintainable codebase.
At MojoStudio, our engineering team builds custom multi-tenant SaaS platforms, white-label CMS engines, and edge routing architectures. Contact our team to scope your multi-tenant platform today.
Frequently Asked Questions
1. What is Multi-Tenant Domain Routing in Next.js 15?
Multi-tenant domain routing is an architectural pattern that allows a single Next.js codebase to serve thousands of distinct tenant subdomains (e.g., acme.platform.com) and custom vanity domains (e.g., acme.com) dynamically via Edge middleware rewrites.
2. How does Next.js Edge Middleware handle URL rewriting?
Edge Middleware inspects the incoming Host header, identifies the tenant via an ultra-fast Redis cache lookup, and uses NextResponse.rewrite() to map the request internally to app/sites/[site]/... without changing the URL visible in the user's browser.
3. How do you provision SSL certificates for thousands of custom domains automatically?
By integrating with Cloudflare for SaaS (SSL for SaaS). When a customer connects their custom domain, the backend triggers Cloudflare APIs to provision, renew, and terminate DV SSL certificates automatically.
4. Why should I use Redis inside middleware instead of PostgreSQL?
middleware.ts executes on every single incoming request at the edge. Querying a traditional PostgreSQL database would exhaust connection pools and add 100ms+ latency. Edge Redis (Upstash) provides sub-2ms response times.
5. Can each tenant customize their own CSS theme and branding?
Yes. Tenant branding tokens (colors, logos, font choices) are fetched server-side in app/sites/[site]/layout.tsx and injected dynamically into CSS custom properties (variables) on the root container.
6. How do I test wildcard subdomains on my local machine?
Modern web browsers resolve all *.localhost:3000 subdomains to 127.0.0.1 automatically, allowing you to test acme.localhost:3000 directly in development without configuring DNS proxies.
7. How does multi-tenant routing prevent cross-tenant data leaks?
Data isolation is enforced in the database layer using tenant IDs and PostgreSQL Row-Level Security (RLS), ensuring that even if a URL is manipulated, SQL queries only access records belonging to the authenticated tenant.
8. Does Next.js multi-tenant routing support custom domain sitemaps?
Yes. You can dynamically generate tenant-specific sitemaps by implementing app/sites/[site]/sitemap.ts to return only the URLs associated with that specific tenant's domain.
9. What is the performance overhead of Edge middleware domain rewriting?
With Edge Redis caching, the entire middleware domain extraction, cache hit, and internal URL rewrite executes in under 3 to 5 milliseconds.
10. How does MojoStudio help companies build multi-tenant SaaS platforms?
MojoStudio engineers custom multi-tenant architectures, Cloudflare SSL pipelines, dynamic Next.js 15 routing, and isolated database schemas. Explore our Web Platform Engineering Services to learn more.
Frequently Asked Questions
Multi-tenant domain routing is an architectural pattern that allows a single Next.js codebase to serve thousands of distinct tenant subdomains (e.g., `acme.platform.com`) and custom vanity domains (e.g., `acme.com`) dynamically via Edge middleware rewrites.