multi-tenant-architecture

npx skills add mblode/agent-skills --skill multi-tenant-architecture
View SKILL.md on GitHub
Installs1.2K
  • 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 codebase-architecture), scaffolding a new repo (use scaffold-nextjs), or the content of per-tenant SEO artifacts once routing serves them dynamically: sitemap entries, canonical URLs, structured data, indexing policy (use optimise-seo).

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.yaml is launcher metadata for external runners only; do not load it in normal use.

Workflow (order matters)

Copy this checklist to track progress:

Multi-tenant progress:
- [ ] Step 1: Domain strategy and PSL decision
- [ ] Step 2: Tenant identification strategy
- [ ] Step 3: Isolation model
- [ ] Step 4: Deterministic routing
- [ ] Step 5: Tenant context propagation
- [ ] Step 6: Least-privilege bindings and tenant config
- [ ] Step 7: Custom domains and per-tenant static files
- [ ] Step 8: Limits mapped to plans
- [ ] Step 9: API parity with the UI
- [ ] Step 10: Extension modes
  1. 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.com damages the whole domain.
  • Register a separate TLD for tenant workloads (e.g. acme.app for tenants, acme.com for brand).
  • Untrusted content on sibling subdomains: choose PSL submission, record owner plus timeline. Otherwise record No PSL with the cookie-isolation reason. See psl.md.
  • Start PSL early; review takes weeks.
  1. 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.
  1. 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_id scoping. Middleware resolves tenant from hostname; every query includes tenant context. Postgres RLS for defence-in-depth.
  1. 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.
  1. 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-plan on forwarded request headers (not the response). Server Components read via headers(); API routes read from request headers. Implementation in vercel-platform.md.
  1. 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/sdk for domain management. DB connections scoped by tenant_id, or database-per-tenant (Neon).
  1. 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/sdk for 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.txt must vary by tenant; never serve from /public. Cloudflare: generate in the tenant Worker. Vercel: route handlers under the domain segment (see vercel-platform.md).
  1. 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.
  1. 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.
  1. 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 use NextResponse.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_id scoping: 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
  • codebase-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.
Agent Skills

Agent Skills

24 skills for the parts of shipping that code review never covers.

Installs19.9K
npx skills add mblode/agent-skills

codebase-architecture

Design a structure, deepen an existing one, or harden it with guardrails.

  • Contents
  • Modes
  • References
  • Design mode (new codebase)
  • Deepen mode (existing codebase)
  • Harden mode (make it stick)
  • Validation loop

scaffold-nextjs

Next.js turborepo with Blode UI, Ultracite, GitHub, and Vercel.

  • Reference Files
  • Scaffold Workflow
  • Placeholder Reference
  • Gotchas
  • Skill Handoffs

scaffold-cli

TypeScript CLI package: ESM, tsdown, vitest, changesets, CI.

  • Reference Files
  • Scaffold Workflow
  • Dependencies
  • Anti-patterns
  • After Scaffolding

4 Items

Architecture