Website Development with Contentful CMS
Contentful is one of the first and most mature headless CMS on the market. Fully SaaS: content storage, CDN, APIs in Contentful cloud. Content model created via GUI or API. REST API (Content Delivery API, Content Management API) and GraphQL out of the box.
Architecture
Contentful Studio (Web)
↕ CMA (Content Management API)
Content Lake (Contentful)
↕ CDA (Content Delivery API) / GraphQL
Next.js / Nuxt / Remix / Mobile App
↕ Contentful Image API (CDN)
Installation
npm install contentful
// lib/contentful.ts
import { createClient } from 'contentful'
export const contentfulClient = createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
environment: 'master',
})
Next.js Integration
// app/blog/[slug]/page.tsx
import { contentfulClient } from '@/lib/contentful'
import { documentToReactComponents } from '@contentful/rich-text-react-renderer'
import { notFound } from 'next/navigation'
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const entries = await contentfulClient.getEntries({
content_type: 'blogPost',
'fields.slug': params.slug,
limit: 1,
})
const post = entries.items[0]
if (!post) notFound()
return (
<article>
<h1>{post.fields.title}</h1>
<div>{documentToReactComponents(post.fields.content)}</div>
</article>
)
}
export async function generateStaticParams() {
const entries = await contentfulClient.getEntries({
content_type: 'blogPost',
select: ['fields.slug'],
limit: 1000,
})
return entries.items.map(entry => ({ slug: entry.fields.slug }))
}
export const revalidate = 3600
GraphQL API
const POSTS_QUERY = `
query GetPosts($limit: Int) {
blogPostCollection(limit: $limit, order: publishedAt_DESC) {
items { title, slug, excerpt, publishedAt }
}
}
`
const response = await fetch(`https://graphql.contentful.com/content/v1/spaces/${SPACE_ID}/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${ACCESS_TOKEN}`,
},
body: JSON.stringify({ query: POSTS_QUERY }),
})
const { data } = await response.json()
Webhooks for ISR
In Contentful: Settings → Webhooks → Add Webhook:
// app/api/revalidate/contentful/route.ts
export async function POST(req: Request) {
const secret = req.headers.get('x-contentful-webhook-secret')
if (secret !== process.env.CONTENTFUL_WEBHOOK_SECRET) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await req.json()
revalidateTag('contentful')
return Response.json({ revalidated: true })
}
Comparison
| Criterion | Contentful | Sanity | Strapi | Directus |
|---|---|---|---|---|
| Hosting | SaaS only | SaaS only | Self-hosted / Cloud | Self-hosted / Cloud |
| Rich text | Contentful RT | Portable Text | Markdown | JSON |
| Real-time | No | Yes | No | Yes |
Timeline
Basic website with 3–5 content types and Next.js — 1.5–2 weeks. Complex multilingual project with Preview Mode and GraphQL — 3–4 weeks.







