In this guide, we'll explore how to implement multi-tenant subdomains for your SaaS application using Django, Next.js, and Nginx. This setup allows each tenant to have their own subdomain (e.g., client1.yourdomain.com, client2.yourdomain.com).
First, let's set up Nginx to handle wildcard subdomains. This configuration will route all subdomains to your application.
server {
listen 80;
server_name *.yourdomain.com;
location / {
proxy_pass <http://localhost:3000>; # Your Next.js frontend
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api {
proxy_pass <http://localhost:8000>; # Your Django backend
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Add a wildcard DNS record in your domain registrar:
Type: A
Host: *
Value: Your-Server-IP
Create a middleware to validate and process subdomains in Django:
# middleware.py
from django.http import HttpResponseForbidden
class SubdomainMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
host = request.get_host().split(':')[0]
subdomain = host.split('.')[0]
if subdomain == 'www':
return HttpResponseForbidden()
# Store subdomain in request for later use
request.subdomain = subdomain
# Validate if subdomain exists in your tenant database
try:
tenant = Tenant.objects.get(subdomain=subdomain)
request.tenant = tenant
except Tenant.DoesNotExist:
return HttpResponseForbidden()
response = self.get_response(request)
return response
Add the middleware to your Django settings:
MIDDLEWARE = [
# ...
'your_app.middleware.SubdomainMiddleware',
# ...
]
Create a middleware.ts file in your Next.js project:
// middleware.ts
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') || ''
const subdomain = hostname.split('.')[0]
// Skip for static files and API routes
if (url.pathname.startsWith('/_next') ||
url.pathname.startsWith('/api') ||
url.pathname.includes('.')) {
return NextResponse.next()
}
// Validate subdomain
if (subdomain === 'www' || subdomain === 'yourdomain') {
return NextResponse.redirect(new URL('/', url))
}
try {
// You can make an API call to validate the subdomain
// Store subdomain information in request headers
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-subdomain', subdomain)
return NextResponse.next({
request: {
headers: requestHeaders,
},
})
} catch (error) {
return NextResponse.redirect(new URL('/404', url))
}
}
export const config = {
matcher: '/:path*',
}
In Django views:
def my_view(request):
tenant = request.tenant # Set by middleware
# Use tenant information to filter data
return JsonResponse({'data': f'Data for {tenant.name}'})
In Next.js components: