Use Cases

Contentful CMS Implementation with Claude Code

Learn about Contentful CMS implementation using Claude Code. Practical tips and code examples included.

Streamlining Contentful CMS Integration With Claude Code

Contentful is the flagship of headless CMS platforms, enabling API-first content management. With structured content models and a rich delivery API, you can fetch content from any frontend. With Claude Code, you can efficiently go from content model design to frontend integration.

Designing the Content Model

> Design a content model for a blog site in Contentful.
> Use three content types: article, category, and author.
// scripts/setup-content-model.ts
import contentful from 'contentful-management';

const client = contentful.createClient({
  accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN!,
});

async function setupContentModel() {
  const space = await client.getSpace(process.env.CONTENTFUL_SPACE_ID!);
  const env = await space.getEnvironment('master');

  // Author content type
  const author = await env.createContentTypeWithId('author', {
    name: 'Author',
    fields: [
      { id: 'name', name: 'Name', type: 'Symbol', required: true },
      { id: 'slug', name: 'Slug', type: 'Symbol', required: true },
      { id: 'bio', name: 'Bio', type: 'Text' },
      { id: 'avatar', name: 'Avatar', type: 'Link', linkType: 'Asset' },
    ],
  });
  await author.publish();

  // Category
  const category = await env.createContentTypeWithId('category', {
    name: 'Category',
    fields: [
      { id: 'name', name: 'Name', type: 'Symbol', required: true },
      { id: 'slug', name: 'Slug', type: 'Symbol', required: true },
      { id: 'description', name: 'Description', type: 'Text' },
    ],
  });
  await category.publish();

  // Blog post
  const blogPost = await env.createContentTypeWithId('blogPost', {
    name: 'Blog Post',
    fields: [
      { id: 'title', name: 'Title', type: 'Symbol', required: true },
      { id: 'slug', name: 'Slug', type: 'Symbol', required: true },
      { id: 'excerpt', name: 'Excerpt', type: 'Text' },
      { id: 'body', name: 'Body', type: 'RichText' },
      { id: 'featuredImage', name: 'Featured Image', type: 'Link', linkType: 'Asset' },
      { id: 'author', name: 'Author', type: 'Link', linkType: 'Entry' },
      { id: 'category', name: 'Category', type: 'Link', linkType: 'Entry' },
      { id: 'tags', name: 'Tags', type: 'Array', items: { type: 'Symbol' } },
      { id: 'publishedAt', name: 'Published At', type: 'Date' },
    ],
  });
  await blogPost.publish();
}

Fetching Content

> Create a client that fetches blog posts via Contentful's Content Delivery API.
> Also generate TypeScript types.
// src/lib/contentful.ts
import { createClient, type Entry, type Asset } from 'contentful';

const client = createClient({
  space: process.env.CONTENTFUL_SPACE_ID!,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
});

// Preview client
const previewClient = createClient({
  space: process.env.CONTENTFUL_SPACE_ID!,
  accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN!,
  host: 'preview.contentful.com',
});

function getClient(preview = false) {
  return preview ? previewClient : client;
}

// Type definitions
interface BlogPostFields {
  title: string;
  slug: string;
  excerpt: string;
  body: any; // Rich Text Document
  featuredImage: Asset;
  author: Entry<AuthorFields>;
  category: Entry<CategoryFields>;
  tags: string[];
  publishedAt: string;
}

interface AuthorFields {
  name: string;
  slug: string;
  bio: string;
  avatar: Asset;
}

interface CategoryFields {
  name: string;
  slug: string;
  description: string;
}

// Get the list of posts
export async function getBlogPosts(options?: {
  limit?: number;
  skip?: number;
  category?: string;
  preview?: boolean;
}) {
  const { limit = 10, skip = 0, category, preview = false } = options || {};

  const query: any = {
    content_type: 'blogPost',
    order: ['-fields.publishedAt'],
    limit,
    skip,
    include: 2,
  };

  if (category) {
    query['fields.category.sys.contentType.sys.id'] = 'category';
    query['fields.category.fields.slug'] = category;
  }

  const entries = await getClient(preview).getEntries<BlogPostFields>(query);

  return {
    posts: entries.items,
    total: entries.total,
  };
}

// Get a single post
export async function getBlogPost(slug: string, preview = false) {
  const entries = await getClient(preview).getEntries<BlogPostFields>({
    content_type: 'blogPost',
    'fields.slug': slug,
    include: 2,
    limit: 1,
  });

  return entries.items[0] || null;
}

ISR Implementation in Next.js

// app/blog/[slug]/page.tsx
import { getBlogPost, getBlogPosts } from '@/lib/contentful';
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { notFound } from 'next/navigation';

export const revalidate = 60; // revalidate every 60 seconds

export async function generateStaticParams() {
  const { posts } = await getBlogPosts({ limit: 100 });
  return posts.map((post) => ({ slug: post.fields.slug }));
}

export default async function BlogPostPage({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getBlogPost(params.slug);
  if (!post) notFound();

  return (
    <article className="max-w-3xl mx-auto p-6">
      <h1 className="text-4xl font-bold">{post.fields.title}</h1>
      <p className="text-gray-500 mt-2">
        {new Date(post.fields.publishedAt).toLocaleDateString('en-US')}
      </p>
      <div className="prose mt-8">
        {documentToReactComponents(post.fields.body)}
      </div>
    </article>
  );
}

Summary

Combining Contentful’s API-first approach with Claude Code lets you efficiently build a flexible content management system. See the blog CMS build guide and SSR/SSG comparison for related topics.

For more on Contentful, see the official Contentful documentation.

#Claude Code #Contentful #CMS #headless CMS #content management

Level up your Claude Code workflow

50 battle-tested prompt templates you can copy-paste into Claude Code right now.

Free

Free PDF: Claude Code Cheatsheet in 5 Minutes

Key commands, shortcuts, and prompt examples on a single printable page.

Download PDF
M

About the Author

Masa

Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.