Multi-Tenant Architecture Patterns

Multi-tenancy decides how content, config, and API access are partitioned across clients, brands, or business units — and which isolation tier you pick sets your caching, deployment, governance, and fetch complexity. This page compares three tiers, their tradeoffs, and the cross-cutting cache and governance controls. Tenant boundaries shape everything downstream in Headless CMS Architecture & Platform Selection, from schema design to CDN strategy.

Three isolation tiersThe three multi-tenant tiers from least to most isolated: a shared schema with tenant id filtering, isolated schemas or collections per tenant in a shared database, and fully isolated instances per tenant.Shared schemacheapest, leak risktenant_id on every recordone modelIsolated schemadivergent modelsnamespace per tenantshared instanceDedicated instancestrongest isolation, highest costown CMS, DB, endpoint
Each step down buys isolation with cost and operational complexity.

Integration Contract

Every tier needs the same contract between frontend and CMS, even if the mechanics differ: how a request is mapped to a tenant, which credentials that tenant uses, which content and schema it can see, and how its caches are keyed and purged. Keep that mapping in one tenant registry, read by the edge, the data layer and the webhook handlers alike. The registry is the single source of truth for tenant ids, domains, locales, CMS endpoints or spaces, token names and feature flags. When a tenant is added, changed or removed, only the registry changes, and every layer follows.

Bash
# .env: per-tenant credentials are referenced by name from the tenant registry
TENANT_REGISTRY_URL=https://config.internal/tenants.json
CMS_TOKEN_ACME=delivery_token_for_acme
CMS_TOKEN_GLOBEX=delivery_token_for_globex
CMS_WEBHOOK_SECRET_ACME=signing_secret_for_acme
CMS_WEBHOOK_SECRET_GLOBEX=signing_secret_for_globex

1. Shared database, shared schema (tenant-ID filtering)

All tenants share tables or collections. Every record carries a tenant_id or site_id, and the API enforces row-level security by injecting a tenant filter into every query. This is the default for SaaS CMS providers and high-volume agency portfolios.

Implementation Blueprint:

YAML
# cms-config.yaml
tenancy:
  mode: shared-schema
  tenant_header: X-Tenant-ID
  fallback_tenant: default
  query_injection: strict
  audit_trail: enabled

Frontend Fetch Pattern:

JavaScript
async function fetchContent(endpoint, tenantId, query) {
  const response = await fetch(endpoint, {
    method: "POST",
    headers: {
      "X-Tenant-ID": tenantId,
      "Content-Type": "application/json",
      "Authorization": `Bearer ${process.env.CMS_TOKEN}`
    },
    body: JSON.stringify(query)
  });
  
  if (!response.ok) throw new Error(`Tenant fetch failed: ${response.status}`);
  return response.json();
}

Caching: Cache keys must vary by tenant. Without Vary: X-Tenant-ID or cache-key normalization, you serve Brand A’s content to Brand B. Use tenant-scoped cache tags (purge:tenant:acme) for granular invalidation that doesn’t flush the whole edge.

Tradeoffs:

  • ✅ Lowest cost, fastest provisioning, unified caching, simple migrations.
  • ❌ Cross-tenant leakage if query injection fails; awkward single-tenant backup/restore; global schema changes hit all tenants at once. See Multi-tenant headless CMS architecture explained for the routing detail.

2. Shared database, isolated schema (per-tenant collections)

Tenants share a database instance but live in separate namespaces, schemas, or collections; the CMS routes to tenant-specific structures at the storage layer. Stronger boundaries, still efficient.

Implementation Blueprint:

JSON
{
  "tenancy": {
    "mode": "isolated-schema",
    "routing_strategy": "path-prefix",
    "tenant_prefix": "/tenants/{tenantId}/api/v1",
    "schema_versioning": "per-tenant"
  }
}

Content modeling: Isolated schemas let tenants diverge without breaking each other. Use shared base types (Hero, Footer) with tenant-specific extensions instead of duplicating models — see Content Modeling Best Practices to avoid schema sprawl. This keeps component libraries consistent while allowing editorial flexibility.

Tradeoffs:

  • ✅ Stronger isolation, per-tenant schema versioning, simpler compliance auditing, less cache collision.
  • ❌ Higher storage overhead, slower onboarding, fragmented caching. Needs careful index management across namespaces as tenant count grows.

3. Fully isolated infrastructure (per-tenant instances)

Each tenant gets a dedicated instance, database, and API endpoint, orchestrated via containers (Kubernetes, Docker) or serverless with independent scaling.

Infrastructure-as-Code Blueprint (Terraform):

HCL
resource "cms_instance" "tenant" {
  for_each = var.tenants
  name     = "cms-${each.key}"
  region   = each.value.region
  db_isolation = "dedicated"
  api_version = "v2"
  tags = {
    tenant    = each.key
    environment = each.value.env
  }
}

API strategy: Isolated endpoints mean frontends juggle multiple base URLs, which reshapes the GraphQL vs REST API Tradeoffs — GraphQL’s single endpoint becomes tenant-specific, needing dynamic resolution at build or runtime. Front it with an API gateway or service mesh to route requests, normalize auth, and aggregate telemetry.

Tradeoffs:

  • ✅ Maximum isolation, independent scaling, predictable SLAs, granular backup/restore.
  • ❌ Highest infrastructure and operational cost, complex CI/CD, fragmented monitoring. The AWS Well-Architected Framework for SaaS covers hardening these pipelines.
Comparing the three tiersShared schema, isolated schema and dedicated instances compared on isolation, cost per tenant, onboarding speed, schema divergence and backup granularity.ConcernShared schemaIsolated schemaDedicatedIsolationapp-level filternamespaceinfrastructureCost per tenantlowestmoderatehighestOnboardingminuteshoursdaysSchema divergencenoneper tenantfullBackup and restorewhole datasetper namespaceper tenant
Most portfolios start shared and move individual tenants to stronger tiers when a requirement forces it.

Tenant Resolution

Every request must be mapped to exactly one tenant before any content is fetched. The usual signals are the host name (acme.example.com or acme.com), a path prefix (/acme/...) or, for APIs, a header set by a trusted upstream. Resolve the tenant at the edge, from the registry, and reject requests for unknown hosts rather than falling back to a default tenant, which is how one tenant’s content ends up on another’s domain. Pass the resolved tenant downstream explicitly, as a parameter of every data function, not through ambient global state that can leak between concurrent requests. The tenant resolution guide shows a complete middleware.

TypeScript
// lib/tenant.ts
export interface Tenant { id: string; hosts: string[]; defaultLocale: string; cmsSpace: string; tokenEnv: string }

let registry: Tenant[] | null = null;

export async function resolveTenant(host: string): Promise<Tenant | null> {
  registry ??= (await (await fetch(process.env.TENANT_REGISTRY_URL!, { next: { revalidate: 300 } })).json()) as Tenant[];
  const normalized = host.toLowerCase().replace(/:\d+$/, "");
  return registry.find((t) => t.hosts.includes(normalized)) ?? null; // no default tenant: unknown hosts are rejected
}

Cross-cutting controls

CDN caching

Map tenant boundaries to cache segmentation: Cache-Control: s-maxage=86400, stale-while-revalidate=3600 plus tenant-scoped surrogate keys. See Cloudflare Cache Control Guidelines for edge patterns that prevent cross-tenant poisoning.

DX metrics

Track tenant provisioning time, API latency p95/p99, cache hit ratio, and schema deploy success. Tenant-aware dashboards isolate regressions before they reach a client.

Governance

The tier sets your audit model: shared-schema needs application-level logging, isolated infrastructure enables database-level compliance reporting. Add CI policy checks that validate tenant boundaries before content or schema merges.

Preview and drafts per tenant

Preview must respect tenant boundaries as strictly as delivery. A preview session for one tenant should only ever read that tenant’s drafts, with that tenant’s preview token, and preview URLs should be generated with the tenant’s own domain so cookies and draft mode apply to the right site. In shared-schema setups, check the tenant of every draft entry before rendering it, since preview tokens often have access to all tenants’ drafts. The preview and draft workflow section covers draft mode itself.

Error handling and noisy neighbours

In shared tiers, one tenant’s traffic spike or expensive queries can exhaust rate limits for everyone. Add per-tenant rate limits and concurrency pools in your data layer, so a campaign on one site cannot slow down the others. Log and alert per tenant: an error rate that is fine in aggregate can hide a tenant whose site is entirely broken. When a tenant’s CMS space or instance is unavailable, fail that tenant only, serving stale content where possible, and never fall back to another tenant’s data.

Testing tenant isolation

Isolation is a property worth testing directly. Keep fixtures for at least two tenants with deliberately colliding slugs, such as both having a /pricing page, and assert that each host renders its own content. Add tests that request one tenant’s content with another tenant’s host or token and expect a 404 or 403. Run a periodic crawl that fetches each tenant’s sitemap URLs from its own domain and checks that the rendered tenant marker, such as a meta tag with the tenant id, matches. These tests catch the rare but serious class of bugs where caching or resolution mistakes show one client’s content on another client’s site.

Webhooks and invalidation per tenant

Publish webhooks must be routed to the right tenant and purge only that tenant’s caches. In shared-schema setups, every webhook carries the tenant id in the entry, and the handler maps it through the registry to that tenant’s cache tags and revalidation endpoint. In isolated setups, each tenant’s space or instance has its own webhook and secret, and the handler resolves the tenant from the secret that verified the signature, never from an unsigned header. Prefix every cache tag with the tenant id, such as acme:entry:42, so a purge can never touch another tenant’s cache, even when entry ids collide across instances. The tenant-aware invalidation guide covers the handler in detail.

Provisioning and Onboarding Tenants

The cost of each tier shows most clearly when a tenant is added. In a shared schema, onboarding is a registry entry, a domain and some seed content, and can take minutes. In isolated schemas, it adds creating the namespace, applying the tenant’s model version and issuing credentials. With dedicated instances, it includes provisioning infrastructure, which should be fully automated, or onboarding becomes a multi-day project that nobody wants to repeat. Whatever the tier, write onboarding as code: one script or pipeline that creates the CMS space or namespace, applies the model, creates tokens and webhooks, registers the domain and certificate, adds the registry entry and runs an isolation test. The tenant onboarding guide walks through such a pipeline.

Onboarding one tenant, by tierTime to onboard a new tenant with automated pipelines: about half an hour for a shared schema, about three hours for an isolated schema including model application, and about a day for a dedicated instance including infrastructure provisioning and certificates.Shared schemaIsolated schemaDedicated instanceinfra + certs0 hours5 hours10 hours15 hours20 hoursisolated done
Automation keeps even the dedicated tier to a day; manual onboarding typically takes a week or more.

Offboarding deserves the same automation. Removing a tenant should revoke its tokens, delete or export its content according to the contract, purge its caches, remove its domains and registry entry, and leave an audit record. Tenants that are removed by hand tend to leave active tokens and cached content behind for months.

Moving Tenants Between Tiers

Requirements change: a tenant on the shared schema signs a contract that demands data residency, or a group of small dedicated instances becomes too expensive to run. Design for movement by keeping the tenant’s identity and content model independent of the tier. The registry records the tier and the CMS location per tenant, the data layer reads from wherever the registry points, and content is exported and imported through the CMS’s APIs rather than database dumps. A move then follows a familiar pattern: provision the new location, copy content, run both in parallel with the new one in preview, switch the registry entry and purge caches, and decommission the old location after a waiting period. Tenants mixed across tiers are normal; the registry is what keeps the frontend unaware of it.

Shared Code, Tenant-Specific Presentation

Most multi-tenant frontends share one codebase and one component library, with presentation varying per tenant. Keep variation in data rather than code: design tokens for colours, typography and spacing, selected by the resolved tenant; feature flags in the registry for optional features; and content-driven layout through blocks. Code branches such as if (tenant === "acme") multiply quickly and make every change risky for every tenant. The component library guide shows how to structure tokens and components so one release serves all tenants.

Data Residency and Contracts

Enterprise tenants often bring contractual requirements that decide the tier for them. Data residency requires content and media for that tenant to be stored, and sometimes delivered, from specific regions; most SaaS CMS platforms offer region choice per space or organization, which effectively forces the tenant into its own space in that region. Retention and deletion clauses require the ability to export and delete one tenant’s content completely, which is trivial with dedicated spaces and laborious with a shared schema. Audit requirements may demand per-tenant logs and access reviews. Collect these requirements during sales, record them in the registry as attributes of the tenant, and let them drive placement automatically: a tenant flagged for EU residency is provisioned in the EU space or instance by the onboarding pipeline, not by someone remembering to.

Observability per Tenant

Aggregate dashboards hide tenant-specific problems. Tag every log line, metric and trace with the tenant id, and build views that show error rates, latency, cache hit rates and publish-to-live latency per tenant. Alert on per-tenant thresholds as well as global ones: a tenant whose site returns errors on every page is an incident even if it accounts for one percent of total traffic. Per-tenant cost attribution is useful too, especially for agencies billing clients: CMS requests, bandwidth and build minutes per tenant show which clients cost more to serve and where caching or query improvements would pay off. The same tenant tagging makes support easier, because a client’s complaint can be matched to that client’s metrics immediately.

Decision matrix

Requirement Recommended Tier
High tenant volume, uniform content models, cost-sensitive Shared Schema
Moderate tenant count, divergent content models, compliance auditing Isolated Schema
Enterprise SLAs, strict data residency, independent scaling Fully Isolated

Pick the tier that matches your operational maturity, governance requirements, and delivery architecture. Multi-tenant design is an evolving contract between infrastructure, content operations, and frontend pipelines, not a one-time config.

A Portfolio in Practice

Consider an agency with 45 client sites. Forty of them are small marketing sites with nearly identical models; they share one CMS space with a tenant field, one Next.js deployment and one set of block components, styled per tenant with design tokens. Four larger clients have their own CMS spaces because their editorial teams need custom content types and their own roles, but they still run on the shared frontend, resolved by domain from the registry. One regulated client has a dedicated CMS instance in its required region and its own deployment, because its contract demands infrastructure isolation. All three tiers are described in the same registry, the same onboarding pipeline creates each kind of tenant, and the same smoke tests and dashboards cover them all. When one of the small clients grew and needed custom content types, it moved to its own space over a week without any frontend release, by copying its content and switching its registry entry. That flexibility, rather than the choice of any one tier, is what the patterns in this topic are meant to provide.

Frequently Asked Questions

Which tier should an agency start with?

Usually the shared schema, or one CMS space per client on a shared frontend, which is the isolated-schema tier in practice. Move individual clients to dedicated instances only when a contract or regulation requires it.

Is a separate CMS space per tenant the same as an isolated schema?

Close enough for architecture decisions: separate spaces isolate content, models and credentials, while the frontend and infrastructure stay shared. It is the most common setup for agencies and multi-brand groups.

How do we prevent one tenant’s content from appearing on another’s site?

Resolve tenants strictly from the registry, pass the tenant explicitly to every data function, prefix cache keys and tags with the tenant id, and test isolation with colliding fixtures and periodic crawls.

Can tenants have different content models?

In isolated tiers, yes, but divergence has a cost: every model difference is a code path in the shared frontend. Keep a shared base model and allow extensions, rather than fully independent models.

How do we handle tenants with different locales?

Store supported and default locales per tenant in the registry, alongside its domains, resolve the locale after the tenant, and include both in cache keys. Localization strategy per content type still follows the localization modeling guide, independent of tenancy.

What is the most common multi-tenant mistake?

Falling back to a default tenant when resolution fails. It hides configuration errors, such as a missing registry entry for a new domain, and exposes content on the wrong domain. Failing loudly with a 404, and alerting on a sudden rise in unknown hosts, is always better, because it turns a silent leak into a visible, fixable configuration error.