Building a Multi-Tenant SaaS Platform with Next.js

Building a Multi-Tenant SaaS Platform with Next.js

·

3 min read

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:

  1. Extracts the tenant from the URL path.

  2. Redirects users without a tenant to a landing page.

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

  1. Simplified Routing: Dynamic routing makes tenant-specific URLs easy to implement.

  2. Built-In Middleware: Next.js middleware simplifies tenant identification and request handling.

  3. Scalable Architecture: ISR and API routes support high traffic while maintaining performance.

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