Multi-tenancy is one of those architectural concepts you hear tossed around a lot in SaaS circles, but when you really get it right, it feels like a superpower.
In my latest project, I built deep-level multi-tenancy into a Next.js application, and the results were nothing short of game-changing:
- One codebase serving multiple schools
- Data isolation for security and privacy
- Scalability without the chaos of separate deployments
Here’s how I did it, and how you can too.
Why Multi-Tenancy Matters
For a SaaS product, multi-tenancy means that a single application instance serves multiple clients (“tenants”), with each tenant’s data, settings, and user sessions kept separate.
The benefits:
- Lower hosting and maintenance costs
- Faster feature rollouts (update once, all tenants benefit)
- Easier scaling compared to managing multiple deployments
In my case, each school using the platform has its own subdomain, isolated authentication, onboarding process, and dashboard.
Why Next.js Was a Perfect Fit
Next.js gives you the exact tools needed for deep multi-tenancy:
- Dynamic routing → Perfect for tenant-specific paths
- Middleware → Great for intercepting requests and directing them to the right tenant context before rendering
- API Routes → Handle tenant-specific API calls with ease
- SSR/SSG flexibility → Customize pages per tenant
My Deep-Level Multi-Tenancy Setup
Here’s a quick peek at my project structure:
This folder structure allows me to scope every route, page, and feature to a specific subdomain (tenant).
The Middleware Magic
The heart of this setup is my Next.js middleware, which:
- Extracts the tenant subdomain from the request
- Handles local dev and production environments (even Vercel preview URLs)
- Manages authentication & onboarding flows per tenant
- Rewrites URLs so each request is served from the correct tenant route
Here’s the key part of my middleware logic:
function extractSubdomain(request: NextRequest): string | null {
const url = request.url;
const host = request.headers.get("host") || "";
const hostname = host.split(":")[0];
// Localhost handling
if (url.includes("localhost")) {
const match = url.match(/http:\/\/([^.]+)\.localhost/);
return match?.[1] || hostname.split(".")[0];
}
// Production handling
const rootDomainFormatted = rootDomain.split(":")[0];
const isSubdomain =
hostname !== rootDomainFormatted &&
hostname !== `www.${rootDomainFormatted}` &&
hostname.endsWith(`.${rootDomainFormatted}`);
return isSubdomain ? hostname.replace(`.${rootDomainFormatted}`, "") : null;
}
And the rewrite step that ties it all together:
if (subdomain) {
const redirectUrl = new URL(`/tenat/${subdomain}${pathname}`, request.url);
return NextResponse.rewrite(redirectUrl);
}
This means:
-
school1.example.com/dashboard
→ internally becomes/tenat/school1/dashboard
-
school2.example.com/login
→ becomes/tenat/school2/login
All while keeping the public-facing URL clean.
Guiding You to Set This Up Yourself
If you want to replicate this deep-level multi-tenancy approach:
-
Plan your folder structure under
/app/[subdomain]
or/app/tenant/[subdomain]
so that tenant-specific routes are cleanly separated. - Write middleware that:
- Extracts the subdomain
- Handles special cases (localhost, staging domains, etc.)
- Decides what to do based on user session and onboarding state
-
Use
NextResponse.rewrite
to send the request to the correct tenant-specific route without changing the public URL. -
Integrate authentication per tenant, your session should store the tenant identifier (
schema_name
in my case) so you can easily redirect users post-login. - Test across environments, production, staging, and local dev, to make sure subdomain routing behaves consistently.
-
Use
Why This Was Worth It
The result?
- Isolated environments for each school without deploying multiple apps
- Centralized updates: push new features once, all tenants get them
- Secure, predictable routing thanks to middleware control
Multi-tenancy isn’t just about being clever, it’s about scaling right. And when done well, it turns complexity into something manageable, even elegant.
Have you tried multi-tenancy before? What was the toughest part, routing, auth, or database isolation?
#Nextjs #SaaS #Multitenancy #WebDevelopment #DevExperience
Top comments (0)