ツール一覧

Astro Content Collections でブログを構築する

Astro ブログ フロントエンド

Markdownを書くだけでブログができる。frontmatterのtypoや必須項目の漏れはビルド時に検出されるので、本番に載せてから気づく失敗を防げる。必要なのは3つのファイルだけ。

1. スキーマを定義する

src/content/config.ts を作成する。

import { defineCollection, z } from "astro:content";

const blog = defineCollection({
  type: "content",
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    tags: z.array(z.string()).default([]),
  }),
});

export const collections = { blog };

z.coerce.date() で「2025-02-15」形式を Date 型に変換する。toLocaleDateString()getTime() が使える。

2. 記事を書く

src/content/blog/hello.mdx を作成する。

---
title: はじめての記事
description: テスト用の記事です。
pubDate: 2025-02-15
tags: ["Astro"]
---

本文。

3. 一覧ページと詳細ページ

一覧(src/pages/blog/index.astro

---
import { getCollection } from "astro:content";

const entries = await getCollection("blog");
const sorted = entries.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
---

<ul>
  {sorted.map((entry) => (
    <li>
      <a href={`/blog/${entry.slug}/`}>{entry.data.title}</a>
      <time>{entry.data.pubDate.toLocaleDateString("ja-JP")}</time>
    </li>
  ))}
</ul>

詳細(src/pages/blog/[...slug].astro

---
import { getCollection } from "astro:content";

export async function getStaticPaths() {
  const entries = await getCollection("blog");
  return entries.map((entry) => ({
    params: { slug: entry.slug },
    props: { entry },
  }));
}

const { entry } = Astro.props;
const { Content } = await entry.render();
---

<article>
  <h1>{entry.data.title}</h1>
  <time datetime={entry.data.pubDate.toISOString()}>
    {entry.data.pubDate.toLocaleDateString("ja-JP", { year: "numeric", month: "long", day: "numeric" })}
  </time>
  <Content />
</article>

entry.slug はファイル名から自動生成される(hello.mdxhello)。MDX 内で Callout や Table を使う場合は、<Content components={mdxComponents} /> でコンポーネントを渡す。

型安全のメリット

  • titledescription を忘れるとビルドエラー
  • 日付の形式が混在するとパースエラー
  • tags を文字列で書くと型エラー(配列必須)

よくある落とし穴

  • slug を変えたい — frontmatter に slug を定義し、スキーマで slug: z.string().optional() にする
  • 複数コレクションblogblog-en を分ける場合、collections に両方登録
  • MDX のコンポーネント<Content components={mdxComponents} /> で Callout などを渡す