핵심 요약
- Astro v5+부터 Content Collections는
glob()로더 기반으로 재설계됐습니다. - Zod 스키마로 마크다운 프런트매터를 빌드 시점에 타입 검증합니다.
- 잘못된 프런트매터가 있으면
astro build가 실패하므로 런타임 오류를 사전에 방지합니다.
Astro v5+ glob 로더 API
Astro v4까지는 src/content/config.ts에서 컬렉션을 정의했습니다. v5부터는 파일 이름이 src/content.config.ts로 바뀌었고, 로더(loader) 방식이 도입됐습니다.
// src/content.config.ts (v5+ 방식)
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
}),
});
export const collections = { blog };
v4 이하와의 차이점:
type: 'content'대신loader: glob(...)사용- 파일 경로가
src/content/config.ts에서src/content.config.ts로 변경 - 로더를 커스텀 구현하거나 외부 API에서 데이터를 가져오는 것도 가능해짐
Zod 스키마로 프런트매터 검증
Zod는 TypeScript-first 스키마 검증 라이브러리입니다. Astro는 astro/zod에서 Zod를 re-export하므로 별도 설치 없이 사용할 수 있습니다.
기본 타입 검증
const blog = defineCollection({
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
schema: z.object({
title: z.string(), // 필수 문자열
description: z.string(), // 필수 문자열
pubDate: z.coerce.date(), // 문자열을 Date로 변환
updatedDate: z.coerce.date().optional(), // 선택 필드
category: z.enum(['프로젝트', '에세이', 'TIL']), // 열거형
tags: z.array(z.string()).default([]), // 기본값 있는 배열
draft: z.boolean().default(false), // 기본값 있는 불리언
schemaType: z.enum(['article', 'faq', 'howto']).default('article'),
}),
});
z.coerce.date()는 마크다운 프런트매터의 2026-04-04 같은 문자열을 자동으로 JavaScript Date 객체로 변환합니다. z.coerce 없이 z.date()를 쓰면 YAML 파싱 결과에 따라 변환이 안 될 수 있습니다.
이미지 필드 처리
coverImage 같은 이미지 경로는 Astro의 이미지 최적화 파이프라인과 통합하기 위해 특별한 처리가 필요합니다:
const blog = defineCollection({
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
schema: ({ image }) =>
z.object({
title: z.string(),
coverImage: z.optional(image()), // image() 헬퍼 사용
}),
});
schema가 함수 형태일 때 { image } 헬퍼를 받아 사용합니다. image()는 상대 경로를 검증하고 <Image /> 컴포넌트와 통합됩니다.
Content Collections 사용 방법
컬렉션을 정의한 후 페이지에서 데이터를 가져오는 방법입니다.
모든 포스트 목록
// src/pages/blog/index.astro
---
import { getCollection } from 'astro:content';
const posts = await getCollection('blog', ({ data }) => {
return data.draft !== true; // 드래프트 제외
});
// 날짜 내림차순 정렬
const sortedPosts = posts.sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);
---
개별 포스트 렌더링
// src/pages/blog/[slug].astro
---
import { getCollection, render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
const post = Astro.props;
const { Content } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
v5+에서는 post.slug 대신 post.id를 사용합니다. 또한 마크다운을 HTML로 변환하려면 entry.render() 대신 render(entry)를 import해서 사용합니다.
빌드 시점 타입 검증의 이점
Zod 스키마 덕분에 마크다운 파일의 프런트매터가 잘못됐을 때 astro build 또는 astro dev 실행 시 즉시 오류가 발생합니다:
[ERROR] Invalid content entry frontmatter.
src/content/blog/my-post.md
> "category" must be one of '프로젝트' | '에세이' | 'TIL'
런타임에 undefined로 조용히 실패하는 대신 빌드가 실패하므로 배포 전에 문제를 발견할 수 있습니다. TypeScript와 조합하면 post.data.title의 타입이 자동으로 string으로 추론됩니다.