The decision to go headless is straightforward. The decision of how to structure a headless CMS for a multi-brand, multi-market organisation serving 50+ websites from a single content backend — that's where complexity lives.
The client: a global manufacturing company with brands in 14 markets, each with local content requirements, translation workflows, and brand-specific design systems, but shared product data and corporate content. The technical stack: Contentful as the CMS, Next.js frontends, and an event-driven integration with the SAP stack described in our earlier article on Symfony Messenger.
Content Modelling Governance
We established a Content Architecture Board — a lightweight governance layer with two roles. The Content Model Owner (central, one person) has final authority over the global content type schema in Contentful. The Content Leads (one per market/brand) can propose new content types but cannot create them without review.
Every content type is tagged with a lifecycle status: stable, experimental, or deprecated. Deprecated types have a sunset date and a migration path. This prevented the accumulation of abandoned types that plagued the pre-migration WordPress setup.
We also introduced a content type design review checklist covering: field naming consistency, required vs optional fields, localisation requirements, and reference relationship depth. Simple, but never previously existed.
Multi-Tenant API Design
Contentful's Content Delivery API is shared across all environments. At 50+ sites, naively calling the API from each frontend creates a significant request volume — and more importantly, inconsistent caching behaviour across markets.
We introduced a thin GraphQL aggregation layer between the frontends and Contentful. This layer handles: per-market content filtering, response caching with market-aware cache keys, locale normalisation, and product data enrichment from the SAP integration. Frontends call one endpoint with market context in the header; the aggregation layer handles all the Contentful complexity.
CDN Invalidation at Scale
The most technically interesting problem: when a product description changes in SAP, it propagates as an event, updates in Contentful, and must invalidate cached pages across 50+ frontends. With each page potentially cached in CloudFront at multiple edge nodes, naive tag-based invalidation was taking 8–12 minutes.
We redesigned the invalidation strategy using surrogate cache keys at the content-type level rather than the page level. A product update invalidates all pages that reference that product type in a single API call — reducing invalidation time to under 90 seconds for 95% of changes.
- Establish content model governance before technical implementation
- A content type lifecycle (stable/experimental/deprecated) prevents model sprawl
- Introduce an aggregation layer to abstract CMS complexity from frontends
- Design CDN invalidation strategy based on content type, not page URL
- One Content Model Owner per organisation — committee governance kills model quality