ブログ記事が増えてくると、管理が大変になりますよね。Astroの「Content Collections API」を使えば、記事を src/content
ディレクトリにまとめて、安全かつ効率的に管理できます。ここではその基本的な手順を紹介します。
Content Collections を使うメリット
- 型安全性: 記事のフロントマター(タイトル、日付など)の型を定義でき、間違いを防げます。
-
管理のしやすさ: 記事データを
src/pages
から分離し、src/content
に集約できます。 - URLの柔軟性: ファイルパスに縛られず、記事ごとに任意のURLを設定できます。
- 強力なAPI: 記事データの取得や処理が簡単になります。
基本的な手順
1.コレクションの設定 (src/content/config.ts
)
まず、どのような記事データ(コレクション)を管理するか定義します。src/content/
ディレクトリ(なければ作成)に config.ts
というファイルを作成し、以下のように記述します。
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
// 'blog' という名前のコレクションを定義
const blogCollection = defineCollection({
type: 'content', // Markdown や MDX ファイルの場合
schema: z.object({ // フロントマターの型定義 (Zodを使用)
title: z.string(),
pubDate: z.coerce.date(), // 文字列を日付型に変換
description: z.string().optional(), // オプショナルな項目
tags: z.array(z.string()).optional(), // 文字列の配列 (オプショナル)
url: z.string().optional(), // ← 追加: カスタムURL用のフィールド (オプショナル)
}),
});
// 定義したコレクションをエクスポート
export const collections = {
'blog': blogCollection,
};
-
defineCollection
でblog
という名前のコレクションを定義します。 -
schema
で、各記事のMarkdownファイルに記述するフロントマターの項目(title
やpubDate
など)とその型をzod
を使って指定します。これにより、型が間違っているとエラーで教えてくれます。 -
url: z.string().optional()
はoptional() なので、フロントマターに url を書かなくてもエラーにはなりません。
2.記事ファイルの配置 (src/content/blog/
)
次に、Markdown記事ファイルを配置する場所を作成します。src/content/
ディレクトリ内に、コレクション名と同じ名前のディレクトリ(この例では blog
)を作成します。
your-project/
└── src/
├── content/
│ ├── config.ts <-- ステップ1で作成
│ └── blog/ <-- コレクション名のディレクトリを作成
│ └── my-first-post.md <-- ここに記事Markdownを置く
│ └── another-post.md
└── pages/
└── layouts/
src/content/blog/
ディレクトリ内に、Markdownファイル(例: my-first-post.md
)を作成または移動します。この際、ファイルのフロントマターは、ステップ1で定義した schema
に従っている必要があります。
---
# src/content/blog/my-first-post.md
title: '私の最初の投稿 (Content Collections版)'
pubDate: 2025-04-14
description: 'Content Collections を使って記事を追加しました。'
tags: ["Astro", "Blog"]
url: my-first-post
---
Content Collections を使うと、記事管理が楽になりますね。
注意: src/content
内のMarkdownファイルでは、以前の方法で使った layout:
フロントマターは 使用できません。レイアウトは後述するページコンポーネント側で適用します。
3.記事一覧ページの作成 (src/pages/blog/index.astro
)
blog
コレクションの記事一覧を表示するページを作成します。src/pages/blog/
ディレクトリ(なければ作成)に index.astro
ファイルを作成します。
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro'; // 例: 基本レイアウト
const posts = await getCollection('blog');
posts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
// URLを決定するヘルパー関数 (例)
function getPostUrl(post) {
if (post.data.url) {
// フロントマターにurlがあれば、ルートからのパスを返す
return `/${post.data.url}/`;
}
// なければファイルパスに基づくデフォルトのパスを返す
// post.slug は 'html/html-custom' のようになる
return `/blog/${post.slug}/`;
}
---
<BaseLayout title="ブログ一覧">
<h1>ブログ記事一覧</h1>
<ul>
{posts.map((post) => (
<li>
{/* getPostUrlヘルパーを使ってリンク先を決定 */}
<a href={getPostUrl(post)}>{post.data.title}</a>
<p>公開日: {post.data.pubDate.toLocaleDateString()}</p>
{post.data.description && <p>{post.data.description}</p>}
</li>
))}
</ul>
</BaseLayout>
-
getCollection('blog')
でblog
コレクションの記事データをすべて取得します。 - 取得した
posts
は配列なので、map
で各記事の情報を取り出し、リスト表示します。 - フロントマターのデータには
post.data.title
のようにアクセスします。 - 各記事へのリンクURLは
post.slug
(ファイル名から自動生成されるURLフレンドリーなID) を使って/blog/${post.slug}/
のように生成します。 - レイアウトは通常のAstroコンポーネントとして
<BaseLayout>
のように適用します。
4.個別記事ページの作成 (src/pages/[...slug].astro
)
個別記事を表示するページを src/pages/ ディレクトリの 直下 に作成し、ファイル名を[...slug].astro
とします。Rest parameters(...slug)
を使うことで、/my-first-post
のようなシンプルなURLと/blog/html/html-custom
のような階層のあるURLの両方をこのファイルで処理できます。
---
// src/pages/[...slug].astro (新規作成、src/pages/blog/[id].astro は削除)
import { getCollection, getEntry } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro'; // レイアウトへのパスが変わることに注意
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => {
// params.slug に設定する値を決定
let slugPath;
if (post.data.url) {
// フロントマターのurlがあれば、それをパスとする
slugPath = post.data.url;
} else {
// なければファイルパスに基づくパス (例: 'blog/html/html-custom')
// post.id は 'blog/html/html-custom.md' のようなので、
// ディレクトリ部分 + ファイル名(拡張子なし) を使う
slugPath = post.id.replace(/\.md$/, ''); // .mdを除去
}
return {
// params.slug は Astro.params.slug でアクセスでき、
// URL の '/.../' の部分の文字列になる
params: { slug: slugPath },
props: { post }, // 記事データを渡す
};
});
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<BaseLayout title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
<p>公開日: {post.data.pubDate.toLocaleDateString()}</p>
{post.data.tags && (
<p>タグ: {post.data.tags.join(', ')}</p>
)}
<hr />
<Content />
</article>
</BaseLayout>
-
getStaticPaths
関数は必須です。ここでgetCollection('blog')
を使って全記事を取得し、それぞれの記事に対してparams
(URLの一部になる) とprops
(ページコンポーネントに渡すデータ) を含むオブジェクトの配列を返します。 - ページコンポーネントのスクリプト部分 (
---
の中) でAstro.props
からpost
データを受け取ります。 -
post.render()
を実行すると、Markdown本文をレンダリングするための{ Content }
コンポーネントが得られます。 - HTMLテンプレート部分で
<Content />
を使うと、Markdownで書かれた本文が表示されます。 -
- ここでもレイアウトは
<BaseLayout>
のように適用します。
- ここでもレイアウトは
5.開発サーバーで確認
ターミナルで開発サーバーを起動します。
npm run dev
# または pnpm dev, yarn dev
ブラウザで以下のURLにアクセスして確認してみましょう。
-
記事一覧ページ:
http://localhost:4321/blog/
-
個別記事ページ:
http://localhost:4321/my-first-post/
(記事のファイル名がmy-first-post.md
の場合)
一覧ページが表示され、各記事へのリンクが正しく機能し、個別記事ページでMarkdownの内容とフロントマターの情報が表示されていれば成功です!
まとめ
Content Collections APIを使うと、最初は設定が必要ですが、記事が増えても構造的に管理でき、型の恩恵も受けられるため、本格的なブログ運営には非常に便利です。ぜひ活用してみてください。