4강. 콘텐츠 컬렉션으로 마크다운 블로그 만들기

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

·

Astro 강의 4강 - 콘텐츠 컬렉션과 마크다운 블로그

블로그 글을 하나 쓸 때마다 .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를 설정하고 실제로 배포해 봅니다.