AstroでMDファイルの管理にContent Collectionsを利用する

AstroでMDファイルの管理にContent Collectionsを利用する

Content Collections は Astro 2.0で追加されたMarkdownファイルやMDXファイルを型安全に管理できる仕組みです。

Astroは標準でMarkdownファイルとMDXファイルの管理機能がありContent Collectionsが追加される前でも取り扱うことができました。
参考:MarkdownとMDX 🚀 Astroドキュメント

Content Collectionsでは従来の管理に加えてファイル内の型情報を定義することができるようになり、ファイルの記述内容が間違っているからビルドが失敗するなどのミスを事前に防ぐことが可能になりました。

実際に簡単なサンプルを作成しながらContent Collectionsを利用方法を解説します。

サンプルの説明

今回作成するサンプルは一覧と詳細があるシンプルなページ構成です。

一覧でタイトルを押下すると詳細に遷移して詳細には本文やタグ一覧が表示されています。

サンプルの一覧ページ
詳細ページ

MarkdownファイルとMDXファイルの配置

Content Collectionsでは src ディレクトリの直下に contentディレクトリを配置します。contentディレクトリはContent Collectionsのために用意さているディレクトリ名なので他の用途で利用することはできません。

contentディレクトリの内側にコレクションディレクトリを作成します。コレクションディレクトリはMarkdownファイルやMDXファイルをまとめる役目をします。例えばブログ記事の一覧をファイルとして管理したいならblogなどとすると良いでしょう。

そしてコレクションディレクトリの中にMarkdownファイルやMDXファイルを配置します。

そうすると以下のような配置になります。

src
└── content
    └── blog
        ├── post-1.md
        ├── post-2.md
        └── post-3.md

このタイミングでnpm run devなどのコマンドでastro devを実行します。そうするとルートディクレクトリに.astroディレクトリが作成されます。

今回、利用するMDファイルは以下のようなシンプルなものです。

---
title: "タイトル1"
tags: ["tag1", "tag2", "tag3" ]
pubDate: 2023-02-02
slug: "post1"
---

## 見出し

本文本文本文本文本文本文本文本文

ファイルのバリデーション

ファイルの設定は conetntディレクトリ直下にconfig.tsというファイルを配置してそちらに記述していきます。

// Import utilities from `astro:content`
import { z, defineCollection } from "astro:content";
// Define a schema for each collection you'd like to validate.
const blogCollection = defineCollection({
    schema: z.object({
      title: z.string(),
      tags: z.array(z.string()),
      pubDate: z.date()
    })
});
// Export a single `collections` object to register your collection(s)
export const collections = {
    blog: blogCollection,
};

バリデーションにはZodが利用できるので直感的に指定できます。

今回は

  • titleは文字列
  • tagsは文字列の配列でオプショナル
  • pubDateは日付

であることを宣言していします。

一覧で表示

作成したマークダウンを一覧で表示してみましょう。getCollectionにコレクション名を指定すると一覧を取得することができます。

マークダウンで指定した項目はdataオブジェクトに格納されています。

---
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
---


<html lang="ja">
	<head>
		<meta charset="utf-8" />
		<title>Astro</title>
	</head>
	<body>
		<ul>
			{posts.map(post => (
			  <li>
				<time datetime={post.data.pubDate.toISOString()}>
				  {new Intl.DateTimeFormat('ja-JP').format(post.data.pubDate)}
				</time>
				<br />
				<a href={`/${post.slug}`}>{post.data.title}</a>
			  </li>
			))}
		  </ul>
	</body>
</html>

ここでポイントは取得した一覧に型情報が追加されていることです。

詳細で表示

詳細の情報はgetEntryBySlugでコレクション名とファイル名を同士に指定すると取得ができます。

import { getEntryBySlug } from 'astro:content';
const entry = await getEntryBySlug('blog', 'post-1');

ファイルごとにページが存在する場合はgetStaticPathsと合わせて指定すると思うので以下のように指定を行います。ファイル名は pages/[slug].astroとしています。

マークダウンの型情報はCollectionEntry<'blog'>で取得できますので取得した型情報をinterface Propsに指定をしています。

本文の情報はconst { Content } = await post.render()で取得ができます。

---
import { getCollection,CollectionEntry } from 'astro:content';
export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map(post => ({
    params: { slug: post.slug }, props: { post },
  }));
}
interface Props {
  post: CollectionEntry<'blog'>;
}
const { post } = Astro.props;
const { Content } = await post.render();
---

<html lang="ja">
	<head>
		<meta charset="utf-8" />
		<title>Astro</title>
	</head>
	<body>
		<h1>{post.data.title}</h1>
		<time datetime={post.data.pubDate.toISOString()}>
			{new Intl.DateTimeFormat('ja-JP').format(post.data.pubDate)}
		</time>
		<Content />
		{post.data.tags && post.data.tags.map((tag:any) => (
			<strong>{tag}</strong>
		))}
	</body>
</html>

CollectionEntryでコレクションの型情報が取得できますのでinterface Propsとして指定を行っておけば本文中で型情報を利用することができるようになっています。

最終的なコードはGitHubに配置しておりますので興味がある方は確認してみてください。
https://github.com/to-r/media-astoro-Content-Collections