A headless CMS stores content and serves it through an API, leaving the website's design to your own code. Sanity and Contentful are hosted services, Strapi and Payload are open source and run on your own database, Storyblok adds a visual page editor, and Decap edits Markdown files in Git. The table below compares them side by side.
| Need | Pick |
|---|---|
| Flexible content modelling | Sanity |
| Own your data, free to self-host | Strapi or Payload |
| Visual editing for marketers | Storyblok |
| Zero cost, content in Git | Decap CMS |
Updated
We are not affiliated with any of these vendors. Each section below fetches the same list of blog posts, so you can compare how each feels to code against, and the table shows listed prices as we read them on the day this page was updated.
Picking the site generator too? See static site generators compared.
| CMS | Type | Where content lives | Free option | Paid from |
|---|---|---|---|---|
| Sanity | Hosted content platform, open-source editor (Studio) | Sanity's cloud | Free plan | $15 per seat a month (Growth) |
| Contentful | Hosted, closed source | Contentful's cloud | See vendor | See vendor (page did not load for us) |
| Strapi | Open source (MIT), self-host or cloud | Your database | Self-hosting is free | $35 a month (Cloud Starter) |
| Payload | Open source (MIT), runs inside Next.js | Your database | Self-hosting is free | Your hosting costs |
| Storyblok | Hosted, visual editor | Storyblok's cloud | Starter plan | $99 a month (Growth) |
| Decap CMS | Open source (MIT), Git-based | Markdown files in your repository | Free | None |
| Situation | Good fit |
|---|---|
| Developers want to model content in code and query it flexibly | Sanity |
| Large team, many locales, strict roles, budget for a vendor | Contentful |
| Want to own the database and the server | Strapi or Payload |
| Already on Next.js and want the CMS in the same app | Payload |
| Marketers want to edit pages visually | Storyblok |
| A small static site where content can live in Git | Decap CMS |
GROQ selects and shapes documents in one query, so the slug arrives as a plain string.
import { createClient } from "next-sanity";
const client = createClient({ projectId: "your-id", dataset: "production", apiVersion: "2025-01-01", useCdn: true });
export async function getPosts() {
return client.fetch(`*[_type == "post"] | order(publishedAt desc){ title, "slug": slug.current, publishedAt }`);
}getEntries filters by content type; fields come back under fields.
import { createClient } from "contentful";
const client = createClient({ space: process.env.CONTENTFUL_SPACE_ID!, accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN! });
export async function getPosts() {
const res = await client.getEntries({ content_type: "blogPost", order: ["-fields.publishDate"] });
return res.items.map((i) => ({ title: i.fields.title, slug: i.fields.slug }));
}Strapi generates a REST API per content type. Version 5 returns fields directly on each item, without the attributes wrapper of version 4.
export async function getPosts() {
const url = `${process.env.STRAPI_URL}/api/posts?sort=publishedAt:desc&fields[0]=title&fields[1]=slug`;
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.STRAPI_TOKEN}` } });
if (!res.ok) throw new Error(`Strapi ${res.status}`);
const { data } = await res.json();
return data; // [{ id, documentId, title, slug }, ...]
}Payload runs inside your Next.js app, so a Server Component can read the database through the Local API with no HTTP request.
// app/blog/page.tsx
import { getPayload } from "payload";
import config from "@payload-config";
export default async function Blog() {
const payload = await getPayload({ config });
const { docs } = await payload.find({ collection: "posts", sort: "-publishedAt", limit: 20 });
return <ul>{docs.map((p) => <li key={p.id}><a href={`/blog/${p.slug}`}>{p.title}</a></li>)}</ul>;
}Stories in a folder are fetched with starts_with. The public token is read-only and safe to use in a static build.
export async function getPosts() {
const qs = new URLSearchParams({ starts_with: "blog/", version: "published", sort_by: "first_published_at:desc", token: process.env.STORYBLOK_TOKEN! });
const res = await fetch(`https://api.storyblok.com/v2/cdn/stories?${qs}`);
const { stories } = await res.json();
return stories.map((s) => ({ title: s.name, slug: s.slug }));
}Decap is an admin page that commits Markdown files to your Git repository. Your site generator builds them like any other file.
# public/admin/config.yml
backend:
name: github
repo: your-org/your-site
branch: main
media_folder: public/images
collections:
- name: posts
label: Blog posts
folder: content/posts
create: true
fields:
- { label: Title, name: title, widget: string }
- { label: Date, name: date, widget: datetime }
- { label: Body, name: body, widget: markdown }
A headless CMS adds an account, an API, previews and a build hook to look after. For a five-page business site that changes a few times a year, that is overhead. Markdown in Git, or a builder where owners edit the page they see, often serves them better.
Traditional WordPress stores content and renders the pages with its themes. A headless CMS only stores and serves content; your own front end, such as a Next.js or Astro site, renders it.
Payload runs inside a Next.js app, which makes it the tightest fit. Sanity, Contentful and Storyblok also have official Next.js guides and work well with server rendering and revalidation.
Yes. Strapi, Payload and Decap CMS are open source and free to self-host. Sanity and Storyblok have free plans with limits on users and usage.
Not always. If developers edit the site, Markdown files in the repository are simpler. If non-technical people edit a small business site, a website builder with a built-in editor is usually quicker than wiring a CMS to custom code.
Related: Static site generators, Frontend framework comparison, React Server Components, Best blogging platforms, All free tools.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.