How to install multi-tenant-architecture
npx skills add https://github.com/mblode/agent-skills --skill multi-tenant-architectureClaude Code
Cursor
Windsurf
Cline
Full instructions (SKILL.md)
Source of truth, from mblode/agent-skills.
name: multi-tenant-architecture description: Provides architecture guidance for multi-tenant SaaS platforms on Cloudflare or Vercel. Covers platform choice, domain strategy, tenant identification and isolation, subdomain routing, custom domains and SSL, white-label setup, tenant context propagation, PSL submission, and mapping platform limits to pricing plans. Use when building a multi-tenant application or asking "how do I support multiple tenants", "build a white-label platform", "add custom domains", "route tenants by subdomain", or "map limits to plans". For general app folder structure use define-architecture; for scaffolding a new Next.js repo use scaffold-nextjs.
Multi-Tenant Platform Architecture (Cloudflare or Vercel)
- IS: domain strategy, tenant identification and isolation, subdomain routing, custom domains, white-label setup, and plan/limit mapping on Cloudflare or Vercel.
- IS NOT: general app folder structure or module boundaries (use
define-architecture), or scaffolding a new repo (usescaffold-nextjs).
Contents
- Platform dispatch (decide first)
- Workflow (order matters)
- Gotchas
- Output schema
- Pre-commit checklist
- Related skills
Platform dispatch (decide first)
| Signals | Platform | Load |
|---|---|---|
| Tenants run untrusted or per-tenant code; need code-level isolation; edge-first compute on D1/KV/Durable Objects | Cloudflare (Workers for Platforms, dispatch namespaces) | cloudflare-platform.md |
| All tenants share one Next.js codebase; need ISR, React Server Components, managed deploys | Vercel (App Router + Middleware) | vercel-platform.md, then vercel-domains.md for domains |
- Pick one platform and commit; never mix hosting (hybrid routing complexity compounds).
- Load only the chosen platform's references unless explicitly comparing.
- Load psl.md when deciding domain strategy (step 1).
- Load limits-and-quotas.md before mapping limits to pricing (step 8).
agents/openai.yamlis launcher metadata for external runners only; do not load it in normal use.
Workflow (order matters)
- Choose domain strategy
- Dedicated tenant domain, separate from the brand domain, for all subdomains and custom hostnames. Reputation does not isolate: a phishing site on
random.acme.comdamages the whole domain. - Register a separate TLD for tenant workloads (e.g.
acme.appfor tenants,acme.comfor brand). - Untrusted content on sibling subdomains: choose PSL submission, record owner plus timeline. Otherwise record
No PSLwith the cookie-isolation reason. See psl.md. - Start PSL early; review takes weeks.
- Choose tenant identification strategy (pick one primary; offer custom domain as upgrade path)
- Subdomain-based:
tenant.yourdomain.com. Requires wildcard DNS. Simplest at scale. - Custom domain: tenant CNAMEs their own domain to your platform. Best for serious/paying tenants.
- Path-based:
yourdomain.com/tenant-slug. No per-tenant DNS/SSL, but limits branding and complicates cookie isolation.
- Define isolation model
- Cloudflare: per-tenant Workers via dispatch namespaces for untrusted code. Avoid shared-tenant branching unless you fully control code and data.
- Vercel: shared Next.js app with
tenant_idscoping. Middleware resolves tenant from hostname; every query includes tenant context. Postgres RLS for defence-in-depth.
- Route traffic deterministically (tenants never control routing or see each other)
- Cloudflare: platform Worker owns routing: hostname -> tenant id -> dispatch namespace -> tenant Worker. 404 when no mapping.
- Vercel: Middleware extracts hostname, rewrites to a
/domains/[domain]segment; Edge Config for sub-millisecond lookups. 404 when no mapping.
- Pass tenant context through the stack (single authority: Middleware or platform Worker; never trust client-supplied identity)
- Cloudflare: platform Worker resolves the tenant, injects headers/bindings before dispatching to the tenant Worker.
- Vercel: Middleware sets
x-tenant-id,x-tenant-slug,x-tenant-planon forwarded request headers (not the response). Server Components read viaheaders(); API routes read from request headers:// middleware.ts import { NextRequest, NextResponse } from "next/server"; export function middleware(request: NextRequest) { const hostname = request.headers.get("host") ?? ""; const tenant = hostname.split(".")[0]; // resolve from Edge Config/DB in production const requestHeaders = new Headers(request.headers); requestHeaders.set("x-tenant-id", tenant); return NextResponse.next({ request: { headers: requestHeaders } }); }
- Bind only what is needed
- Cloudflare: least-privilege bindings per tenant (DB/storage/limited platform API), no shared global state. New bindings are explicit changes; redeploy to grant access.
- Vercel: Edge Config for tenant config (domain mappings, feature flags, plan info).
@vercel/sdkfor domain management. DB connections scoped bytenant_id, or database-per-tenant (Neon).
- Support custom domains and per-tenant static files
- Provide a DNS target, verify ownership, store the mapping, route by hostname.
- Cloudflare: Cloudflare for SaaS custom hostnames with managed certs. See cloudflare-platform.md.
- Vercel:
@vercel/sdkfor domain CRUD plus automatic Let's Encrypt SSL; wildcard subdomains require Vercel nameservers. See vercel-domains.md. - Custom domains shift reputation to the tenant and create natural user segments (casual on platform domain, serious on their own).
robots.txt,sitemap.xml,llms.txtmust vary by tenant; never serve from/public. Cloudflare: generate in the tenant Worker. Vercel: route handlers under the domain segment (see vercel-platform.md).
- Surface limits as plans
- Map platform limits to pricing tiers; expose in API and UI.
- No long jobs in requests; use queues or workflows.
- See limits-and-quotas.md; re-check official docs before final architecture or pricing decisions.
- Make the API the product
- Everything works over HTTP; the UI is for ops, incidents, billing.
- Platform logic stays in the routing layer (dispatch Worker or Middleware); tenant content serves requests.
- If it only works in the UI, the platform is leaking.
- Extend without breaking boundaries
- Add queues, workflows, or containers as optional modes. Keep routing explicit and isolation intact.
Gotchas
- Tenant headers go on the Middleware request, not the response:
headers()in Server Components reads forwarded request headers, so useNextResponse.next({ request: { headers } })or the tenant id never arrives. - Don't start path-based if custom domains are on the roadmap: migrating later means URL rewrites, cookie changes, and DNS migration.
- Never share DB connections across tenants without RLS or
tenant_idscoping: one missing WHERE clause leaks another tenant's data. - Never block
/.well-known/acme-challenge/*with Middleware or redirects: Let's Encrypt HTTP-01 validation fails and custom-domain SSL never issues. - Edge Config writes are not instant: propagation takes up to 10 seconds, so a "domain connected" UI reading Edge Config immediately shows stale state.
Output schema
# Multi-tenant architecture
## Platform decision
- Platform: Cloudflare | Vercel
- Why this platform:
- Rejected platform and reason:
## Domain map
- Brand domain:
- Tenant domain:
- Tenant subdomains:
- Custom domains:
- PSL decision: Submit | No PSL
- PSL owner/timeline or No PSL reason:
## Routing matrix
| Host pattern | Resolver | Destination | Unknown tenant behavior |
|---|---|---|---|
## Tenant context flow
- Authority: Middleware | platform Worker
- Propagation:
- Server read path:
- Database/API scoping:
## Isolation model
- Compute isolation:
- Data isolation:
- Config/binding isolation:
## Custom-domain lifecycle
1. DNS target:
2. Ownership verification:
3. Certificate provisioning:
4. Routing activation:
5. Removal/failure path:
## Limits-to-plan table
| Limit | Source URL/date | Free | Pro | Enterprise | Enforcement point |
|---|---|---:|---:|---:|---|
## Validation evidence
| Check | Command/source | Expected result | Result |
|---|---|---|---|
Pre-commit checklist
- Platform chosen with documented rationale
- Tenant workloads off the brand domain; PSL decision and timeline set
- Tenant identification strategy chosen; custom-domain upgrade path defined
- Isolation model defined: per-tenant Workers (Cloudflare) or shared-app plus RLS (Vercel)
- Routing authoritative and tenant-blind; dispatch or Middleware handles all traffic
- Tenant context flows through Middleware/platform Worker only; no client-supplied identity trusted
- Custom-domain onboarding defined: DNS target, verification, cert provisioning
- Per-tenant static files (robots.txt, sitemap.xml, llms.txt) served dynamically
- Limits tied to billing; API parity with UI
- Limits snapshot refreshed from official docs and dated in planning notes
Evidence commands (run or mark N/A):
| Check | Evidence |
|---|---|
| Tenant context exists at the boundary | `rg "x-tenant-id |
| Tenant routing works | curl -sI -H "Host: tenant.example.com" <local-or-preview-url> |
| Per-tenant static files are dynamic | curl -s -H "Host: tenant.example.com" <url>/robots.txt and curl -s -H "Host: tenant.example.com" <url>/sitemap.xml |
| Custom-domain verification path exists | API route, SDK call, or platform config path in the plan |
| Platform limits are up to date | official Cloudflare/Vercel URLs with access date in the Limits-to-plan table |
Related skills
define-architecture: folder structure, module contracts, and middleware pipelines for the application itself.scaffold-nextjs: bootstrap the Next.js turborepo before applying these tenancy patterns.optimise-seo: per-tenant sitemaps, canonical URLs, and structured data once routing works.
Related skills
More from mblode/agent-skills and the wider catalog.