개요
이전 글 Astro 설치 및 프로젝트 구성에서 프로젝트의 기본 구성을 완료했습니다. 이번 글에서는 Astro의 핵심 기능 중 하나인 Content Collections를 이용하여 블로그 콘텐츠를 관리하는 방법과, Jekyll에서 마크다운 파일을 마이그레이션하는 과정을 설명합니다.
Content Collections란
Content Collections는 Astro에서 마크다운, MDX 등의 콘텐츠 파일을 구조화하여 관리하는 기능입니다. 주요 특징은 다음과 같습니다.
- 타입 안전성:
Zod스키마로 frontmatter를 검증하여 빌드 타임에 오류를 감지 - 자동 타입 생성: TypeScript 타입이 자동으로 생성되어 IDE에서 자동 완성 지원
- 유연한 로더: glob 패턴으로 파일을 유연하게 로드
- 빌트인 API:
getCollection(),getEntry()등의 API로 콘텐츠를 쉽게 쿼리
스키마 정의
Content Collections의 스키마는 src/content.config.ts 파일에서 정의합니다. 이 블로그에서 사용하는 스키마는 다음과 같습니다.
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content' }),
schema: z.object({
title: z.string(),
description: z.string(),
lang: z.enum(['ja', 'ko', 'en']),
category: z.string(),
permalink: z.string(),
image: z.string().optional(),
comments: z.boolean().default(true),
date: z.coerce.date(),
published: z.boolean().default(true),
}),
});
export const collections = { blog };
스키마 필드 설명
스키마의 각 필드는 다음과 같은 역할을 합니다.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
title | string | O | 포스트 제목 |
description | string | O | SEO용 설명 (160자 이내 권장) |
lang | enum | O | 언어 코드 (ja, ko, en) |
category | string | O | 카테고리 slug (예: astro, react) |
permalink | string | O | URL 경로 (예: /astro/installation/) |
image | string | - | 대표 이미지 경로 |
comments | boolean | - | 댓글 표시 여부 (기본값: true) |
date | date | O | 작성일 |
published | boolean | - | 공개 여부 (기본값: true) |
주요 포인트
glob 로더
loader: glob({ pattern: '**/*.md', base: './src/content' }),
src/content/ 아래의 모든 .md 파일을 재귀적으로 로드합니다. 카테고리별 하위 디렉토리 구조를 자유롭게 구성할 수 있습니다.
z.coerce.date()
date: z.coerce.date(),
z.date() 대신 z.coerce.date()를 사용합니다. 이렇게 하면 frontmatter에서 문자열로 작성된 날짜('2026-04-15')를 자동으로 Date 객체로 변환해줍니다. Jekyll에서 마이그레이션할 때 날짜 형식을 변경하지 않아도 되므로 편리합니다.
z.enum으로 언어 제한
lang: z.enum(['ja', 'ko', 'en']),
lang 필드를 enum으로 제한하여 지원하지 않는 언어 코드가 입력되면 빌드 타임에 에러가 발생합니다.
디렉토리 구조와 네이밍 규칙
디렉토리 구조
포스트 파일은 src/content/ 아래에 카테고리별 디렉토리로 구분합니다.
src/content/
├── astro/
│ ├── 2026-04-01-migration-reason/
│ │ ├── index-ja.md
│ │ ├── index-ko.md
│ │ └── index-en.md
│ └── 2026-04-08-installation/
│ ├── index-ja.md
│ ├── index-ko.md
│ └── index-en.md
├── react/
│ └── 2026-03-03-react-19-migration/
│ ├── index-ja.md
│ ├── index-ko.md
│ └── index-en.md
├── jekyll/
│ └── ...
└── ...
네이밍 규칙
디렉토리와 파일의 네이밍 규칙은 다음과 같습니다.
- 디렉토리명:
YYYY-MM-DD-slug/형식으로 날짜와 슬러그를 포함 - 파일명:
index-{lang}.md형식으로 언어별 파일을 구분index-ja.md— 일본어index-ko.md— 한국어index-en.md— 영어
이 구조의 장점은 하나의 포스트에 대한 다국어 파일이 같은 디렉토리에 모여있어 관리가 편하다는 것입니다.
Jekyll에서 Frontmatter 마이그레이션
Jekyll의 Frontmatter
Jekyll에서 사용하던 frontmatter 형식입니다.
---
layout: post
title: jekyll 설치
description: jekyll 블로그를 시작하기 위해, Mac/Windows에 jekyll을 설치하고 기본 프로젝트를 생성하자.
image: /assets/images/category/jekyll/install.jpg
categories: jekyll
date: 2018-09-08
---
Astro의 Frontmatter
Astro로 마이그레이션한 frontmatter 형식입니다.
---
title: jekyll 설치
description: jekyll 블로그를 시작하기 위해, Mac/Windows에 jekyll을 설치하고 기본 프로젝트를 생성하자.
lang: ko
category: jekyll
permalink: /jekyll/installation/
date: '2018-09-08'
published: false
comments: true
image: /assets/images/category/jekyll/install.jpg
---
주요 변경 사항
Jekyll의 frontmatter와 Astro의 frontmatter를 비교하면 다음과 같은 차이점이 있습니다.
| 항목 | Jekyll | Astro |
|---|---|---|
| 레이아웃 | layout: post | 불필요 (페이지에서 레이아웃 지정) |
| 카테고리 | categories: jekyll | category: jekyll (단수형) |
| 언어 | 플러그인으로 관리 | lang: ko (명시적) |
| URL | 자동 생성 | permalink: /jekyll/installation/ (명시적) |
| 날짜 | 2018-09-08 | '2018-09-08' (문자열 권장) |
Jekyll에서는 layout 필드로 사용할 레이아웃을 지정했지만, Astro에서는 페이지 컴포넌트([...path].astro)에서 레이아웃을 import하여 사용하므로 frontmatter에 layout 필드가 필요 없습니다.
카테고리 시스템
블로그의 카테고리는 src/data/categories.ts에서 중앙 집중적으로 관리합니다.
// src/data/categories.ts
export type CategoryGroup =
| 'frontend'
| 'backend'
| 'development'
| 'service'
| 'etc';
export interface Category {
slug: string;
group: CategoryGroup;
image: string;
isMainCategory: boolean;
permalink: string;
singlePage: boolean;
searchJson: string;
translations: Record<string, CategoryTranslation>;
}
각 카테고리는 다음과 같은 정보를 포함합니다.
- slug: URL과 콘텐츠 디렉토리에서 사용되는 식별자
- group: 프론트엔드, 백엔드, 개발, 서비스 등의 분류
- translations: 일본어/한국어/영어 3개 언어의 제목, 설명
{
slug: 'astro',
group: 'frontend',
image: '/assets/images/category/astro/background.jpg',
isMainCategory: true,
permalink: '/astro/',
singlePage: false,
searchJson: 'astro',
translations: {
ja: {
title: 'Astro',
description: 'JekyllからAstroへのマイグレーション記録です...',
seeMore: 'もっと見る',
noSearchResult: '検索結果がありません。',
},
ko: {
title: 'Astro',
description: 'Jekyll에서 Astro로의 마이그레이션 기록입니다...',
seeMore: '자세히 보기',
noSearchResult: '검색 결과가 없습니다.',
},
en: {
title: 'Astro',
description: 'A record of migrating from Jekyll to Astro...',
seeMore: 'see more',
noSearchResult: 'There is no search result.',
},
},
},
콘텐츠 쿼리
Content Collections에 저장된 콘텐츠는 getCollection() API로 쿼리할 수 있습니다.
기본 사용법
getCollection() API는 다음과 같이 사용할 수 있습니다.
import { getCollection } from 'astro:content';
// 모든 블로그 포스트 가져오기
const allPosts = await getCollection('blog');
// 한국어 + 공개된 포스트만 필터링
const koPosts = await getCollection('blog', ({ data }) => {
return data.lang === 'ko' && data.published !== false;
});
카테고리별 필터링
getCollection()을 사용하여 다음과 같이 특정 카테고리의 포스트만 가져올 수도 있습니다.
// 특정 카테고리의 포스트 가져오기
const astroPosts = await getCollection('blog', ({ data }) => {
return (
data.lang === 'ko' && data.category === 'astro' && data.published !== false
);
});
// 날짜 내림차순 정렬
const sortedPosts = astroPosts.sort(
(a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime()
);
페이지에서 사용
getCollection()은 다음과 같이 동적 라우팅 페이지([...path].astro)에서 getStaticPaths와 함께 사용합니다.
// src/pages/ko/[...path].astro
export async function getStaticPaths() {
const posts = await getCollection('blog', ({ data }) => {
return data.lang === 'ko' && data.published !== false;
});
return posts.map((post) => ({
params: { path: post.data.permalink.slice(1, -1) },
props: { post },
}));
}
permalink 필드의 앞뒤 /를 제거하여 params.path로 사용하는 것이 포인트입니다. 이렇게 하면 /ko/astro/installation/ 같은 URL로 접근할 수 있습니다.
완료
이번 글에서는 Astro의 Content Collections를 이용하여 블로그 콘텐츠를 관리하는 방법을 살펴보았습니다.
src/content.config.ts에서Zod스키마로 frontmatter를 타입 안전하게 관리YYYY-MM-DD-slug/index-{lang}.md네이밍으로 다국어 포스트 구조화Jekyllfrontmatter에서Astrofrontmatter로의 변환 포인트categories.ts에서 카테고리를 중앙 집중 관리getCollection()API로 콘텐츠 쿼리 및 필터링
다음 글 다국어(i18n) 구현에서는 일본어/한국어/영어 3개 언어를 지원하는 다국어 시스템 구현 방법을 다룹니다.
시리즈 안내
이 포스트는 Jekyll에서 Astro로 마이그레이션 시리즈의 일부입니다.
- Jekyll에서 Astro로 마이그레이션한 이유
- Astro 설치 및 프로젝트 구성
- Content Collections와 마크다운 마이그레이션
- 다국어(i18n) 구현
- SEO 구현
- 이미지 최적화 — 커스텀 rehype 플러그인
- 댓글 시스템 (Utterances)
- 광고 연동 (Google AdSense)
- Pagefind를 이용한 검색 구현
- 레이아웃과 컴포넌트 아키텍처
- GitHub Pages 배포
- 소셜 공유 자동화 스크립트
- 트러블슈팅과 팁
제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!
앱 홍보
Deku가 개발한 앱을 한번 사용해보세요.Deku가 개발한 앱은 Flutter로 개발되었습니다.관심있으신 분들은 앱을 다운로드하여 사용해 주시면 정말 감사하겠습니다.