Skip to main content
Jose Hidalgo | Senior Full-Stack EngineerFull-Stack Engineer | Distributed Systems
HomeProjectsBlogStatusCV
ENES
Jose Hidalgo | Senior Full-Stack Engineer

Full-Stack Engineer | Distributed Systems

Engineered with clarity and intention.

Navigation

  • Home
  • Projects
  • Blog
  • Status
  • CV

Connect

GitHubLinkedInEmail
© 2026 Jose Hidalgo. All rights reserved.Back to top ↑
← Back to blog

Blog post

Building a Multi-Tenant Portfolio Platform with Payload CMS and Next.js

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.

Mar 18, 2026
2 min read3 views
Next.jsPayload CMSMulti-TenantReactDockerTypeScript
ShareXLinkedIn

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.

Why an Embedded CMS

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).

What Each Tenant Gets

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.

  • Custom domains with DNS verification and automatic SSL
  • Eight theme presets — cyberpunk, minimal, editorial, corporate, medical, creative, nature, elegant — plus full custom branding via CSS variables
  • Interactive backgrounds — three canvas-based animations suggested per site type
  • Two homepage layouts — a default portfolio view with timeline and project grid, or a product-focused layout with hero, features, testimonials, and FAQ
  • Per-tenant analytics — no third-party tracking
  • Bilingual content (EN + ES) with locale fallback chains and a bulk translation workflow
  • Toggleable sections — services, testimonials, team members, FAQs can be enabled or disabled per tenant

Rich Content That Looks Good by Default

Content 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 Experience

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.

Lessons Learned

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:

  • Payload’s Globals are singletons — they can’t hold per-tenant data. Convert them to tenant-scoped collections early, not after you’ve referenced them everywhere.
  • Schema push tools are great for prototyping. In production, they can silently skip changes. Use proper migrations once you’re past the prototype stage.
  • Payload uses CSS layers for its admin styles. If you wrap your overrides in a layer too, they silently lose specificity. Keep custom admin CSS unlayered. I lost a full day to this.
  • Put all CI logic in a Makefile, not in CI-platform YAML. Switching providers means rewriting YAML wrappers, not build logic. And you can run the exact same checks locally.

Where It Stands

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.