Short answer
Going headless does not make a CMS scalable – the content model does. A badly modelled headless system is harder to fix than a badly modelled monolithic one, because the damage is now spread across two codebases and an API contract. Model the content first, choose the delivery architecture second, and be honest about whether you need headless at all.
Headless WordPress gets adopted for two very different reasons. One is good: the same content has to serve a website, a mobile app and a partner integration, and coupling it to PHP templates is the wrong shape. The other is that someone wants to write React, which is a preference rather than an architecture.
Both are common. Only the first survives contact with an editorial team.
Model content as content, not as pages
The failure that causes the most pain later is modelling the CMS around what the current design looks like. Fields named hero_left_column_text encode a layout decision into the data, and when the design changes – which it will – the migration is a content problem rather than a CSS one.
Three rules that hold up:
- Name fields for what they mean, not where they appear.
summary, notintro_paragraph_below_title. - Model relationships as relationships. An author is an entity referenced by a post, not a text field repeated on every post. Denormalised strings drift, and there is no way to fix them in bulk once they have.
- Separate structure from presentation. The API returns what something is. The front end decides how it looks. When a field’s value only makes sense to one template, it belongs to that template.
Gutenberg blocks as the content model
Blocks are frequently treated as a page-builder feature and dismissed. Used deliberately, they are the structured content model – a post’s content becomes a typed, ordered list of components with validated attributes, which is exactly what a front end wants to consume.
What makes that work in practice:
- Attributes over markup. A block whose data lives in typed attributes can be serialised to JSON cleanly. A block that parses meaning out of its saved HTML cannot, and every consumer has to re-implement that parsing.
- Server-side rendering for dynamic blocks. Anything that queries data should render at request time, not bake results into saved content that goes stale invisibly.
- Version and validate. Block attributes are a schema. Changing them without deprecations produces validation errors across existing content, and editors experience that as the CMS breaking.
- Constrain the palette. Every block available is a shape your front end must handle. A curated set of twelve blocks is a maintainable contract; core plus every plugin’s blocks is not.
REST or GraphQL
Both work. The trade-off is real but smaller than the debate suggests.
| REST | GraphQL | |
|---|---|---|
| Over-fetching | Common; endpoints return fixed shapes | Client selects fields |
| Round trips | Multiple for related data | One query, nested |
| Caching | Straightforward – HTTP caching applies | Harder; usually POST, needs application-level caching |
| Cost visibility | Predictable per endpoint | One query can be arbitrarily expensive |
| Debugging | Readable in any HTTP tool | Needs tooling to inspect |
GraphQL’s flexibility is also its risk: a client can compose a query that traverses relationships deeply enough to generate hundreds of database queries. Query depth limits, complexity analysis and dataloader-style batching are not optional at scale – they are what makes GraphQL safe to expose.
For a single, known front end, REST with purpose-built endpoints is often simpler and easier to cache. GraphQL earns its complexity when there are several consumers with genuinely different data needs.
What headless costs you
Worth stating plainly, because it is usually discovered after the decision rather than before:
- Preview stops being free. Editors expect to see unpublished changes. In a decoupled setup that requires draft-aware API access and a preview route in the front end – a feature you now own.
- So does live-page editing context. The editing experience no longer reflects the rendered result unless you invest in making it do so.
- Cache invalidation becomes yours. A publish must invalidate the right pages in the right layers. Get it wrong and editors publish into a void, which erodes trust in the CMS faster than almost anything else.
- Two deployments, one release. An API change and its front-end consumer have to ship in a compatible order, every time.
- Redirects, sitemaps, canonicals and metadata all move to the front end. None of it is difficult; all of it was previously free.
None of these are reasons not to go headless. They are reasons to go headless for an actual architectural requirement rather than a stack preference.
A middle path worth considering
The decision is not binary. A traditional WordPress front end with React islands for the genuinely interactive parts keeps preview, editing context and cache invalidation working, while giving you a modern component model where it matters. For a content site with a handful of interactive features, this is frequently the right answer and it is rarely the one considered.
If you do go fully decoupled, the caching and query-budget discipline matters more rather than less, because you have added a network hop between the content and the renderer – see Full-Stack Web Performance: From Next.js SSR to Database Query Budgets. And if the platform is enterprise WordPress, the constraints in Engineering for WPVIP apply to the API layer exactly as they do to templates.
Questions people actually ask
- Is headless WordPress faster than traditional WordPress?
- Not inherently. A well-cached traditional WordPress site frequently outperforms a decoupled one, because headless adds a network hop between content and renderer. What headless gives you is delivery flexibility and independent front-end deployment. If speed is the only goal, fix caching and the query budget first – it is cheaper and usually sufficient.
- REST or GraphQL for headless WordPress?
- REST with purpose-built endpoints when there is one known front end – simpler, and HTTP caching works out of the box. GraphQL when several consumers need genuinely different shapes of the same data. If you choose GraphQL, add depth limits, complexity analysis and query batching before exposing it, or one client query can generate hundreds of database queries.
- How do editors preview unpublished content?
- The front end needs an authenticated preview route that requests draft content from the API, typically with a short-lived token issued by the CMS. Budget for it explicitly: it is the single most-missed requirement in headless projects, and its absence is what turns editorial teams against the platform.
- Should custom blocks or custom fields hold the content?
- Blocks for content that is composed and ordered by the editor – the body of a page. Fields for attributes that are always present and structurally fixed, like a publication date or a canonical author. Using fields to simulate page composition rebuilds a worse block editor; using blocks for fixed metadata makes it optional and inconsistent.
- When is headless the wrong choice?
- When the only consumer is one website, the editorial team relies on preview and in-context editing, and the motivation is a preferred front-end framework rather than a delivery requirement. In that case you are paying the full operational cost of decoupling for a developer-experience benefit that React islands inside a traditional theme would deliver at a fraction of the price.