Headless CMS
28. September, 2024 • 7 min read • Develop
Everything except the page
I have been a core developer on django CMS since 2014, which means I have spent a long time inside a CMS that renders its own pages. So when someone tells me that going headless solves everything, my first question is what they plan to do about preview, URLs and redirects. Those parts rarely make it onto the landing page.
That is not an argument against headless. It is an argument for knowing what you trade away. The trade is usually worth it these days, and the tooling has got genuinely good. But it is a trade, and the comparison tables you find online are written by the people selling you the API.
So: what a headless CMS actually is, how the pieces fit together, and where four of the common ones differ once you get past the marketing.
What “headless” actually means
A headless CMS stores and edits content but does not render it. The “head” is the presentation layer, and it has been cut off. What is left is a content repository plus an API over it, usually REST or GraphQL, often both.
Compare that with WordPress or django CMS in their default setup. The same application that stores an article also decides what the page looks like, owns the URL it lives at, and produces the HTML. That coupling is where a traditional CMS gets its best features. It is also where it gets its worst constraints, because everything you want to do has to be expressed as a theme or a template or a plugin.
The practical definition is short. You get structured content over an API, and you build the front end yourself. Everything else follows from those two facts.
How the pieces fit
There are two moving parts.
The content repository holds your models and your data. You define content types (an article has a title, a body, a hero image, a handful of tags), and editors fill them in through an admin interface.
The API layer exposes that content. Your front end queries it and decides what to do with the response. That front end can be Next.js, Nuxt, an iOS app, or the screen in a shop window that nobody has updated since 2019.
The workflow that falls out of this:
- Someone models the content types. This is the step teams rush through and then regret for two years.
- Editors create and edit entries in the admin.
- The front end fetches entries over REST or GraphQL, at build time, at request time, or some mix of the two.
- The front end renders. Nobody in the CMS knows or cares what it looked like.
Step four is the entire difference. It is also the source of every complaint I have ever heard about a headless setup.
Traditional against headless
| Feature | Headless CMS | Traditional CMS |
|---|---|---|
| Front-end flexibility | Decoupled from the CMS, use any framework you like | Coupled to the CMS, limited to themes and templates |
| Content delivery | Delivered over an API | Rendered by the CMS itself |
| Multi-channel support | One content source feeds web, apps and devices | Built primarily for websites |
| Editing experience | Form-based, preview has to be built | In-context editing and preview come for free |
| Customisation | Total control over presentation | Customisation means fighting the template layer |
Read the first row again, though. “Use any framework you like” also means “you have to build it”. And total control over presentation means total responsibility for it. The table is honest, it just does not tell you who pays.
What you give up
Being specific about this, because the vendor comparison pages will not be:
- Preview. In a coupled CMS, preview is free. The CMS renders the page, so it can render the draft too. Headless, preview is a feature you build. Storyblok and Contentful both ship preview APIs, and Storyblok’s visual editor is the main reason people choose it, but you still wire the thing up yourself.
- In-context editing. Clicking a paragraph on the live page and typing into it is the single thing editors love most about django CMS. It is also the hardest behaviour to reproduce over an API, because the CMS has no idea where that paragraph ended up on screen.
- Routing and redirects. Someone has to own the mapping from a slug to a page. If the CMS does not, your front end does, and that includes the redirect you owe the world every time marketing renames a page.
- The “who broke it” question. With two systems there are two places for a deploy to go wrong, and two teams who are fairly sure it was not them.
None of these are dealbreakers. All of them are work that was not on the estimate.
The four you will be asked about
Strapi
Strapi is the open-source default. Node.js, self-hosted, and you can customise most of it including the admin UI. Content types are defined through a builder that writes schema files to disk, which means they are in version control where they belong.
- REST and GraphQL out of the box.
- Roles and permissions for content teams.
- A plugin ecosystem, of varying quality.
Start one with:
npx create-strapi@latest my-strapi-projectPick it when you want control and are happy to run the thing yourself.
Payload CMS
Payload is also Node.js and also self-hosted, but aimed squarely at developers rather than at content teams. Configuration is code, and the generated client is type-safe end to end, which is a real advantage if your front end is TypeScript.
- Authentication and access control built in.
- Content modelling defined in config files, not a UI.
- Rich text editing with Markdown support.
npx create-payload-app@latestPick it when the developers outnumber the editors and the types matter more than the admin polish.
Storyblok
Storyblok is hosted, and its selling point is the visual editor. Editors click on the rendered page and edit the component they clicked, which is the closest anyone has got to bringing coupled-CMS editing to a decoupled stack. You pay for that with a component model you have to design around.
Pick it when the editors have opinions and will voice them.
Contentful
Contentful is the enterprise option. Hosted, API-first, REST and GraphQL, a CDN in front of everything, and asset management that holds up at scale. The interface is competent rather than lovable, and the pricing model rewards you for planning your content model before you start.
Pick it when the content is distributed globally and someone else’s uptime is the point.
Where django CMS sits in all this
I would be leaving something out if I did not mention it. There has been an active effort this year to expose django CMS content over a REST API so it can be run headless while keeping the editing model that makes it worth using. The request for comments went up in March and djangocms-rest has been moving since May. It is early, and I am watching it with an obvious personal bias.
That is the shape of the interesting problem, I think. Not “headless or not”, but whether you can keep the editing experience of a coupled CMS while serving the content over an API. Nobody has fully solved that yet.
What I would actually do
For a marketing site with a small team and a real editorial workload, I would still reach for a coupled CMS first, because preview and in-context editing are worth more than framework freedom to the people using it every day.
For anything feeding more than one front end, headless, without much hesitation. Start with Strapi if you want to host it and Storyblok if you do not, and spend an afternoon on the content model before you write a single component. That afternoon is the cheapest one in the project.
‘Till next time!