Ship's Log
One Codebase, Many Ships: Multi-Tenant CMS Architecture
The pitch was simple: one Payload CMS instance, one Next.js frontend, unlimited tenants. Each tenant gets their own domain, their own theme, their own content. No separate deployments. No config hell. One ship, many flags.
Tenant resolution
Every request starts with a hostname lookup. The middleware reads the incoming domain, matches it against the tenant domains table, and injects the tenant context into the request. From there, every API query filters by tenant ID. Simple, fast, impossible to leak data across tenants.
Theming without Tailwind
We use three layers: CSS custom properties for colors and fonts (injected at :root from tenant config), data attributes on <html> for shape variants like border radius, and component-level prop variants for structural differences. Zero utility classes. CSS Modules everywhere. It’s more code upfront but the runtime is clean.
Was it worth it?
Absolutely. Adding a new tenant is now a seed file entry and a DNS record. No new deployments, no new repos, no DevOps tickets. The fleet grows but the maintenance doesn’t.