Next.js
Multitenancy
Implementing multi-tenant architecture using Next.js Middleware and dynamic routing.
Routing Middleware: Next.js Middleware intercepts requests and rewrites them based on the hostname or subdomain to a dynamic tenant route.
middleware.ts
TS
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const url = request.nextUrl
// Get hostname of request (e.g. demo.vercel.pub, demo.localhost:3000)
let hostname = request.headers.get('host')!
// Allow local routing
hostname = hostname.replace('.localhost:3000', '')
const searchParams = request.nextUrl.searchParams.toString()
const path = `${url.pathname}${searchParams.length > 0 ? `?${searchParams}` : ''}`
// Rewrite for subdomains or custom domains
if (hostname !== 'localhost:3000' && hostname !== 'vercel.pub') {
return NextResponse.rewrite(new URL(`/${hostname}${path}`, request.url))
}
return NextResponse.next()
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
Tenant Dynamic Route: A dynamic route catch-all that receives the tenant identifier (e.g., domain) as a parameter and fetches tenant-specific data.
app/[tenant]/page.tsx
TSX
import { notFound } from 'next/navigation'
async function getTenantData(domain: string) {
// Fetch tenant data from database based on domain
const res = await fetch(`https://api.example.com/tenants/${domain}`)
if (!res.ok) return null
return res.json()
}
export default async function TenantPage({ params }: { params: { tenant: string } }) {
const tenant = await getTenantData(params.tenant)
if (!tenant) {
notFound()
}
return (
<div>
<h1>Welcome to {tenant.name}</h1>
<p>This is a multi-tenant page mapped to the domain: {params.tenant}</p>
</div>
)
}