Claude Code로 Contentful CMS 통합하기
Claude Code로 Contentful CMS를 Next.js/Astro에 연결하고 API, 타입, 미리보기, locale, ISR 실수를 줄입니다.
통합 범위를 먼저 정한다
Contentful은 헤드리스 CMS입니다. 편집자는 Contentful에서 글, 이미지, 작성자, FAQ, CTA를 관리하고, 프런트엔드는 API로 데이터를 받아 Next.js나 Astro에서 렌더링합니다. Claude Code를 쓰면 모델 설계, SDK 설정, 타입 정의, 미리보기, 캐시 무효화까지 한 번에 다룰 수 있지만, 지시가 모호하면 작은 데모만 동작하는 코드가 나옵니다.
실무에서 자주 깨지는 부분은 Content Delivery API, Content Preview API, Content Management API의 경계입니다. 공개 글은 잘 보이는데 초안은 보이지 않거나, 한국어 페이지가 영어로 나오거나, Contentful에서 publish했는데 Next.js 페이지가 오래된 상태로 남는 문제가 생깁니다. Claude Code에게 “Contentful 붙여줘”라고만 하지 말고, API별 책임과 locale, preview, webhook까지 명시해야 합니다.
전체 블로그 CMS 구조는 Claude Code blog CMS를 참고하면 좋습니다. CMS 데이터를 제품 API로도 노출해야 한다면 Claude Code API development를, 콘텐츠를 매출 경로로 연결하려면 content funnel audit를 함께 보는 흐름이 자연스럽습니다.
API와 토큰을 섞지 않는다
Contentful의 Content Delivery API는 publish된 콘텐츠만 읽습니다. Content Preview API는 초안과 미공개 변경을 읽습니다. Content Management API는 content type 생성, entry 수정, publish 같은 관리 작업에 씁니다. 이 세 가지를 코드와 환경 변수에서 분리해야 합니다.
| API | 용도 | 토큰 | 사용 위치 |
|---|---|---|---|
| Content Delivery API | 공개된 entry와 asset 읽기 | Delivery token | SSG, ISR, 서버 컴포넌트, Astro build |
| Content Preview API | 초안과 미공개 변경 확인 | Preview token | preview 전용 서버 라우트 |
| Content Management API | 모델 생성, entry 수정, publish | Management token 또는 PAT | 로컬 스크립트, 신뢰할 수 있는 CI |
| Images API | 이미지 리사이즈와 포맷 변환 | delivery/preview 맥락 | 썸네일, OGP, 이미지 helper |
공식 JavaScript SDK 문서에서는 Preview API 사용 시 preview access token과 host: "preview.contentful.com"을 함께 지정합니다. Management API의 Personal Access Token은 사용자 권한과 연결되므로 비밀번호처럼 다뤄야 합니다. 구현 전에는 Contentful JavaScript SDK, Personal Access Tokens, environment permissions, Astro Contentful guide를 확인해 두는 편이 안전합니다.
Claude Code 프롬프트 예시
프롬프트는 구현 범위와 금지 사항을 함께 적습니다. 이렇게 하면 Claude Code가 잘못된 토큰을 클라이언트 코드에 넣었을 때 리뷰하기 쉽습니다.
이 저장소에 Contentful 블로그 통합을 추가해 주세요.
- 공개 콘텐츠는 Content Delivery API만 사용합니다.
- preview mode가 켜졌을 때만 Content Preview API를 사용합니다.
- Content Management API token은 scripts/setup-contentful-model.ts에서만 사용합니다.
- 기본 locale은 ko-KR이고 en-US도 함수 파라미터로 지원합니다.
- Next.js는 Draft Mode와 revalidatePath를 사용합니다.
- Astro는 getStaticPaths에서 빌드 시 entry를 가져옵니다.
- Preview token과 Management token은 브라우저에 노출하지 않습니다.
- 마지막에 변경 파일과 검증 명령을 정리합니다.
이 정도로 구체화하면 결과물이 작아집니다. Claude Code가 모델 생성 스크립트, fetch helper, preview route, revalidation route를 분리해서 만들 가능성이 높고, 잘못된 변경도 바로 찾을 수 있습니다.
유스케이스별로 모델을 나눈다
Contentful에서 모든 것을 하나의 Rich Text 필드에 넣으면 처음에는 빨라 보입니다. 그러나 다국어, CTA, 가격표, 검색 색인, 분석 이벤트가 들어가면 유지보수가 어려워집니다.
| 유스케이스 | 주요 필드 | 캐시 전략 | 비즈니스 목표 |
|---|---|---|---|
| 기술 블로그 | title, slug, excerpt, body, heroImage, tags, author, publishedAt | SSG + publish webhook revalidate | 무료 자료, 관련 글, 교육 상담 |
| 제품 랜딩 페이지 | headline, sections, pricingCopy, faq, ctaLabel, ctaUrl | 짧은 ISR 또는 수동 revalidate | 구매, 데모 신청, 문의 |
| 문서 사이트 | category, version, body, relatedDocs, updatedAt | SSG 중심, 검색 색인 동기화 | 유료 지원, 템플릿 |
| 다국어 뉴스 | locale별 title/body, region, canonicalSlug | locale별 빌드와 preview | 지역별 CTA와 영업 리드 |
테스트 프로젝트에서 겪은 실수는 블로그와 랜딩 페이지를 같은 blogPost 타입으로 만든 것이었습니다. 글은 잘 나왔지만 랜딩 페이지에는 가격 문구, FAQ, CTA 실험 라벨이 필요했습니다. 편집자가 버튼 문구를 본문에 직접 넣기 시작하자 클릭 측정과 디자인 통일이 어려워졌습니다. Claude Code로 나중에 정리할 수는 있지만, 처음부터 모델을 나누는 편이 낫습니다.
환경 변수와 locale을 명확히 둔다
다음처럼 .env.example을 먼저 만들면 토큰 역할이 분명해집니다.
CONTENTFUL_SPACE_ID=your_space_id
CONTENTFUL_ENVIRONMENT=master
CONTENTFUL_DEFAULT_LOCALE=ko-KR
CONTENTFUL_DELIVERY_TOKEN=delivery_token_for_published_content
CONTENTFUL_PREVIEW_TOKEN=preview_token_for_drafts
CONTENTFUL_MANAGEMENT_TOKEN=management_token_for_scripts
CONTENTFUL_PREVIEW_SECRET=random_secret_for_draft_mode
CONTENTFUL_REVALIDATE_SECRET=random_secret_for_webhooks
NEXT_PUBLIC_SITE_URL=http://localhost:3000
Preview token이나 Management token에 NEXT_PUBLIC_를 붙이면 안 됩니다. Next.js에서는 이 접두사가 붙은 값이 브라우저 번들에 들어갈 수 있습니다. Preview token은 미공개 콘텐츠를 읽을 수 있고, Management token은 공간을 수정할 수 있으므로 서버, 로컬 스크립트, 신뢰할 수 있는 CI에만 있어야 합니다.
locale도 코드에서 하드코딩하지 않는 편이 좋습니다. Contentful 프로젝트마다 ko-KR, ko, en-US 같은 설정이 다를 수 있습니다. URL이 /ko/blog/example인데 쿼리에 locale을 넘기지 않으면 기본 언어가 표시되거나 fallback 값이 나옵니다. 모든 fetch 함수는 locale 파라미터를 받아야 합니다.
타입이 있는 Contentful 클라이언트
아래 코드는 Delivery와 Preview의 차이를 preview 옵션 하나로 모읍니다. Next.js와 Astro 모두 서버 측에서 같은 함수를 사용할 수 있습니다.
// src/lib/contentful.ts
import { createClient, type EntryFieldTypes, type EntrySkeletonType } from "contentful";
type BlogPostFields = {
title: EntryFieldTypes.Symbol;
slug: EntryFieldTypes.Symbol;
excerpt: EntryFieldTypes.Text;
body: EntryFieldTypes.RichText;
heroImage: EntryFieldTypes.AssetLink;
tags: EntryFieldTypes.Array<EntryFieldTypes.Symbol>;
publishedAt: EntryFieldTypes.Date;
};
export type BlogPostSkeleton = EntrySkeletonType<BlogPostFields, "blogPost">;
function required(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
export function getContentfulClient(options: { preview?: boolean } = {}) {
const preview = options.preview ?? false;
return createClient({
space: required("CONTENTFUL_SPACE_ID"),
environment: process.env.CONTENTFUL_ENVIRONMENT ?? "master",
accessToken: preview
? required("CONTENTFUL_PREVIEW_TOKEN")
: required("CONTENTFUL_DELIVERY_TOKEN"),
host: preview ? "preview.contentful.com" : "cdn.contentful.com",
});
}
export async function getBlogPostBySlug(
slug: string,
options: { locale?: string; preview?: boolean } = {},
) {
const locale = options.locale ?? process.env.CONTENTFUL_DEFAULT_LOCALE ?? "ko-KR";
const response = await getContentfulClient(options).getEntries<BlogPostSkeleton>({
content_type: "blogPost",
"fields.slug": slug,
limit: 1,
include: 2,
locale,
});
return response.items[0] ?? null;
}
규모가 커지면 Contentful 모델에서 TypeScript 타입을 생성하고 CI에서 차이를 확인하는 것이 좋습니다. 그래도 기본 원칙은 같습니다. content type ID, field ID, locale, preview 상태를 컴포넌트 곳곳에 흩뿌리지 말고 한 계층에 모읍니다.
Next.js와 Astro에서의 선택
Next.js에서는 공개 페이지를 정적으로 만들고, 초안은 Draft Mode에서 Preview API로 읽고, Contentful Webhook은 revalidatePath로 처리하는 구성이 실용적입니다. 현재 공식 문서의 draftMode와 revalidatePath를 버전에 맞게 확인하세요. 특히 최신 App Router에서는 draftMode가 비동기 함수라는 점을 놓치면 복사한 예제가 바로 깨질 수 있습니다.
Astro는 안정적인 정적 출력에 강합니다. getStaticPaths에서 Contentful entry를 읽고 빌드 시 HTML을 만들면 빠르고 단순합니다. 대신 Contentful에서 글을 수정해도 배포된 HTML은 자동으로 바뀌지 않습니다. 배포 플랫폼의 rebuild webhook을 연결하거나, 자주 바뀌는 랜딩 페이지만 Next.js ISR로 분리하는 전략이 현실적입니다.
출시 전 실패 사례 체크
| 실패 | 증상 | 해결 |
|---|---|---|
| Preview token을 Delivery host에 사용 | 401 또는 초안 미표시 | preview 때 preview.contentful.com 사용 |
| Delivery token으로 초안 읽기 | preview 페이지가 null | Draft Mode에서 Preview API 사용 |
| Management token 노출 | 심각한 보안 문제 | scripts/CI 서버에만 보관 |
| locale 누락 | 잘못된 언어 표시 | 모든 쿼리에 locale 전달 |
| asset 미publish | 본문은 보이지만 이미지 누락 | entry와 asset 모두 publish |
| save 이벤트마다 webhook 실행 | 편집 중 캐시가 계속 비워짐 | publish/unpublish 이벤트로 제한 |
| field ID 변경 | 타입과 렌더링 코드 깨짐 | API identifier 유지 |
마지막 검증은 개발 서버만으로 끝내지 마세요. Next.js의 재검증은 프로덕션 빌드에 가까운 환경에서 확인하고, Astro는 build 결과와 rebuild hook을 봐야 합니다. Claude Code에게 “각 route가 어떤 token, host, locale, path를 사용하는지 표로 출력하라”고 지시하면 숨은 설정 오류를 빨리 찾을 수 있습니다.
수익화 CTA까지 모델에 넣는다
CMS 통합은 기술 구현만이 아니라 콘텐츠 운영과 수익 경로에도 영향을 줍니다. ctaKind, ctaLabel, ctaUrl, relatedPosts 같은 필드를 구조화하면 편집자는 문구를 바꿀 수 있고 개발자는 동일한 컴포넌트와 분석 이벤트를 유지할 수 있습니다.
기술 글은 무료 치트시트로, 팀 도입 글은 교육과 상담으로, 템플릿 중심 글은 제품 페이지로 자연스럽게 이어질 수 있습니다. 실제로 이 구성을 테스트했을 때 가장 효과가 컸던 부분은 Delivery, Preview, Management의 책임을 먼저 분리한 것입니다. 그 이후 Claude Code의 변경은 작아졌고, preview token과 locale 실수도 배포 전에 발견하기 쉬워졌습니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Obsidian 메모를 CLAUDE.md로 바꾸는 Claude Code 워크플로
Obsidian 작업 메모를 CLAUDE.md 운영 노트로 정리해 Claude Code 세션의 문맥 반복을 줄입니다.
Claude Code Revenue CTA Routing: 글에서 PDF, Gumroad, 상담으로 보내기
독자 의도에 따라 무료 PDF, Gumroad 상품, 상담으로 나누는 Claude Code CTA 설계입니다.
Claude Code 팀 인계 규칙: 리뷰 증거, 권한, 롤백, 수익 경로까지 넘기는 법
Claude Code 작업을 팀에 넘길 때 필요한 증거, 권한 규칙, 롤백, 무료 PDF, Gumroad, 상담 경로 체크리스트.