Markdown의 장점은 단순함이지만, 글이 많아지면 단순한 파일 모음만으로는 부족해집니다. 제목 표기가 들쭉날쭉해지고, 날짜 형식이 섞이며, 목록 페이지에 필요한 설명이 빠진 글도 생깁니다.

Astro의 콘텐츠 컬렉션은 이 문제를 빌드 시점의 데이터 검증으로 해결합니다. 글쓰기 경험은 Markdown 그대로 유지하면서, frontmatter는 타입이 있는 데이터처럼 다룰 수 있습니다.

스키마는 편집 원칙이다

좋은 스키마는 모든 가능성을 담는 것이 아니라 사이트가 꾸준히 유지해야 할 최소한의 약속을 표현합니다.

const articles = defineCollection({
  loader: glob({
    base: "./src/content/articles",
    pattern: "**/*.md",
  }),
  schema: z.object({
    title: z.string().min(1),
    description: z.string().min(20).max(180),
    publishedAt: z.coerce.date(),
    updatedAt: z.coerce.date().optional(),
    category: z.enum(["learn", "guide", "thought"]),
    topics: z.array(z.string()).min(1),
    draft: z.boolean().default(false),
  }),
});

description 길이를 제한하면 검색 결과와 공유 카드에 들어갈 요약이 지나치게 짧거나 길어지는 일을 막을 수 있습니다. 날짜를 z.coerce.date()로 변환하면 정렬과 구조화 데이터 생성도 단순해집니다.

분류와 주제를 구분하기

이 사이트에서는 분류를 글의 형식으로, 주제를 글이 다루는 대상으로 사용합니다.

구분 질문 예시
분류 어떤 방식의 글인가? 배운 것, 가이드, 생각
주제 무엇에 관한 글인가? Astro, Cloudflare, 디자인

분류는 하나만 선택하게 하고, 주제는 여러 개를 허용합니다. 이 규칙 덕분에 탐색 구조가 단순해지면서도 서로 다른 글을 유연하게 연결할 수 있습니다.

파일 경로를 주소로 사용하기

별도의 slug를 매번 적는 대신 파일 경로에서 글 ID를 생성하면 중복 관리가 줄어듭니다.

src/content/articles/
├── astro-static-cloudflare.md
├── content-collections.md
└── readable-technical-writing.md

이때 파일 이름은 영문 소문자와 하이픈을 사용합니다. 제목은 언제든 다듬을 수 있지만 URL은 외부 링크가 생긴 뒤 바꾸기 어렵기 때문입니다.

초안도 같은 컬렉션에 둔다

초안과 공개 글을 다른 폴더에 나누면 이동 과정에서 이력이 끊기거나 경로가 바뀌기 쉽습니다. draft 필드를 두고 조회 단계에서 제외하면 같은 구조 안에서 안전하게 작성할 수 있습니다.

const articles = await getCollection(
  "articles",
  ({ data }) => !data.draft,
);

콘텐츠 컬렉션의 핵심 가치는 타입 자동 완성보다도, 글이 늘어날수록 편집 원칙을 자동으로 지켜 준다는 데 있습니다.