4강. 콘텐츠 컬렉션으로 마크다운 블로그 만들기
Astro 콘텐츠 컬렉션을 이용해 마크다운 파일로 글을 쓰고, 스키마로 형식을 검증하며, getStaticPaths로 글 목록과 상세 페이지를 자동 생성하는 방법을 배웁니다.

블로그 글을 하나 쓸 때마다 .astro 파일을 만드는 것은 번거롭습니다. Astro의 콘텐츠 컬렉션(Content Collections) 을 사용하면 마크다운 파일만 추가해도 글 목록과 상세 페이지가 자동으로 만들어집니다. 지금 보고 계신 이 강의 사이트도 같은 방식으로 만들어졌습니다.
콘텐츠 컬렉션이란?
src/content/ 폴더 아래에 마크다운 파일을 모아 두고, 그 파일들의 형식(제목, 날짜, 태그 등)을 스키마로 정해 두는 기능입니다. 형식이 틀린 파일이 있으면 빌드할 때 바로 오류로 알려주므로, 글이 수백 개가 되어도 안심하고 관리할 수 있습니다.
1단계: 컬렉션 정의하기
src/content.config.ts 파일을 만들고 블로그 컬렉션을 정의합니다.
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
// src/content/blog 폴더의 md, mdx 파일을 모두 읽습니다
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
// 각 파일의 frontmatter 형식
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
z는 Zod라는 검증 라이브러리입니다. z.string()은 문자열, z.coerce.date()는 날짜로 변환, .default([])는 값이 없을 때의 기본값을 뜻합니다.
2단계: 마크다운 글 쓰기
src/content/blog/first-post.md 파일을 만듭니다.
---
title: '첫 번째 글'
description: '콘텐츠 컬렉션으로 쓴 첫 글입니다.'
pubDate: 2026-09-08
tags: ['일상']
---
여기부터 본문입니다. **마크다운** 문법을 그대로 쓸 수 있습니다.
## 소제목
- 목록
- 항목
파일 맨 위 --- 사이의 영역을 frontmatter라고 하며, 1단계에서 정한 스키마와 맞아야 합니다. title을 빼먹으면 빌드 시 “title is required” 같은 오류가 나타납니다.
3단계: 글 목록 페이지
src/pages/blog/index.astro에서 getCollection()으로 모든 글을 가져옵니다.
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getCollection } from 'astro:content';
const posts = (await getCollection('blog'))
.filter((post) => !post.data.draft) // 초안 제외
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime()); // 최신순
---
<BaseLayout title="블로그">
<h1>블로그</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.id}`}>{post.data.title}</a>
<time>{post.data.pubDate.toLocaleDateString('ko-KR')}</time>
</li>
))}
</ul>
</BaseLayout>
post.id는 파일 이름에서 확장자를 뺀 값(first-post)이고, post.data에 frontmatter 내용이 들어 있습니다.
4단계: 글 상세 페이지 (동적 라우트)
src/pages/blog/[...slug].astro 파일을 만듭니다. 대괄호 파일명은 2강에서 잠깐 언급한 동적 라우트입니다.
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getCollection, render } from 'astro:content';
// 어떤 주소들을 만들지 Astro에 알려줍니다
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); // 마크다운 → HTML
---
<BaseLayout title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
<time>{post.data.pubDate.toLocaleDateString('ko-KR')}</time>
<Content />
</article>
</BaseLayout>
getStaticPaths()는 빌드 시 한 번 실행되어 “이 글들의 페이지를 만들어라”라고 알려주는 함수입니다. 글이 5개면 5개의 HTML 페이지가 생성됩니다. render(post)는 마크다운 본문을 HTML 컴포넌트로 바꿔 주며, <Content /> 자리에 렌더링됩니다.
이미지 넣기
스키마에 image() 헬퍼를 쓰면 대표 이미지도 자동으로 최적화(WebP 변환, 크기 조절)됩니다.
schema: ({ image }) =>
z.object({
title: z.string(),
heroImage: image().optional(),
}),
마크다운에서는 글 파일 기준 상대 경로로 적습니다.
heroImage: ./images/first-post.png
정리
src/content.config.ts에서 컬렉션과 스키마를 정의하면 마크다운 형식이 자동으로 검증됩니다.getCollection('blog')로 글 목록을 가져오고,[...slug].astro+getStaticPaths()로 상세 페이지를 만듭니다.- 새 글은 마크다운 파일 하나만 추가하면 끝입니다.
다음 강의에서는 만든 사이트가 구글과 네이버에 잘 검색되도록 SEO를 설정하고 실제로 배포해 봅니다.