Contentful & Astro
Contentful은 콘텐츠를 관리하고, 다른 서비스와 통합하고, 여러 플랫폼에 게시할 수 있는 헤드리스 CMS입니다.
Astro와 통합
섹션 제목: Astro와 통합이 섹션에서는 Contentful SDK를 사용하여 클라이언트 측 JavaScript 없이 Contentful space를 Astro에 연결합니다.
전제조건
섹션 제목: 전제조건시작하려면 다음이 필요합니다.
-
Astro 프로젝트 - 아직 Astro 프로젝트가 없다면 설치 가이드를 참조하여 즉시 설치하고 실행할 수 있습니다.
-
Contentful 계정과 Contentful space. 계정이 없다면 무료 계정에 가입하고 새로운 Contentful space를 만들 수 있습니다. 기존 space가 있는 경우 이를 사용할 수도 있습니다.
-
Contentful 자격 증명 - contentful 대시보드 Settings > API keys에서 다음 자격 증명을 찾을 수 있습니다. API 키가 없으면 Add API key를 선택하여 만듭니다.
- Contentful space ID - Contentful space의 ID입니다.
- Contentful delivery access token - Contentful space에서 게시된 콘텐츠를 소비하기 위한 액세스 토큰입니다.
- Contentful preview access token - Contentful space에서 게시되지 않은 콘텐츠를 소비하기 위한 액세스 토큰입니다.
자격 증명 설정
섹션 제목: 자격 증명 설정Astro에 Contentful space의 자격 증명을 추가하려면 다음 변수를 사용하여 프로젝트 루트에 .env
파일을 생성하세요.
CONTENTFUL_SPACE_ID=YOUR_SPACE_IDCONTENTFUL_DELIVERY_TOKEN=YOUR_DELIVERY_TOKENCONTENTFUL_PREVIEW_TOKEN=YOUR_PREVIEW_TOKEN
이제 프로젝트에서 이러한 환경 변수를 사용할 수 있습니다.
Contentful 환경 변수에 IntelliSense를 사용하려면 src/
디렉터리에 env.d.ts
파일을 만들고 다음과 같이 ImportMetaEnv
를 구성하면 됩니다.
interface ImportMetaEnv { readonly CONTENTFUL_SPACE_ID: string; readonly CONTENTFUL_DELIVERY_TOKEN: string; readonly CONTENTFUL_PREVIEW_TOKEN: string;}
Astro의 환경 변수 사용 및 .env
파일에 대해 자세히 알아보세요.
이제 루트 디렉터리에 다음과 같은 새 파일이 포함되어야 합니다.
Directorysrc/
- env.d.ts
- .env
- astro.config.mjs
- package.json
종속성 설치
섹션 제목: 종속성 설치Contentful space에 연결하려면 선호하는 패키지 관리자로 아래 단일 명령을 사용하여 다음을 모두 설치하세요.
contentful.js
, JavaScript용 공식 Contentful SDKrich-text-html-renderer
, Contentful의 리치 텍스트 필드를 HTML로 렌더링하는 패키지
npm install contentful @contentful/rich-text-html-renderer
pnpm add contentful @contentful/rich-text-html-renderer
yarn add contentful @contentful/rich-text-html-renderer
다음으로, 프로젝트의 src/lib/
디렉터리에 contentful.ts
라는 새 파일을 만듭니다.
import contentful from "contentful";
export const contentfulClient = contentful.createClient({ space: import.meta.env.CONTENTFUL_SPACE_ID, accessToken: import.meta.env.DEV ? import.meta.env.CONTENTFUL_PREVIEW_TOKEN : import.meta.env.CONTENTFUL_DELIVERY_TOKEN, host: import.meta.env.DEV ? "preview.contentful.com" : "cdn.contentful.com",});
위 코드 조각은 .env
파일에서 자격 증명을 전달하여 새로운 Contentful 클라이언트를 생성합니다.
개발 모드에서는 Contentful Preview API에서 콘텐츠를 가져옵니다. 이는 Contentful 웹 앱에서 게시되지 않은 콘텐츠를 볼 수 있음을 의미합니다.
빌드 시 Contentful Delivery API에서 콘텐츠를 가져옵니다. 이는 빌드 시 게시된 콘텐츠만 사용할 수 있음을 의미합니다.
마지막으로 루트 디렉터리에는 이제 다음과 같은 새 파일이 포함되어야 합니다.
Directorysrc/
- env.d.ts
Directorylib/
- contentful.ts
- .env
- astro.config.mjs
- package.json
데이터 가져오기
섹션 제목: 데이터 가져오기Astro 컴포넌트는 contentfulClient
를 사용하고 content_type
을 지정하여 Contentful 계정에서 데이터를 가져올 수 있습니다.
예를 들어 제목에 대한 텍스트 필드와 콘텐츠에 대한 리치 텍스트 필드가 있는 “blogPost” 콘텐츠 타입이 있는 경우 컴포넌트는 다음과 같습니다.
---import { contentfulClient } from "../lib/contentful";import { documentToHtmlString } from "@contentful/rich-text-html-renderer";import type { EntryFieldTypes } from "contentful";
interface BlogPost { contentTypeId: "blogPost", fields: { title: EntryFieldTypes.Text content: EntryFieldTypes.RichText, }}
const entries = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost",});---<body> {entries.items.map((item) => ( <section> <h2>{item.fields.title}</h2> <article set:html={documentToHtmlString(item.fields.content)}></article> </section> ))}</body>
Contentful space가 비어 있는 경우 Contentful model 설정을 확인하여 콘텐츠에 대한 기본 블로그 모델을 만드는 방법을 알아보세요.
Contentful 문서에서 더 많은 쿼리 옵션을 확인할 수 있습니다.
Astro와 Contentful로 블로그 만들기
섹션 제목: Astro와 Contentful로 블로그 만들기이제, 위 설정을 통해 Contentful을 CMS로 사용하는 블로그를 만들 수 있습니다.
전제조건
섹션 제목: 전제조건- Contentful space - 이 튜토리얼은 빈 space에서 시작하는 것이 좋습니다. 이미 콘텐츠 모델이 있는 경우 자유롭게 사용하세요. 하지만 콘텐츠 모델에 맞게 코드 조각을 수정해야 합니다.
- Contentful SDK와 통합된 Astro 프로젝트 - Contentful을 사용하여 Astro 프로젝트를 설정하는 방법에 대한 자세한 내용은 Astro와 통합을 참조하세요.
Contentful model 설정
섹션 제목: Contentful model 설정Contentful space의 Content model 섹션에서 다음 필드와 값을 사용하여 새 콘텐츠 모델을 만듭니다.
- Name: Blog Post
- API identifier:
blogPost
- Description: This content type is for a blog post
새로 생성된 콘텐츠 타입에서 Add Field 버튼을 사용하여 다음 매개변수가 포함된 5개의 새 필드를 추가합니다.
- Text field
- Name: title
- API identifier:
title
(다른 매개변수는 기본값으로 둡니다.)
- Date and time field
- Name: date
- API identifier:
date
- Text field
- Name: slug
- API identifier:
slug
(다른 매개변수는 기본값으로 둡니다.)
- Text field
- Name: description
- API identifier:
description
- Rich text field
- Name: content
- API identifier:
content
변경사항을 저장하려면 Save을 클릭하세요.
Contentful space의 Content 섹션에서 Add Entry 버튼을 클릭하여 새 항목을 만듭니다. 그런 다음 다음 필드를 입력합니다.
- Title:
Astro is amazing!
- Slug:
astro-is-amazing
- Description:
Astro is a new static site generator that is blazing fast and easy to use.
- Date:
2022-10-05
- Content:
This is my first blog post!
Publish를 클릭하여 항목을 저장하세요. 방금 첫 번째 블로그 게시물을 작성하셨습니다.
원하는 만큼 블로그 게시물을 추가한 다음 즐겨 사용하는 코드 편집기로 전환하여 Astro로 해킹을 시작해 보세요!
블로그 게시물 목록 표시하기
섹션 제목: 블로그 게시물 목록 표시하기BlogPost
라는 새 인터페이스를 만들어 src/lib/
디렉터리에 있는 contentful.ts
파일에 추가하세요. 이 인터페이스는 Contentful의 블로그 게시물 콘텐츠 타입 필드와 일치합니다. 이는 블로그 게시물 항목 응답의 타입을 지정하는 데 사용됩니다.
import contentful, { EntryFieldTypes } from "contentful";
export interface BlogPost { contentTypeId: "blogPost", fields: { title: EntryFieldTypes.Text content: EntryFieldTypes.RichText, date: EntryFieldTypes.Date, description: EntryFieldTypes.Text, slug: EntryFieldTypes.Text }}
export const contentfulClient = contentful.createClient({ space: import.meta.env.CONTENTFUL_SPACE_ID, accessToken: import.meta.env.DEV ? import.meta.env.CONTENTFUL_PREVIEW_TOKEN : import.meta.env.CONTENTFUL_DELIVERY_TOKEN, host: import.meta.env.DEV ? "preview.contentful.com" : "cdn.contentful.com",});
다음으로, Contentful에서 데이터를 가져올 Astro 페이지로 이동하세요. 이 예시에서는 src/pages/
디렉터리에 있는 홈 페이지 index.astro
를 사용합니다.
src/lib/contentful.ts
파일에서 BlogPost
인터페이스와 contentfulClient
를 가져옵니다.
BlogPost
인터페이스를 응답의 타입으로 전달하여 Contentful에서 콘텐츠 타입이 blogPost
인 모든 항목을 가져옵니다.
---import { contentfulClient } from "../lib/contentful";import type { BlogPost } from "../lib/contentful";
const entries = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost",});---
이 fetch 호출은 entries.items
에 블로그 게시물 배열을 반환합니다. map()
을 사용하여 반환된 데이터의 형식을 지정하는 새 배열 (posts
)을 만들 수 있습니다.
아래 예시에서는 콘텐츠 모델의 items.fields
속성을 반환하여 블로그 게시물 미리 보기를 생성하는 동시에 날짜 형식을 더 읽기 쉬운 형식으로 다시 지정합니다.
---import { contentfulClient } from "../lib/contentful";import type { BlogPost } from "../lib/contentful";
const entries = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost",});
const posts = entries.items.map((item) => { const { title, date, description, slug } = item.fields; return { title, slug, description, date: new Date(date).toLocaleDateString() };});---
마지막으로 템플릿에서 posts
을 사용하여 각 블로그 게시물의 미리보기를 표시할 수 있습니다.
---import { contentfulClient } from "../lib/contentful";import type { BlogPost } from "../lib/contentful";
const entries = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost",});
const posts = entries.items.map((item) => { const { title, date, description, slug } = item.fields; return { title, slug, description, date: new Date(date).toLocaleDateString() };});---<html lang="en"> <head> <title>My Blog</title> </head> <body> <h1>My Blog</h1> <ul> {posts.map((post) => ( <li> <a href={`/posts/${post.slug}/`}> <h2>{post.title}</h2> </a> <time>{post.date}</time> <p>{post.description}</p> </li> ))} </ul> </body></html>
개별 블로그 게시물 생성
섹션 제목: 개별 블로그 게시물 생성위와 동일한 방법을 사용하여 Contentful에서 데이터를 가져오지만, 이번에는 페이지에서 각 블로그 게시물에 대한 고유한 페이지 경로를 생성합니다.
정적 사이트 생성
섹션 제목: 정적 사이트 생성Astro의 기본 정적 모드를 사용하는 경우 동적 경로 및 getStaticPaths()
함수를 사용합니다. 이 함수는 페이지가 되는 경로 목록을 생성하기 위해 빌드 시 호출됩니다.
src/pages/posts/
에 [slug].astro
라는 새 파일을 만듭니다.
index.astro
에서 했던 것처럼 src/lib/contentful.ts
에서 BlogPost
인터페이스와 contentfulClient
를 가져옵니다.
이번에는 getStaticPaths()
함수 내에서 데이터를 가져옵니다.
---import { contentfulClient } from "../../lib/contentful";import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() { const entries = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost", });}---
그런 다음 params
및 props
속성을 사용하여 각 항목을 객체에 매핑합니다. params
속성은 페이지의 URL을 생성하는 데 사용되며 props
속성은 페이지 컴포넌트에 props로 전달됩니다.
---import { contentfulClient } from "../../lib/contentful";import { documentToHtmlString } from "@contentful/rich-text-html-renderer";import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() { const entries = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost", });
const pages = entries.items.map((item) => ({ params: { slug: item.fields.slug }, props: { title: item.fields.title, content: documentToHtmlString(item.fields.content), date: new Date(item.fields.date).toLocaleDateString(), }, })); return pages;}---
params
내부의 속성은 동적 경로의 이름과 일치해야 합니다. 파일 이름이 [slug].astro
이므로 slug
를 사용합니다.
이 예시에서 props
객체는 세 가지 속성을 페이지에 전달합니다.
- title (문자열)
- content (
documentToHtmlString
을 사용하여 HTML로 변환된 리치 텍스트 문서) - date (
Date
생성자를 사용하여 포맷)
마지막으로 props
페이지를 사용하여 블로그 게시물을 표시할 수 있습니다.
---import { contentfulClient } from "../../lib/contentful";import { documentToHtmlString } from "@contentful/rich-text-html-renderer";import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() { const { items } = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost", }); const pages = items.map((item) => ({ params: { slug: item.fields.slug }, props: { title: item.fields.title, content: documentToHtmlString(item.fields.content), date: new Date(item.fields.date).toLocaleDateString(), }, })); return pages;}
const { content, title, date } = Astro.props;---<html lang="en"> <head> <title>{title}</title> </head> <body> <h1>{title}</h1> <time>{date}</time> <article set:html={content} /> </body></html>
http://localhost:4321/로 이동하여 게시물 중 하나를 클릭하여 동적 경로가 작동하는지 확인하세요!
서버 측 렌더링
섹션 제목: 서버 측 렌더링SSR 모드를 선택한 경우 slug
매개변수를 사용하는 동적 경로를 사용하여 Contentful에서 데이터를 가져옵니다.
src/pages/posts
에 [slug].astro
페이지를 만듭니다. Astro.params
를 사용하여 URL에서 슬러그를 가져온 다음 이를 getEntries
에 전달합니다.
---import { contentfulClient } from "../../lib/contentful";import type { BlogPost } from "../../lib/contentful";
const { slug } = Astro.params;
const data = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost", "fields.slug": slug,});---
항목을 찾을 수 없으면 Astro.redirect
를 사용하여 사용자를 404 페이지로 리디렉션할 수 있습니다.
---import { contentfulClient } from "../../lib/contentful";import type { BlogPost } from "../../lib/contentful";
const { slug } = Astro.params;
try { const data = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost", "fields.slug": slug, });} catch (error) { return Astro.redirect("/404");}---
게시물 데이터를 템플릿 섹션에 전달하려면 try/catch
블록 외부에 post
객체를 만듭니다.
문서의 content
를 HTML로 변환하려면 documentToHtmlString
을 사용하고, 날짜 형식을 지정하려면 Date 생성자를 사용하세요. title
은 그대로 둘 수 있습니다. 그런 다음 post
객체에 이러한 속성을 추가하세요.
---import Layout from "../../layouts/Layout.astro";import { contentfulClient } from "../../lib/contentful";import { documentToHtmlString } from "@contentful/rich-text-html-renderer";import type { BlogPost } from "../../lib/contentful";
let post;const { slug } = Astro.params;try { const data = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost", "fields.slug": slug, }); const { title, date, content } = data.items[0].fields; post = { title, date: new Date(date).toLocaleDateString(), content: documentToHtmlString(content), };} catch (error) { return Astro.redirect("/404");}---
마지막으로 post
를 참조하여 템플릿 섹션에 블로그 게시물을 표시할 수 있습니다.
---import Layout from "../../layouts/Layout.astro";import { contentfulClient } from "../../lib/contentful";import { documentToHtmlString } from "@contentful/rich-text-html-renderer";import type { BlogPost } from "../../lib/contentful";
let post;const { slug } = Astro.params;try { const data = await contentfulClient.getEntries<BlogPost>({ content_type: "blogPost", "fields.slug": slug, }); const { title, date, content } = data.items[0].fields; post = { title, date: new Date(date).toLocaleDateString(), content: documentToHtmlString(content), };} catch (error) { return Astro.redirect("/404");}---<html lang="en"> <head> <title>{post?.title}</title> </head> <body> <h1>{post?.title}</h1> <time>{post?.date}</time> <article set:html={post?.content} /> </body></html>
사이트 게시
섹션 제목: 사이트 게시웹사이트를 배포하려면 배포 가이드를 방문하여 선호하는 호스팅 제공업체의 지침을 따르세요.
Contentful 변경에 대한 재빌드
섹션 제목: Contentful 변경에 대한 재빌드프로젝트가 Astro의 기본 정적 모드를 사용하는 경우 콘텐츠가 변경될 때 새 빌드를 트리거하도록 웹후크를 설정해야 합니다. Netlify 또는 Vercel을 호스팅 공급자로 사용하는 경우 웹후크 기능을 사용하여 Contentful 이벤트에서 새 빌드를 트리거할 수 있습니다.
Netlify
섹션 제목: NetlifyNetlify에서 웹후크를 설정하려면:
-
사이트 대시보드로 이동하여 Build & deploy를 클릭합니다.
-
Continuous Deployment 탭에서 Build hooks 섹션을 찾아 Add build hook를 클릭합니다.
-
웹후크의 이름을 제공하고 빌드를 트리거할 브랜치를 선택합니다. Save를 클릭하고 생성된 URL을 복사하세요.
Vercel
섹션 제목: VercelVercel에서 웹후크를 설정하려면 다음 안내를 따르세요.
-
프로젝트 대시보드로 이동하여 Settings을 클릭합니다.
-
Git 탭에서 Deploy Hooks 섹션을 찾습니다.
-
빌드를 트리거할 웹후크와 브랜치의 이름을 제공합니다. Add를 클릭하고 생성된 URL을 복사합니다.
Contentful에 웹후크 추가하기
섹션 제목: Contentful에 웹후크 추가하기Contentful space settings에서 Webhooks 탭을 클릭하고 Add Webhook 버튼을 클릭하여 새 웹후크를 생성합니다. 웹후크의 이름을 제공하고 이전 섹션에서 복사한 웹후크 URL을 붙여넣습니다. 마지막으로 Save을 눌러 웹후크를 만듭니다.
이제 Contentful에 새 블로그 게시물을 게시할 때마다 새 빌드가 실행되고 블로그가 업데이트됩니다.