
ねつき
お兄ちゃん〜、Astroシリーズ最終回だよ♪
今日はContent Collectionsについて説明するね(´∀`)

ねつき
Markdownファイルを型安全に扱える機能だよ!
frontmatterのタイポとか、必須項目の書き忘れとか、ビルド時に教えてくれるの♪
よくあるfrontmatterの悲劇

ねつき
まず、普通のMarkdownブログでありがちな問題を見てみよう。
---
title: 今日の日記
date: 2025-12-10
tags: [日記, 雑談]
---
本文...
---
titl: 今日の日記 # ← タイポ!
date: '12月10日' # ← 形式が違う!
tag: [日記, 雑談] # ← tagsじゃなくてtag!
---

ねつき
こういうミス、普通のMarkdownだとビルド通っちゃうの(´;ω;`)
で、本番で「あれ?タイトル表示されない…」ってなる…

ねつき
そこでContent Collections!
スキーマを定義しておけば、ビルド時にエラーで教えてくれるの(≧∇≦)
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/blog' }),
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()),
description: z.string().optional(),
}),
});
export const collections = { blog };

ねつき
そうそう!Zodっていうバリデーションライブラリを使ってるの♪
これで「titleは文字列」「dateは日付」「tagsは文字列の配列」って定義できるんだ(´∀`)
タイポしたらどうなる?

ねつき
さっきのタイポ入りMarkdownでビルドしてみると…
Error: blog → 2025-12-10.md frontmatter does not match collection schema.
"title" is required.
"date" must be a valid date.
"tags" is required.

ねつき
ビルド時にちゃんと怒られる!(゚∀゚)
本番にデプロイする前に気づけるの♪
コレクションからデータを取得

ねつき
定義したコレクションはgetCollection()で取得できるよ♪
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
---
<h1>ブログ記事一覧</h1>
<ul>
{
posts.map((post) => (
<li>
<a href={`/blog/${post.id}/`}>{post.data.title}</a>
<time>{post.data.date.toLocaleDateString('ja-JP')}</time>
</li>
))
}
</ul>

ねつき
post.data.titleとかpost.data.dateとか、型補完が効くの!(≧∇≦)
VSCodeで.を打つと候補が出てくるよ♪
フィルタリングもできる

ねつき
getCollection()の第2引数でフィルタリングできるの♪
---
import { getCollection } from 'astro:content';
// 下書きを除外
const publishedPosts = await getCollection('blog', ({ data }) => {
return data.draft !== true;
});
// 特定のタグだけ
const techPosts = await getCollection('blog', ({ data }) => {
return data.tags.includes('技術');
});
---

ねつき
下書き機能とか、タグ絞り込みとか、簡単に実装できるの(´∀`)
単一エントリの取得

ねつき
1つだけ取りたいときはgetEntry()を使うよ♪
---
// src/pages/blog/[...slug].astro
import { getEntry } from 'astro:content';
const { slug } = Astro.params;
const post = await getEntry('blog', slug);
if (!post) {
return Astro.redirect('/404');
}
const { Content } = await post.render();
---
<article>
<h1>{post.data.title}</h1>
<time>{post.data.date.toLocaleDateString('ja-JP')}</time>
<Content />
</article>

お兄ちゃん
render()でMarkdownをHTMLに変換するのか。

ねつき
そうそう!<Content />コンポーネントとしてレンダリングできるの♪
このサイトでの使用例

ねつき
このサイトでもContent Collections使ってるんだよ♪
日記のスキーマはこんな感じ。
const diary = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/diary' }),
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()),
description: z.string(),
}),
});
const diaryEn = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/diary-en' }),
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()),
description: z.string(),
}),
});

ねつき
日本語版と英語版、別コレクションで管理してるの(´∀`)
だからfrontmatterの書き忘れがあったらすぐわかる♪
Zodのよく使うバリデーション
| 書き方 | 意味 |
|---|
z.string() | 文字列(必須) |
z.string().optional() | 文字列(任意) |
z.string().default('デフォルト値') | デフォルト値付き |
z.coerce.date() | 日付(文字列から変換) |
z.array(z.string()) | 文字列の配列 |
z.boolean() | 真偽値 |
z.enum(['a', 'b', 'c']) | 列挙型 |

ねつき
うん!frontmatterのdate: 2025-12-10が自動的にDateオブジェクトになるの♪
Astroシリーズまとめ
| 日付 | テーマ | 学んだこと |
|---|
| 12/8 | 基礎編 | コンポーネント、レイアウト、Islands |
| 12/9 | 動的ルーティング | [slug]、getStaticPaths |
| 12/10 | Content Collections | スキーマ定義、型安全なMarkdown |

ねつき
でしょ?(≧∇≦)
この3つを押さえれば、ブログサイトは余裕で作れるよ♪
Astro、いいフレームワークでしょ?(´∀`)

ねつき
興味が出たら公式チュートリアルもやってみてね♪
ねつきのAstro入門シリーズ、これにて完結〜!
また何か面白いテーマがあったら書くね(´∀`)