---
title: "Multi-Tenant Development Environment: 4-Step Local Guide"
slug: "multi-tenant-dev-environment-nextjs-payload"
published: "2025-12-16"
updated: "2025-12-21"
categories:
  - "Next.js"
tags:
  - "multi-tenant development environment"
  - "local domain mapping"
  - "Next.js middleware"
  - "Payload CMS"
  - "tenant routing"
  - "hosts file setup"
  - "preview mode"
  - "domain-based routing"
  - "getDevelopmentDomain"
  - "SEO testing for tenants"
  - "local development domains"
llm-intent: "how-to"
audience-level: "intermediate"
llm-purpose: "Multi-tenant development environment: map local domains and configure Next.js middleware with Payload CMS to test tenant routing, auth, and SEO. Start now."
llm-prereqs:
  - "Next.js"
  - "Payload CMS"
  - "TypeScript"
  - "NextAuth"
  - "pnpm"
  - "Vercel"
  - "nginx"
---

**Summary Triples**
- (Multi-Tenant Development Environment: 4-Step Local Guide, expresses-intent, how-to)
- (Multi-Tenant Development Environment: 4-Step Local Guide, covers-topic, multi-tenant development environment)
- (Multi-Tenant Development Environment: 4-Step Local Guide, provides-guidance-for, Multi-tenant development environment: map local domains and configure Next.js middleware with Payload CMS to test tenant routing, auth, and SEO. Start now.)

### {GOAL}
Multi-tenant development environment: map local domains and configure Next.js middleware with Payload CMS to test tenant routing, auth, and SEO. Start now.

### {PREREQS}
- Next.js
- Payload CMS
- TypeScript
- NextAuth
- pnpm
- Vercel
- nginx

### {STEPS}
1. Map production domains locally
2. Implement Next.js middleware
3. Create development domain utility
4. Start and configure dev server
5. Verify routing and tenant behavior
6. Troubleshoot common issues
7. Harden for production deployment

<!-- llm:goal="Multi-tenant development environment: map local domains and configure Next.js middleware with Payload CMS to test tenant routing, auth, and SEO. Start now." -->
<!-- llm:prereq="Next.js" -->
<!-- llm:prereq="Payload CMS" -->
<!-- llm:prereq="TypeScript" -->
<!-- llm:prereq="NextAuth" -->
<!-- llm:prereq="pnpm" -->
<!-- llm:prereq="Vercel" -->
<!-- llm:prereq="nginx" -->

# Multi-Tenant Development Environment: 4-Step Local Guide
> Multi-tenant development environment: map local domains and configure Next.js middleware with Payload CMS to test tenant routing, auth, and SEO. Start now.
Matija Žiberna · 2025-12-16

> **Context:** This guide is part of the [Multi-Tenant Setup series](/blog/production-ready-multi-tenant-nextjs-payload).

This guide explains how to set up a local development environment that mimics production for multi-tenant applications using Payload CMS and Next.js.

## Overview

When building multi-tenant applications, you need to test how different tenants (domains) behave in production. Instead of using subdirectories or query parameters, this setup allows you to:

- Access different tenants via local domains (e.g., `tenant-a.local`, `tenant-b.local`)
- Test production-like URL structures locally
- Ensure middleware, routing, and tenant resolution work exactly like production
- Debug domain-specific features without deploying

## Quick Start Checklist

1. ✅ Edit `/etc/hosts` → add local domains
2. ✅ Start dev server on port 80 (or configure port)
3. ✅ Access `http://tenant-a.local` in browser
4. ✅ Verify middleware rewrites in Network tab

## Step 1: Configure Local Domain Mapping

First, map your production domains to local development domains using your system's hosts file.

### Edit /etc/hosts File

```bash
sudo nano /etc/hosts
```

Add these entries to map local domains to localhost:

```hosts
# Multi-tenant development domains
127.0.0.1 tenant-a.local
127.0.0.1 tenant-b.local
```

### Why This Works

- These entries tell your system to resolve `tenant-a.local` and `tenant-b.local` to `127.0.0.1` (localhost)
- Your browser will send the host header with the domain name to your local Next.js server
- This allows your middleware to identify tenants by hostname, just like in production

## Step 2: Configure Next.js Middleware

## Step 2: Configure Next.js Middleware

The middleware handles tenant routing based on the hostname. Here's a simplified version for development:

```typescript
// Simplified middleware - for full production implementation, see:
// [Production-Ready Multi-Tenant Setup - Middleware Section](/blog/production-ready-multi-tenant-nextjs-payload#middleware-implementation)

const tenantMap = {
  'tenant-a.local': 'tenant-a', // Maps local domain to tenant slug
  'tenant-b.local': 'tenant-b',
};

// ... logic to rewrite request to /[tenant]/...
```

For the complete production-ready middleware with authentication, preview mode, and diverse domain handling, see the [full implementation guide](/blog/production-ready-multi-tenant-nextjs-payload#middleware-implementation).

### How It Works

1. **Hostname Detection**: The middleware extracts the hostname from the request headers
2. **Tenant Mapping**: Maps hostnames to tenant slugs using the `tenantMap` object
3. **URL Rewriting**: Rewrites the request to the internal Next.js route structure
4. **Preview Support**: Handles both production and preview modes

### Key Features

- **Production Mimicry**: Uses the same hostname detection as production
- **Multi-Protocol Support**: Works with both `.local` and production domains
- **Preview Mode**: Supports draft preview functionality
- **Static Asset Handling**: Excludes static assets and API routes from tenant routing

## Step 3: Development Domain Utility

Create a utility to map production domains to development domains when running locally.

### File: src/payload/utilities/getDevelopmentDomain.ts

```typescript
/**
 * Map production domains to development domains for local testing
 */
export const getDevelopmentDomain = (domain: string): string => {
  const devDomainMap: Record<string, string> = {
    'tenant-a.vercel.app': 'tenant-a.local',
    'tenant-b.vercel.app': 'tenant-b.local',
  };

  return devDomainMap[domain] || domain;
};
```

### Usage in SEO and URL Generation

This utility is used in places where URLs need to be generated for the current environment. The `getDevelopmentDomain()` utility maps production domains to local test domains. See [Development Environment Guide](/blog/multi-tenant-dev-environment-nextjs-payload#development-domain-utility) for implementation.

Example usage in SEO utilities:

```typescript
// Example usage in SEO utilities
const isDevelopment = process.env.NODE_ENV === 'development';
const finalDomain = isDevelopment ? getDevelopmentDomain(tenant.domain) : tenant.domain;
const protocol = isDevelopment ? 'http' : 'https';
const baseUrl = `${protocol}://${finalDomain}`;
```

## Step 4: Development Server Configuration

When starting your development server, make sure it's configured to handle the custom domains.

### Start Development Server

```bash
# Using pnpm (as per project conventions)
pnpm dev
```

The server typically starts on port 3000, but you can configure it in your package.json:

```json
{
  "scripts": {
    "dev": "next dev -p 80"
  }
}
```

### Why Port 80?

- Running on port 80 allows you to access the domains without specifying a port
- `http://tenant-a.local` instead of `http://tenant-a.local:3000`
- More closely mimics production where sites run on standard HTTP/HTTPS ports

## Testing the Setup

### 1. Verify Domain Resolution

Open these URLs in your browser:

- `http://tenant-a.local` - Should show the Tenant A
- `http://tenant-b.local` - Should show the Tenant B
- `http://localhost:3000` - Should work (fallback behavior)

### 2. Check Middleware Behavior

Look at the network requests in your browser dev tools:

- Request URL: `http://tenant-a.local/`
- Rewritten to: `http://localhost:3000/tenant-slugs/tenant-a/home`
- The response should show content for the Tenant A

### 3. Test Production Features

Test features that depend on tenant identification:

- **SEO Metadata**: Check that titles include the correct tenant name. For detailed SEO testing with tenant-specific metadata and OG images, see [Multi-Tenant SEO with Payload & Next.js](/blog/multi-tenant-seo-payload-nextjs-guide#testing-and-validation)
- **Branding**: Verify tenant-specific logos, colors, and content
- **Routing**: Ensure internal links use the correct domain
- **API Calls**: Confirm API requests include the correct tenant context

## Troubleshooting

### Domain Not Resolving

**Issue**: Browser shows "Server not found" or DNS error

**Solution**:
1. Verify your `/etc/hosts` entries are correct
2. Flush your DNS cache:
   ```bash
   # On macOS
   sudo dscacheutil -flushcache
   
   # On Linux
   sudo systemd-resolve --flush-caches
   ```
3. Try accessing `http://127.0.0.1` to ensure the server is running

### Middleware Not Triggering

**Issue**: Tenant routing not working, showing default content

**Solution**:
1. Check the middleware matcher pattern
2. Verify the hostname is being correctly extracted
3. Add console logging to the middleware:
   ```typescript
   console.log('Middleware called with hostname:', hostname);
   ```

### Mixed Content Errors

**Issue**: HTTPS resources on HTTP development domains

**Solution**:
- Use the `getDevelopmentDomain` utility for all URL generation
- Ensure all internal links use protocol-relative URLs
- Configure your CMS to generate correct URLs for the environment

### Port Conflicts

**Issue**: Port 80 requires sudo or is already in use

**Solution**:
1. Use a different port and update your domains:
   ```hosts
   127.0.0.1 tenant-a.local:3000
   127.0.0.1 tenant-b.local:3000
   ```
2. Or use a proxy like nginx to forward port 80 to your app

## Production Deployment Considerations

When deploying to production:

1. **Update Middleware**: Ensure only production domains are in the tenant map
2. **Update DNS**: Configure actual domain DNS records
3. **SSL Certificates**: Set up HTTPS for all tenant domains
4. **Environment Variables**: Configure production-specific settings
5. **Monitor Logs**: Watch for any development-specific code paths

For comprehensive production setup including hardcoded tenant configuration, edge middleware optimization, and Payload CMS multi-tenant plugin configuration, see [Production-Ready Multi-Tenant Setup](/blog/production-ready-multi-tenant-nextjs-payload).

## Benefits of This Approach

### Development Advantages

1. **Realistic Testing**: Test production-like URL structures locally
2. **Tenant Isolation**: Clear separation between tenant data and logic
3. **No Workarounds**: No need for subdirectories or query parameters
4. **SEO Testing**: Verify domain-specific SEO metadata (see [SEO Testing procedures](/blog/multi-tenant-seo-payload-nextjs-guide#testing-and-validation))
5. **Feature Parity**: Same code path as production

### Maintenance Benefits

1. **Single Codebase**: Same middleware handles dev and production
2. **Environment-Aware**: Automatic switching between dev and prod domains
3. **Type Safety**: Full TypeScript support with proper interfaces
4. **Debugging**: Easy to debug tenant-specific issues locally

## Alternative Approaches

### Subdirectory Approach

Instead of domain-based routing, you could use subdirectories:

- `http://localhost:3000/tenant-a/...`
- `http://localhost:3000/tenant-b/...`

**Pros**: No hosts file modification needed
**Cons**: Doesn't mimic production URL structure

### Query Parameter Approach

Use query parameters to specify tenants:

- `http://localhost:3000/?tenant=tenant-a`
- `http://localhost:3000/?tenant=tenant-b`

**Pros**: Simplest setup
**Cons**: Very different from production, SEO issues

## Conclusion

This development environment setup provides a production-like experience for multi-tenant applications without complex infrastructure. By using local domain mapping and intelligent middleware, you can:

- Test tenant-specific features realistically
- Debug production-like URL structures
- Maintain a single codebase for all environments
- Ensure seamless transition from development to production

The key insight is that by mapping local domains to localhost and using middleware to handle routing, you create a development environment that behaves identically to production while keeping the setup simple and maintainable.