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 .env
to copy the example environment variables -
pnpm dev
,yarn dev
ornpm run dev
to start the server- Press
y
when prompted to seed the database
- Press
-
open http://gold.localhost:3000
to access the home page (note: you can also go tosilver.
orbronze.
) -
open http://localhost:3000/admin
to access the admin panel (note: adding/admin
to 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/about
into 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.tsx
in the app directory page.tsx
uses 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 |