Implementing Multi-Tenant Architecture with Subdomains in Next.js
Architecting dynamic multi-tenant web applications where each customer or organization receives their own dedicated subdomain (such as `acme.yourplatform.com`) requires robust request routing, wildcard DNS configuration, and intelligent middleware interception.
In modern SaaS (Software as a Service) development, multi-tenancy allows a single application instance to serve multiple distinct clients while keeping their organizational data, branding, and user permissions logically isolated.
Next.js provides exceptional primitives for building scalable multi-tenant architectures. By leveraging Next.js Middleware, dynamic routing segments, and modern edge runtimes, developers can intercept incoming requests, extract subdomain identifiers, and rewrite requests to tenant-specific views seamlessly.
This comprehensive guide explores how to configure wildcard subdomains, implement custom middleware routing, manage tenant identification, and structure isolated database access patterns in Next.js.
Understanding Tenant Isolation and Subdomain Routing
Multi-tenant architectures generally fall into three database isolation models:
• Shared Database, Shared Schema: All tenants share the same database tables, differentiated only by a `tenant_id` foreign key column. This is cost-effective and easy to scale but requires strict query filtering.
• Shared Database, Isolated Schemas: Tenants share a single physical database cluster but maintain separate database schemas, providing cleaner logical isolation.
• Database-per-Tenant: Each enterprise customer receives an entirely isolated database instance, offering maximum security and compliance for high-value clients.
Regardless of the database strategy chosen, the frontend entry point relies on dynamic subdomain resolution. When a user navigates to `tenant.domain.com`, the application must intercept the request, extract `tenant`, and load the corresponding organization workspace.
Intercepting Requests with Next.js Middleware
Next.js Middleware executes at the Edge before a request completes, making it the ideal place to inspect host headers, extract subdomains, and rewrite incoming URLs to internal routing structures.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const url = request.nextUrl;
const hostname = request.headers.get('host') || '';
// Define your production domain or local development domain
const rootDomain = process.env.NODE_ENV === 'production' ? 'yourplatform.com' : 'localhost:3000';
// Check if the request is on a subdomain
if (hostname.includes(`.${rootDomain}`)) {
const subdomain = hostname.replace(`.${rootDomain}`, '');
// Prevent rewriting internal paths or api routes
if (subdomain === 'www' || subdomain === '') {
return NextResponse.next();
}
// Rewrite request to dynamic tenant route folder
return NextResponse.rewrite(new URL(`/tenant/${subdomain}${url.pathname}${url.search}`, request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
Structuring Tenant Views in the App Router
Once middleware rewrites incoming subdomain requests, your Next.js App Router file structure must handle the extracted tenant parameter. By creating a dynamic folder structure under `app/tenant/[slug]`, all tenant dashboard pages can access the organization context.
import React from 'react';
interface TenantPageProps {
params: Promise<{ slug: string }>;
}
async function getTenantData(slug: string) {
// Fetch tenant organization details from database using slug
return { name: slug.toUpperCase(), plan: 'Enterprise' };
}
export default async function TenantDashboard({ params }: TenantPageProps) {
const { slug } = await params;
const tenant = await getTenantData(slug);
return (
<div className="min-h-screen bg-gray-50 p-8">
<div className="max-w-4xl mx-auto bg-white rounded-2xl shadow p-6">
<h1 className="text-3xl font-bold text-gray-900">{tenant.name} Dashboard</h1>
<p className="text-gray-600 mt-2">Active Subdomain Workspace: {slug}.yourplatform.com</p>
<div className="mt-6 p-4 bg-indigo-50 rounded-xl border border-indigo-100">
<span className="font-semibold text-indigo-900">Subscription Plan:</span> {tenant.plan}
</div>
</div>
</div>
);
}
Querying Tenant-Specific Data Safely
When implementing shared-database multi-tenancy, every database query must include a `tenantId` filter to prevent cross-tenant data leaks. Using ORMs like Prisma or Drizzle with middleware extensions automatically appends tenant constraints to all queries.
For database-per-tenant architectures, your application can dynamically instantiate database connection pools based on the resolved subdomain slug stored in your central registry database.
Summary
Implementing multi-tenant architecture with subdomains in Next.js empowers developers to build scalable SaaS platforms with clean organization isolation.
By combining wildcard DNS configuration, Next.js Edge Middleware request rewrites, dynamic App Router folder structures, and secure tenant-filtered queries, you can deliver customized workspaces for every customer efficiently.