Next.js Multi-Tenant SaaS: Subdomain Routing Middleware


Get the Next.js weekly drop
Free weekly notes on App Router, auth, and frontend stacks that ship. No spam—just what's worth your inbox.
By subscribing you agree to our Privacy Policy.
Multi-tenant SaaS means one Next.js app serves many customers (tenants)—each with isolated data and, often, its own subdomain like acme.yourapp.com. You share infrastructure; you never share tenant data by accident.
This guide covers subdomain routing and middleware for App Router products: how to resolve the tenant, keep routes clean, and keep isolation honest.
This guide demonstrates how to build a multi-tenant application using Next.js with subdomain-based tenant separation, like tenant1.yourdomain.com, tenant2.yourdomain.com, etc.
Organize your project like so:
├── app/
│ ├── [subdomain]/ # Tenant-specific routes
│ │ ├── page.tsx # Main page for the tenant
│ │ └── layout.tsx # Layout wrapper for each tenant
│ └── middleware.ts # Middleware for subdomain handling
├── public/
│ └── images/tenant1/ # Static assets per tenant
├── package.json # Project config
├── .env # Environment variablesThe middleware identifies tenants from the subdomain, handles auth, and rewrites routes dynamically.
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { domain } from "@/lib/env";
export function middleware(request: NextRequest) {
const hostname = request.headers.get("host") || "";
const url = request.nextUrl.clone();
const tenantSlug = hostname.split(".")[0];
if (hostname === domain || hostname === domain.split(":")[0]) {
return NextResponse.redirect(new URL(`http://login.${domain}`));
}
if (tenantSlug === "login") {
if (url.pathname !== "/" && url.pathname !== "/api/login") {
return NextResponse.redirect(new URL("/", request.url));
}
return NextResponse.next();
}
const userId = request.cookies.get("userId")?.value;
if (!userId && tenantSlug !== "login") {
return NextResponse.redirect(new URL(`http://login.${domain}/`, request.url));
}
url.pathname = `/${tenantSlug}${url.pathname}`;
return NextResponse.rewrite(url);
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};Host header.login.yourdomain.com.app/[subdomain]/.userId) for simple auth handling.Point both your root domain and subdomains to the same server.
Define your domain in .env:
DOMAIN=yourdomain.comUse local DNS mapping (e.g., /etc/hosts) to simulate subdomains:
127.0.0.1 login.localhost tenant1.localhost tenant2.localhostCreate a login page on the login subdomain. Upon successful login, set a userId cookie.
⚠️ Note: In practice, JWTs or secure cookies should be used. Since cross-domain cookies can be problematic, you may pass tokens via query params as a fallback.
Build tenant-specific components inside the app/[subdomain]/ directory.
🧠 This approach is based on a personal project and is not a full production-ready solution.
For robust cross-subdomain authentication and domain aliasing, consult more comprehensive guides or frameworks tailored for SaaS.
Have feedback or want to contribute? Reach out on GitHub or share this post with someone building a SaaS product.