This repo demonstrates how to implement multi-tenancy and localization using:
- Payload
@payloadcms/plugin-multi-tenant- A single Next.js frontend app
It allows you to serve multiple tenants (e.g. gold.localhost, silver.localhost) with localized URLs (e.g. /en, /fr) from one codebase.
To spin up this example locally, follow these steps:
-
cp .env.example .envto copy the example environment variables -
pnpm dev,yarn devornpm run devto start the server- Press
ywhen prompted to seed the database
- Press
-
open http://gold.localhost:3000to access the home page (note: you can also go tosilver.orbronze.) -
open http://localhost:3000/adminto access the admin panel (note: adding/adminto the custom domains will also take you to the admin panel)
Login with email [email protected] and password demo
For the domain portion of the example to function properly, you will need to add the following entries to your system's /etc/hosts file:
127.0.0.1 gold.localhost silver.localhost bronze.localhost
File: payload.config.ts
Localization setup:
The localization field defines supported locales (e.g. ['en', 'fr']) and default locale.
This enables Payload collections and globals to support localized fields and content.
localization: {
locales: ['en', 'fr'],
defaultLocale: 'en',
fallback: true,
},Multi-Tenant Plugin
The @payloadcms/plugin-multi-tenant plugin is installed and passed to Payload via plugins.
plugins: [
multiTenant({
// plugin options (e.g., tenant collection, defaults)
}),
],File: next.config.js
This file rewrites incoming URLs based on domain and path to match your app directory routing structure.
async rewrites() {
return [
{
source: '/((?!admin|api)):locale/:path*',
destination: '/:tenant/:locale/:path*',
has: [
{
type: 'host',
value: '(?<tenant>.*)',
},
],
},
]
},- Extracts the tenant from the domain (e.g.
gold.localhost) - Restructures URLs like
/en/aboutinto internal paths like/:tenant/:locale/:path* - Allows your app directory to cleanly handle tenants and locales via nested dynamic routes
Folder: /app
/app/[tenant]– captures tenant from rewritten URL/app/[tenant]/[locale]– captures locale from URL/app/[tenant]/[locale]/[...slug]– handles all pages under the locale
This allows paths like to be transformed into the app structure:
http://gold.localhost:3000/en/about
→ /app/gold/en/about → tenant = "gold", locale = "en", slug = "about"File: /app/[tenant]/[locale]/[...slug]/page.tsx
This is the main entry point for rendering localized tenant content. Within this file it does the following:
- Reads tenant, locale, and slug from the URL
- Fetches tenant-specific config and localized content from Payload
- Renders dynamic pages for each tenant/locale combo
You can customize this file to fetch and render any kind of tenant-aware, locale-aware data (e.g. pages, navigation, layout).
- A user visits
http://gold.localhost:3000/fr/about - The Next.js rewrite rule extracts:
- Tenant from domain (
gold) - Locale and path from URL (
fr,about)
- Tenant from domain (
- The URL is rewritten internally to
/gold/fr/about - Next.js matches the route via
/app/[tenant]/[locale]/[...slug]/page.tsxin the app directory page.tsxuses the tenant, locale, and slug params to fetch content from Payload- The requested localized content for the correct tenant is rendered
| File / Folder | Purpose |
|---|---|
payload.config.ts |
Sets up localization + multi-tenant plugin |
next.config.js |
Rewrites URLs based on domain and locale |
/app/[tenant]/[locale]/[...slug]/page.tsx |
Fetches and renders tenant + locale-specific content |
/app/[tenant]/[locale]/[...slug] |
Directory structure that mirrors rewritten URL paths |