컨텐츠로 건너뛰기

Kontent.ai & Astro

Kontent.ai는 AI 기능이 지원되는 구조화되고 모듈화된 방식으로 콘텐츠를 관리할 수 있는 헤드리스 CMS입니다.

이 섹션에서는 Kontent.ai TypeScript SDK를 사용하여 Kontent.ai 프로젝트를 Astro 애플리케이션에 연결합니다.

시작하려면 다음이 필요합니다.

  1. Kontent.ai 프로젝트 - 아직 Kontent.ai 계정이 없다면 무료 회원가입 후 새 프로젝트를 생성하세요.

  2. Delivery API keys - 게시된 콘텐츠에는 Environment ID가 필요하고 초안을 가져오려면 Preview API key가 필요합니다 (선택 사항). 두 키 모두 Kontent.ai의 Environment Settings -> API keys 탭에 있습니다.

Astro에 Kontent.ai 자격 증명을 추가하려면 다음 변수를 사용하여 프로젝트 루트에 .env 파일을 만듭니다.

.env
KONTENT_ENVIRONMENT_ID=YOUR_ENVIRONMENT_ID
KONTENT_PREVIEW_API_KEY=YOUR_PREVIEW_API_KEY

이제 이러한 환경 변수를 Astro 프로젝트에서 사용할 수 있습니다.

이러한 환경 변수를 위한 TypeScript IntelliSense를 얻으려면 src/ 디렉터리에 새 env.d.ts 파일을 만들고 다음과 같이 ImportMetaEnv를 구성할 수 있습니다.

src/env.d.ts
interface ImportMetaEnv {
readonly KONTENT_ENVIRONMENT_ID: string;
readonly KONTENT_PREVIEW_API_KEY: string;
}

이제 루트 디렉터리에 다음과 같은 새 파일이 포함되어야 합니다.

  • Directorysrc/
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

Astro를 Kontent.ai 프로젝트와 연결하려면 Kontent.ai TypeScript SDK를 설치하세요.

Terminal window
npm install @kontent-ai/delivery-sdk

다음으로 Astro 프로젝트의 src/lib/ 디렉터리에 kontent.ts라는 새 파일을 만듭니다.

src/lib/kontent.ts
import { createDeliveryClient } from "@kontent-ai/delivery-sdk";
export const deliveryClient = createDeliveryClient({
environmentId: import.meta.env.KONTENT_ENVIRONMENT_ID,
previewApiKey: import.meta.env.KONTENT_PREVIEW_API_KEY,
});

이 구현은 .env 파일의 자격 증명을 사용하여 새로운 DeliveryClient 객체를 생성합니다.

마지막으로 Astro 프로젝트의 루트 디렉터리에는 이제 다음과 같은 새 파일이 포함되어야 합니다.

  • Directorysrc/
    • Directorylib/
      • kontent.ts
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

이제 DeliveryClient를 모든 컴포넌트에서 사용할 수 있습니다. 콘텐츠를 가져오려면 DeliveryClient와 메서드 체이닝을 사용하여 원하는 항목을 정의하세요. 이 예시에서는 블로그 게시물의 기본 가져오기를 보여주고 제목을 목록으로 렌더링합니다.

src/pages/index.astro
---
import { deliveryClient } from "../lib/kontent";
const blogPosts = await deliveryClient
.items()
.type("blogPost")
.toPromise()
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Astro</title>
</head>
<body>
<ul>
{blogPosts.data.items.map(blogPost => (
<li>{blogPost.elements.title.value}</li>
))}
</ul>
</body>
</html>

Kontent.ai 문서에서 더 많은 쿼리 옵션을 찾을 수 있습니다.

Astro와 Kontent.ai로 블로그 만들기

섹션 제목: Astro와 Kontent.ai로 블로그 만들기

위 설정을 통해 이제 Kontent.ai를 콘텐츠 소스로 사용하는 블로그를 만들 수 있습니다.

  1. Kontent.ai 프로젝트 - 이 튜토리얼에서는 빈 프로젝트를 사용하는 것이 좋습니다. 콘텐츠 모델에 이미 일부 콘텐츠 타입이 있는 경우 이를 사용할 수 있지만 콘텐츠 모델에 맞게 코드 조각을 수정해야 합니다.

  2. Kontent.ai에서 콘텐츠를 가져오도록 구성된 Astro 프로젝트 - Kontent.ai를 사용하여 Astro 프로젝트를 설정하는 방법에 대한 자세한 내용은 위를 참조하세요.

Kontent.ai에서 Content model로 이동하여 다음 필드와 값을 사용하여 새 콘텐츠 타입을 만듭니다.

  • Name: Blog Post
  • Elements:
    • Text field
      • Name: Title
      • Element Required: yes
    • Rich text field
      • Name: Teaser
      • Element Required: yes
      • Allowed in this element: only check Text
    • Rich text field
      • Name: Content
      • Element Required: yes
    • Date & time field
      • Name: Date
    • URL slug field
      • Name: URL slug
      • Element Required: yes
      • Auto-generate from: select “Title”

그런 다음 Save Changes을 클릭하세요.

이제 Content & assets 탭으로 이동하여 Blog Post 타입의 새 콘텐츠 항목을 만듭니다. 다음 값을 사용하여 필드를 채웁니다.

  • Content item name: Astro
  • Title: Astro is amazing
  • Teaser: Astro is an all-in-one framework for building fast websites faster.
  • Content: You can use JavaScript to implement the website functionality, but no client bundle is necessary.
  • Date & time: select today
  • URL slug: astro-is-amazing

완료되면 상단의 Publish 버튼을 사용하여 블로그 게시물을 게시하세요.

참고: 다음 단계로 넘어가기 전에 원하는 만큼 블로그 게시물을 작성해 보세요.

TypeScript에서 콘텐츠 모델 생성

섹션 제목: TypeScript에서 콘텐츠 모델 생성

다음으로 콘텐츠 모델에서 TypeScript 타입을 생성합니다.

먼저 Kontent.ai JS model generator, ts-node, dotenv를 설치합니다.

Terminal window
npm install @kontent-ai/model-generator ts-node dotenv

그런 다음 package.json에 다음 스크립트를 추가합니다.

package.json
{
...
"scripts": {
...
"regenerate:models": "ts-node --esm ./generate-models.ts"
},
}

타입에는 공개 API에서 사용할 수 없는 프로젝트에 대한 구조적 정보가 필요하므로 .env 파일에 콘텐츠 관리 API key도 추가해야 합니다. Environment settings -> API keys -> Management API에서 키를 생성할 수 있습니다.

.env
KONTENT_ENVIRONMENT_ID=YOUR_ENVIRONMENT_ID
KONTENT_PREVIEW_API_KEY=YOUR_PREVIEW_API_KEY
KONTENT_MANAGEMENT_API_KEY=YOUR_MANAGEMENT_API_KEY

마지막으로, 모델을 생성하도록 모델 생성기를 구성하는 generate-models.ts 스크립트를 추가합니다.

generate-models.ts
import { generateModelsAsync, textHelper } from '@kontent-ai/model-generator'
import { rmSync, mkdirSync } from 'fs'
import * as dotenv from 'dotenv'
dotenv.config()
const runAsync = async () => {
rmSync('./src/models', { force: true, recursive: true })
mkdirSync('./src/models')
// 작업 디렉토리를 models로 변경
process.chdir('./src/models')
await generateModelsAsync({
sdkType: 'delivery',
apiKey: process.env.KONTENT_MANAGEMENT_API_KEY ?? '',
environmentId: process.env.KONTENT_ENVIRONMENT_ID ?? '',
addTimestamp: false,
isEnterpriseSubscription: false,
})
}
// 스스로 호출되는 비동기 함수
;(async () => {
await runAsync()
})().catch(err => {
console.error(err)
throw err
})

이제 실행해 보세요.

Terminal window
npm run regenerate:models

블로그 게시물 목록 표시하기

섹션 제목: 블로그 게시물 목록 표시하기

이제 일부 콘텐츠를 가져올 준비가 되었습니다. 모든 블로그 게시물 목록을 표시하려는 Astro 페이지로 이동합니다 (예: src/pages의 홈페이지 index.astro).

Astro 페이지의 프런트매터에 있는 모든 블로그 게시물을 가져옵니다:

src/pages/index.astro
---
import { deliveryClient } from '../lib/kontent';
import type { BlogPost } from '../models';
import { contentTypes } from '../models/project/contentTypes';
const blogPosts = await deliveryClient
.items<BlogPost>
.type(contentTypes.blog_post.codename)
.toPromise()
---

모델 생성을 건너뛴 경우 타입이 지정되지 않은 객체와 문자열 리터럴을 사용하여 타입을 정의할 수도 있습니다.

const blogPosts = await deliveryClient
.items()
.type("blogPost")
.toPromise()

fetch 호출은 data.items의 모든 블로그 게시물 목록을 포함하는 response 객체를 반환합니다. Astro 페이지의 HTML 섹션에서 map() 함수를 사용하여 블로그 게시물을 나열할 수 있습니다.

src/pages/index.astro
---
import { deliveryClient } from '../lib/kontent';
import type { BlogPost } from '../models';
import { contentTypes } from '../models/project/contentTypes';
const blogPosts = await deliveryClient
.items<BlogPost>
.type(contentTypes.blogPost.codename)
.toPromise()
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Astro</title>
</head>
<body>
<h1>Blog posts</h1>
<ul>
{blogPosts.data.items.map(blogPost => (
<li>
<a href={`/blog/${blogPost.elements.url_slug.value}/`} title={blogPost.elements.title.value}>
{blogPost.elements.title.value}
</a>
</li>
))}
</ul>
</body>
</html>

개별 블로그 게시물 생성

섹션 제목: 개별 블로그 게시물 생성

튜토리얼의 마지막 단계는 자세한 블로그 게시물 페이지를 생성하는 것입니다.

이 섹션에서는 Astro와 함께 정적 (SSG) 모드를 사용합니다.

먼저, /src/pages/blog/[slug].astro 파일을 생성합니다. 이 파일은 CMS로부터 모든 데이터를 가져오는 getStaticPaths 함수를 내보내야 합니다.

src/pages/blog/[slug].astro
---
import { deliveryClient } from '../../lib/kontent';
import type { BlogPost } from '../../models';
import { contentTypes } from '../../models/project/contentTypes';
export async function getStaticPaths() {
const blogPosts = await deliveryClient
.items<BlogPost>()
.type(contentTypes.blog_post.codename)
.toPromise()
---

지금까지 이 함수는 Kontent.ai에서 모든 블로그 게시물을 가져왔습니다. 코드 조각은 홈 페이지에서 사용한 것과 정확히 동일합니다.

다음으로 함수는 각 블로그 게시물의 경로와 데이터를 내보내야 합니다. 파일 이름을 [slug].astro로 지정했으므로 URL 슬러그를 나타내는 매개변수를 slug라고 합니다.

src/pages/blog/[slug].astro
---
import { deliveryClient } from '../../lib/kontent';
import type { BlogPost } from '../../models';
import { contentTypes } from '../../models/project/contentTypes';
export async function getStaticPaths() {
const blogPosts = await deliveryClient
.items<BlogPost>()
.type(contentTypes.blog_post.codename)
.toPromise()
return blogPosts.data.items.map(blogPost => ({
params: { slug: blogPost.elements.url_slug.value },
props: { blogPost }
}))
}
---

마지막 부분은 HTML 템플릿을 제공하고 각 블로그 게시물을 표시하는 것입니다.

src/pages/blog/[slug].astro
---
import { deliveryClient } from '../../lib/kontent';
import type { BlogPost } from '../../models';
import { contentTypes } from '../../models/project/contentTypes';
export async function getStaticPaths() {
const blogPosts = await deliveryClient
.items<BlogPost>()
.type(contentTypes.blog_post.codename)
.toPromise()
return blogPosts.data.items.map(blogPost => ({
params: { slug: blogPost.elements.url_slug.value },
props: { blogPost }
}))
}
const blogPost: BlogPost = Astro.props.blogPost
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>{blogPost.elements.title.value}</title>
</head>
<body>
<article>
<h1>{blogPost.elements.title.value}</h1>
<Fragment set:html={blogPost.elements.teaser.value} />
<Fragment set:html={blogPost.elements.content.value} />
<time>{new Date(blogPost.elements.date.value ?? "")}</time>
</body>
</html>

Astro 미리보기 (기본적으로 http://localhost:4321/blog/astro-is-amazing/)로 이동하여 렌더링된 블로그 게시물을 확인하세요.

SSR 모드를 선택한 경우 동적 경로를 사용하여 Kontent.ai에서 페이지 데이터를 가져옵니다.

/src/pages/blog/[slug].astro라는 새 파일을 만들고 다음 코드를 추가하세요. 데이터 가져오기는 이전 사용 사례와 매우 유사하지만 사용된 URL을 기반으로 올바른 블로그 게시물을 찾을 수 있는 equalsFilter를 추가합니다.

src/pages/blog/[slug].astro
---
import { deliveryClient } from '../../lib/kontent';
import type { BlogPost } from '../../models';
import { contentTypes } from '../../models/project/contentTypes';
const { slug } = Astro.params
let blogPost: BlogPost;
try {
const data = await deliveryClient
.items<BlogPost>()
.equalsFilter(contentTypes.blog_post.elements.url_slug.codename, slug ?? '')
.type(contentTypes.blog_post.codename)
.limitParameter(1)
.toPromise()
blogPost = data.data.items[0]
} catch (error) {
return Astro.redirect('/404')
}
---

생성된 타입을 사용하지 않는 경우 대신 문자열 리터럴을 사용하여 콘텐츠 항목 타입과 필터링된 요소 코드명을 정의할 수 있습니다.

const data = await deliveryClient
.items()
.equalsFilter("url_slug", slug ?? '')
.type("blog_post")
.limitParameter(1)
.toPromise()

마지막으로 HTML 코드를 추가하여 블로그 게시물을 렌더링합니다. 이 부분은 정적 생성과 동일합니다.

src/pages/blog/[slug].astro
---
import { deliveryClient } from '../../lib/kontent';
import type { BlogPost } from '../../models';
import { contentTypes } from '../../models/project/contentTypes';
const { slug } = Astro.params
let blogPost: BlogPost;
try {
const data = await deliveryClient
.items<BlogPost>()
.equalsFilter(contentTypes.blog_post.elements.url_slug.codename, slug ?? '')
.type(contentTypes.blog_post.codename)
.limitParameter(1)
.toPromise()
blogPost = data.data.items[0]
} catch (error) {
return Astro.redirect('/404')
}
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>{blogPost.elements.title.value}</title>
</head>
<body>
<article>
<h1>{blogPost.elements.title.value}</h1>
<Fragment set:html={blogPost.elements.teaser.value} />
<Fragment set:html={blogPost.elements.content.value} />
<time>{new Date(blogPost.elements.date.value ?? '')}</time>
</body>
</html>

웹사이트를 배포하려면 배포 가이드를 방문하여 선호하는 호스팅 제공업체의 지침을 따르세요.

Kontent.ai 변경에 따른 재빌드

섹션 제목: Kontent.ai 변경에 따른 재빌드

프로젝트가 Astro의 기본 정적 모드를 사용하는 경우 콘텐츠가 변경될 때 새 빌드를 트리거하도록 웹후크를 설정해야 합니다. Netlify 또는 Vercel을 호스팅 공급자로 사용하는 경우 해당 웹후크 기능을 사용하여 Kontent.ai 이벤트에서 새 빌드를 트리거할 수 있습니다.

Netlify에서 웹후크를 설정하려면:

  1. 사이트 대시보드로 이동하여 Build & deploy를 클릭합니다.
  2. Continuous Deployment 탭에서 Build hooks 섹션을 찾아 Add build hook를 클릭합니다.
  3. 웹후크의 이름을 제공하고 빌드를 트리거할 브랜치를 선택합니다. Save을 클릭하고 생성된 URL을 복사하세요.

Vercel에서 웹후크를 설정하려면 다음 안내를 따르세요.

  1. 프로젝트 대시보드로 이동하여 Settings을 클릭합니다.
  2. Git 탭에서 Deploy Hooks 섹션을 찾습니다.
  3. 빌드를 트리거할 웹후크와 브랜치의 이름을 제공합니다. Add를 클릭하고 생성된 URL을 복사합니다.

Kontent.ai에 웹후크 추가

Kontent.ai 앱에서 Environment settings -> Webhooks로 이동합니다. Create new webhook를 클릭하고 새 웹후크의 이름을 입력하세요. Netlify 또는 Vercel에서 복사한 URL을 붙여넣고 웹후크를 트리거할 이벤트를 선택하세요. 기본적으로 게시된 콘텐츠가 변경될 때 사이트를 다시 빌드하려면 Delivery API triggers 아래에 PublishUnpublish 이벤트만 필요합니다. 완료되면 저장을 클릭하세요.

이제 Kontent.ai에 새 블로그 게시물을 게시할 때마다 새 빌드가 실행되고 블로그가 업데이트됩니다.

더 많은 CMS 안내서