Table of Contents
Overview
In the previous post Astro Installation and Project Setup, we completed the basic project configuration. In this post, I’ll explain how to manage blog content using Content Collections, one of Astro’s core features, and the process of migrating markdown files from Jekyll.
What are Content Collections
Content Collections is a feature in Astro for structuring and managing content files like markdown and MDX. Key features include:
- Type Safety: Validates frontmatter with
Zodschemas to detect errors at build time - Automatic Type Generation: TypeScript types are automatically generated, supporting IDE autocompletion
- Flexible Loader: Flexibly loads files using glob patterns
- Built-in API: Easily query content with APIs like
getCollection()andgetEntry()
Schema Definition
The Content Collections schema is defined in the src/content.config.ts file. Here’s the schema used in this blog:
// 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 };
Schema Field Descriptions
Each field in the schema serves the following role:
| Field | Type | Required | Description |
|---|---|---|---|
title | string | O | Post title |
description | string | O | SEO description (recommended under 160 characters) |
lang | enum | O | Language code (ja, ko, en) |
category | string | O | Category slug (e.g., astro, react) |
permalink | string | O | URL path (e.g., /astro/installation/) |
image | string | - | Featured image path |
comments | boolean | - | Whether to show comments (default: true) |
date | date | O | Creation date |
published | boolean | - | Whether to publish (default: true) |
Key Points
glob Loader
loader: glob({ pattern: '**/*.md', base: './src/content' }),
Recursively loads all .md files under src/content/. You can freely organize subdirectory structures by category.
z.coerce.date()
date: z.coerce.date(),
Uses z.coerce.date() instead of z.date(). This automatically converts dates written as strings in frontmatter ('2026-04-15') into Date objects. It’s convenient because you don’t need to change the date format when migrating from Jekyll.
Restricting Languages with z.enum
lang: z.enum(['ja', 'ko', 'en']),
By restricting the lang field with enum, a build-time error occurs if an unsupported language code is entered.
Directory Structure and Naming Conventions
Directory Structure
Post files are organized into category-specific directories under 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/
│ └── ...
└── ...
Naming Conventions
The naming conventions for directories and files are as follows:
- Directory name:
YYYY-MM-DD-slug/format including date and slug - File name:
index-{lang}.mdformat to distinguish files by languageindex-ja.md— Japaneseindex-ko.md— Koreanindex-en.md— English
The advantage of this structure is that multilingual files for a single post are grouped in the same directory, making management convenient.
Frontmatter Migration from Jekyll
Jekyll’s Frontmatter
Here’s the frontmatter format used in Jekyll:
---
layout: post
title: Jekyll Installation
description: Let's install Jekyll on Mac/Windows and create a basic project to start a Jekyll blog.
image: /assets/images/category/jekyll/install.jpg
categories: jekyll
date: 2018-09-08
---
Astro’s Frontmatter
Here’s the frontmatter format after migrating to Astro:
---
title: Jekyll Installation
description: Let's install Jekyll on Mac/Windows and create a basic project to start a Jekyll blog.
lang: en
category: jekyll
permalink: /jekyll/installation/
date: '2018-09-08'
published: false
comments: true
image: /assets/images/category/jekyll/install.jpg
---
Key Changes
Comparing Jekyll’s frontmatter with Astro’s frontmatter, here are the main differences:
| Item | Jekyll | Astro |
|---|---|---|
| Layout | layout: post | Not needed (layout specified in page) |
| Category | categories: jekyll | category: jekyll (singular) |
| Language | Managed by plugin | lang: en (explicit) |
| URL | Auto-generated | permalink: /jekyll/installation/ (explicit) |
| Date | 2018-09-08 | '2018-09-08' (string recommended) |
In Jekyll, the layout field specified which layout to use, but in Astro, layouts are imported in the page component ([...path].astro), so the layout field is not needed in frontmatter.
Category System
Blog categories are centrally managed in 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>;
}
Each category contains the following information:
- slug: Identifier used in URLs and content directories
- group: Classification such as frontend, backend, development, service, etc.
- translations: Titles and descriptions in three languages — Japanese/Korean/English
{
slug: 'astro',
group: 'frontend',
image: '/assets/images/category/astro/background.jpg',
isMainCategory: true,
permalink: '/astro/',
singlePage: false,
searchJson: 'astro',
translations: {
ja: {
title: 'Astro',
description: 'A record of migrating from Jekyll to Astro...',
seeMore: 'もっと見る',
noSearchResult: '検索結果がありません。',
},
ko: {
title: 'Astro',
description: 'A record of migrating from Jekyll to 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 Queries
Content stored in Content Collections can be queried using the getCollection() API.
Basic Usage
The getCollection() API can be used as follows:
import { getCollection } from 'astro:content';
// Get all blog posts
const allPosts = await getCollection('blog');
// Filter only English + published posts
const enPosts = await getCollection('blog', ({ data }) => {
return data.lang === 'en' && data.published !== false;
});
Filtering by Category
You can also use getCollection() to retrieve posts from a specific category:
// Get posts from a specific category
const astroPosts = await getCollection('blog', ({ data }) => {
return (
data.lang === 'en' && data.category === 'astro' && data.published !== false
);
});
// Sort by date descending
const sortedPosts = astroPosts.sort(
(a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime()
);
Using in Pages
getCollection() is used with getStaticPaths in dynamic routing pages ([...path].astro) as follows:
// src/pages/en/[...path].astro
export async function getStaticPaths() {
const posts = await getCollection('blog', ({ data }) => {
return data.lang === 'en' && data.published !== false;
});
return posts.map((post) => ({
params: { path: post.data.permalink.slice(1, -1) },
props: { post },
}));
}
The key point is removing the leading and trailing / from the permalink field to use as params.path. This allows access via URLs like /en/astro/installation/.
Conclusion
In this post, we looked at how to manage blog content using Astro’s Content Collections.
- Type-safe frontmatter management with
Zodschemas insrc/content.config.ts - Structuring multilingual posts with
YYYY-MM-DD-slug/index-{lang}.mdnaming - Key conversion points from
Jekyllfrontmatter toAstrofrontmatter - Central category management in
categories.ts - Content querying and filtering with the
getCollection()API
In the next post Multilingual (i18n) Implementation, we’ll cover how to implement a multilingual system supporting three languages — Japanese, Korean, and English.
Series Guide
This post is part of the Jekyll to Astro migration series.
- Why I Migrated from Jekyll to Astro
- Astro Installation and Project Setup
- Content Collections and Markdown Migration
- Multilingual (i18n) Implementation
- SEO Implementation
- Image Optimization — Custom rehype Plugin
- Comment System (Utterances)
- Ad Integration (Google AdSense)
- Search Implementation with Pagefind
- Layout and Component Architecture
- GitHub Pages Deployment
- Social Share Automation Script
- Troubleshooting and Tips
Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!
App promotion
Deku.Deku created the applications with Flutter.If you have interested, please try to download them for free.