Architecture
App segments
The app is split into three main areas:
(frontend)— Tenant sites. Each request whose path is not admin, api, or docs is rewritten so the host becomes the first segment:/:tenantDomain/:path*. All tenant pages and data are scoped to that tenant.(payload)— Admin panel and Payload API. Served at/adminand under/apias needed. Not subject to tenant rewrites./docs— Documentation (Nextra). Excluded from tenant rewrites so the same docs are available regardless of host.
Multi-tenancy (domain → tenant)
Tenancy is host-based. The incoming request host (e.g. mysite.com or localhost:3000) is used to find a tenant document in the tenants collection (by the domain field). That tenant is then used for layout, SEO, and data fetching.
Flow:
- Request arrives (e.g.
GET https://mysite.com/). - Rewrite rule in
next.config.mjsapplies only when the path does not matchadmin,api, ordocs. The host is captured and the request is rewritten to/:tenantDomain/:path*(e.g.mysite.com→ tenantDomain). - The frontend layout or data layer looks up the tenant by this domain (e.g. via
getPayload().find({ collection: 'tenants', where: { domain: { equals: tenantDomain } } })). - The tenant document is used for theme, site name, and any tenant-scoped queries. Pages and posts are filtered by tenant where applicable.
Resolving the tenant
Tenant resolution typically happens in:
- Layout or root — e.g.
[tenantDomain]/layout.tsxreceivestenantDomainfrom the route params (which were set by the rewrite). It may callgetPayload()and query the tenants collection withoverrideAccess: falseif a user is passed, then provide the tenant to the tree (e.g. TenantProvider, or props). - SEO — Metadata helpers use the tenant (site name, default title, OG image) when generating titles and Open Graph tags.
- Data fetching — Pages and posts are often filtered by
tenant: equals tenantIdso each tenant only sees its own content.
The rewrite configuration lives in next.config.mjs; the tenants collection and access rules are in src/payload.