Blog post
How I built a multi-tenant CMS platform using Payload CMS 3 embedded in Next.js 15 — tenant isolation, custom domains, theme presets, and a cyberpunk design system, all running on a single ARM box.
One process. One deployment. Multiple websites. This platform serves portfolio sites, blogs, literary works, and business pages from a single codebase. Each tenant gets isolated content, custom domains, and independent theming.
This post covers the architecture decisions, the features each tenant gets, and the lessons that cost real hours to learn.
Payload CMS 3 runs inside Next.js 15 — not alongside it, inside the same Node.js process. When a page needs data, it calls the CMS directly. No HTTP round-trip, no serialization overhead, no separate backend to deploy and maintain. This single decision removed an API gateway, a separate deployment pipeline, and about 200ms of latency from every page render.
The stack: React 19 with Server Components by default, Tailwind CSS with a cyberpunk neon palette, Lexical rich text with custom rendering, and Docker Compose for production (reverse proxy, app, database, analytics, backups — five containers total).
Every tenant is fully isolated — content, settings, media, analytics. From the user’s perspective, each site is independent. Under the hood, one codebase handles all of them.
CSS variablesContent editors write in a visual rich text editor — headings, bold, lists, images, code blocks, video embeds, callout boxes. No Markdown syntax to learn. The rendering layer automatically applies typography enhancements: drop caps on opening paragraphs, gradient underlines on section headings, accent-colored list markers, syntax-highlighted code with copy buttons, and responsive image lightboxes.
The goal was simple: a user writes a heading and a paragraph, and it already looks polished. No custom CSS classes, no special markup. The structure of the content itself drives the visual treatment.
The admin panel is customized to match the cyberpunk design system — branded dashboard, content status widgets, and a tenant onboarding wizard that walks new site owners through setup in four steps. Super-admins see usage dashboards per tenant: content counts, media usage, active domains.
One feature I’m particularly happy with: the locale transfer tool. Export any content entry as JSON, translate it externally (or with AI), and re-import it into the target locale. It handles the edge cases that make Payload’s i18n tricky — array ID conflicts, nested rich text fields, mixed localized and non-localized data.
The embedded CMS pattern is the best architectural decision in this project. No separate API, no auth synchronization, no CORS headaches. But it comes with a cost: every feature you build must be tenant-aware from the start. Bolting on multi-tenancy after the fact means touching every data-fetching function, every access control rule, every hook.
Other lessons that cost hours:
The platform is in production, serving real content. 13 content collections, 560+ unit tests, bilingual support, seven site type templates, and a deployment pipeline that fits in a Makefile. It handles everything from blog posts to PDF resume generation to headless API responses.
If you want full control over your content stack — the CMS, the admin panel, the deployment, the analytics — the embedded CMS pattern with multi-tenancy is a path worth exploring. Not the only one. But one that works in production, today.