Setting Up Multi-Tenant Subdomains with Django and Next.js

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).

1. Configuring Nginx with Wildcard Subdomains

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;
    }
}

2. DNS Configuration

Add a wildcard DNS record in your domain registrar:

Type: A
Host: *
Value: Your-Server-IP

3. Django Middleware for Subdomain Validation

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',
    # ...
]

4. Next.js Middleware for Subdomain Handling

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*',
}

5. Using Subdomain Information in Your Application

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: