Multi-tenant SaaS (Software as a Service) platforms allow multiple clients (tenants) to share a single application while maintaining separation of their data and configurations. In this guide, we will explore how to build a multi-tenant SaaS platform using Next.js, leveraging its middleware and dynamic routing capabilities.
Key Concepts in Multi-Tenancy
1. Tenant Identification
Each request must be associated with a specific tenant. This can be done through:
Subdomains (e.g.,
tenant1.example.com
)Path-based routing (e.g.,
example.com/tenant1
)Custom headers (e.g.,
X-Tenant-ID
)
2. Tenant Isolation
Ensure data isolation between tenants. This involves:
Separate database schemas or collections per tenant
Scoped configurations for themes, permissions, or features
3. Custom Branding
Allow tenants to customize the application, such as uploading logos or changing colors, while sharing the same codebase.
Setting Up Dynamic Routing in Next.js
Dynamic routing in Next.js enables tenant-specific pages. Let’s implement a path-based routing structure.
Folder Structure
src
├── app
│ ├── [tenant]
│ │ ├── dashboard
│ │ │ └── page.tsx
│ │ ├── settings
│ │ │ └── page.tsx
│ ├── middleware.ts
In this structure:
[tenant]
is a dynamic segment that matches tenant identifiers.Pages like
/[tenant]/dashboard
and/[tenant]/settings
serve tenant-specific content.
Dynamic Routes
Create tenant-specific pages using dynamic segments:
import { useRouter } from 'next/navigation';
export default function DashboardPage() {
const router = useRouter();
const { tenant } = router.query;
return (
<div>
<h1>Welcome, {tenant}</h1>
<p>This is the dashboard for tenant: {tenant}</p>
</div>
);
}
Using Middleware for Tenant Identification
Middleware in Next.js can intercept requests and perform tasks such as tenant identification and redirection.
Middleware Example
Create a middleware.ts
file in the app
directory:
import { NextResponse } from 'next/server';
export function middleware(req) {
const url = req.nextUrl;
const tenant = url.pathname.split('/')[1];
if (!tenant) {
// Redirect to a generic landing page if no tenant is specified
return NextResponse.redirect(new URL('/landing', req.url));
}
// Add tenant to the request headers for downstream processing
req.headers.set('x-tenant-id', tenant);
return NextResponse.next();
}
export const config = {
matcher: '/:path*',
};
This middleware:
Extracts the tenant from the URL path.
Redirects users without a tenant to a landing page.
Adds the tenant identifier to the request headers for further use.
Data Isolation for Tenants
Database Design
Single Database with Tenant ID: Add a
tenantId
field to each record.Separate Databases or Schemas: Use a unique database or schema for each tenant.
Fetching Tenant Data
Modify API calls to include tenant-specific filters:
export default async function handler(req, res) {
const tenantId = req.headers['x-tenant-id'];
// Fetch data scoped to the tenant
const data = await fetchDataForTenant(tenantId);
res.status(200).json(data);
}
Custom Branding and Configurations
Allow tenants to upload their own assets or set configurations.
Example: Tenant Themes
Define Theme Configurations
const tenantThemes = {
tenant1: { primaryColor: '#ff0000', logo: '/logos/tenant1.png' },
tenant2: { primaryColor: '#0000ff', logo: '/logos/tenant2.png' },
};
Apply Themes in Components
import { useRouter } from 'next/router';
export default function Header() {
const router = useRouter();
const { tenant } = router.query;
const theme = tenantThemes[tenant] || tenantThemes['default'];
return (
<header style={{ backgroundColor: theme.primaryColor }}>
<img src={theme.logo} alt={`${tenant} logo`} />
</header>
);
}
Benefits of Using Next.js for Multi-Tenancy
Simplified Routing: Dynamic routing makes tenant-specific URLs easy to implement.
Built-In Middleware: Next.js middleware simplifies tenant identification and request handling.
Scalable Architecture: ISR and API routes support high traffic while maintaining performance.
Customizable UI: Tenant-specific branding and themes enhance the user experience.
Conclusion
Building a multi-tenant SaaS platform with Next.js is efficient and scalable. By leveraging middleware and dynamic routing, you can create tenant-specific experiences while maintaining a shared codebase. With proper database design and configuration management, you can ensure data isolation and customization for each tenant, paving the way for a robust and flexible SaaS application.