Markdownを型安全に!AstroのContent Collections入門♪

#技術#Astro#チュートリアル#Web制作#初心者向け
ねつきのトーク
ねつき
ねつき
Markdownを型安全に!AstroのContent Collections入門♪
ねつき
ねつき

お兄ちゃん〜、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 };
お兄ちゃん
お兄ちゃん

z.objectってZodか。

ねつき
ねつき

そうそう!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のよく使うバリデーション

ねつき
ねつき

Zodで使える便利なバリデーションをまとめるね♪

書き方意味
z.string()文字列(必須)
z.string().optional()文字列(任意)
z.string().default('デフォルト値')デフォルト値付き
z.coerce.date()日付(文字列から変換)
z.array(z.string())文字列の配列
z.boolean()真偽値
z.enum(['a', 'b', 'c'])列挙型
お兄ちゃん
お兄ちゃん

coerceは型変換してくれるのか。

ねつき
ねつき

うん!frontmatterのdate: 2025-12-10が自動的にDateオブジェクトになるの♪


Astroシリーズまとめ

ねつき
ねつき

3日間のAstroシリーズ、振り返ってみよう♪

日付テーマ学んだこと
12/8基礎編コンポーネント、レイアウト、Islands
12/9動的ルーティング[slug]getStaticPaths
12/10Content Collectionsスキーマ定義、型安全なMarkdown
お兄ちゃん
お兄ちゃん

基礎から応用まで一通り学べたな。

ねつき
ねつき

でしょ?(≧∇≦)

この3つを押さえれば、ブログサイトは余裕で作れるよ♪

  • コンポーネントで共通パーツ管理

  • 動的ルーティングでページ生成

  • Content Collectionsで型安全なコンテンツ管理

Astro、いいフレームワークでしょ?(´∀`)

お兄ちゃん
お兄ちゃん

確かに良くできてる。

ねつき
ねつき

興味が出たら公式チュートリアルもやってみてね♪

ねつきのAstro入門シリーズ、これにて完結〜!

また何か面白いテーマがあったら書くね(´∀`)

お兄ちゃん
お兄ちゃん

お疲れ様。

ねつき
ねつき

えへへ〜、読んでくれてありがとう♪(〃´∪`〃)

♪ 拍手 ♪
0 拍手