コンテンツにスキップ

コンテンツコレクション

追加: astro@2.0.0

コンテンツコレクションは、Astroプロジェクトでコンテンツを管理し、オーサリングするもっとも良い方法です。コレクションはドキュメントを整理し、フロントマターを検証、すべてのコンテンツに対して自動的にTypeScriptの型安全性を提供します。

予約されているプロジェクトディレクトリsrc/contentの中にあるトップレベルのディレクトリは、1つのコンテンツコレクションを表わします。たとえばsrc/content/newslettersrc/content/authorsなどになります。src/contentディレクトリの中に入れられるのは、コンテンツコレクションだけです。このディレクトリは他のものには使えません。

コレクションエントリーは、コンテンツコレクションディレクトリ内に保存されたコンテンツのことです。エントリーには、Markdown(.md)やMDX (.mdx MDXインテグレーション (EN)を使用)などのコンテンツオーサリングフォーマットや、YAML(.yaml)やJSON(.json)などのデータフォーマットを使用できます。コンテンツの検索と整理を容易にするため、ファイルには一貫性のある命名スキーム(小文字、スペースの代わりにダッシュ)の使用をおすすめしますが、これは必須ではありません。また、ファイル名の前にアンダースコア(_)を付けることで、ビルド対象からエントリーを除外できます。

  • ディレクトリsrc/content/
    • ディレクトリnewsletter/ “newsletter”コレクション
      • week-1.md コレクションエントリー
      • week-2.md コレクションエントリー
      • week-3.md コレクションエントリー

コレクションができたら、Astroの組み込みコンテンツAPIを使ってコンテンツのクエリを始められます。

Astroは、コンテンツコレクションの重要なメタデータを、プロジェクト内の.astroディレクトリに保存します。このディレクトリを維持または更新するために、何かする必要はありません。プロジェクトでの作業中は、このディレクトリを完全に無視してください。

.astroディレクトリは、astro devコマンドやastro buildコマンドを実行すると常に自動で更新されます。必要に応じてastro syncを実行し、手動で.astroディレクトリを更新できます。

2つのファイルが異なる種類のコンテンツ(例えばブログの投稿と著者のプロフィール)を表す場合、それらは異なるコレクションに属する可能性が高いでしょう。多くの機能(フロントマターの検証、TypeScriptの自動型安全性)では、コレクション内のすべてのエントリーが同様の構造を共有する必要がありますので、これは重要です。

さまざまなタイプのコンテンツを扱うことになったら、それぞれのタイプを表す複数のコレクションを作成する必要があります。プロジェクトには、いくつでも異なるコレクションを作成できます。

  • ディレクトリsrc/content/
    • ディレクトリnewsletter/
      • week-1.md
      • week-2.md
    • ディレクトリblog/
      • post-1.md
      • post-2.md
    • ディレクトリauthors/
      • grace-hopper.json
      • alan-turing.json

コンテンツコレクションは、常にsrc/content/ディレクトリ内のトップレベルのフォルダです。コレクションを別のコレクションの中に入れ子にはできません。しかし、サブディレクトリを使ってコレクション内のコンテンツを整理できます。

たとえば、1つのdocsコレクション内で多言語の翻訳を整理するために、次のディレクトリ構造を使えます。このコレクションをクエリするとき、ファイルパスを使用して言語によって結果をフィルタできます。

  • ディレクトリsrc/content/
    • ディレクトリdocs/ このコレクションは、言語別に整理したサブディレクトリを使用
      • ディレクトリen/
      • ディレクトリes/
      • ディレクトリde/

コンテンツコレクションを最大限に活用するには、プロジェクト内にsrc/content/config.tsファイルを作成してください(.js.mjsの拡張子もサポートされています)。これは、Astroがコンテンツコレクションを設定するために自動的に読み込んで使用する特別なファイルです。

src/content/config.ts
// 1. `astro:content`からユーティリティをインポート
import { defineCollection } from 'astro:content';
// 2. コレクションを定義
const blogCollection = defineCollection({ /* ... */ });
// 3. コレクションを登録するために、単一の`collections`オブジェクトをエクスポート
// このキーは、"src/content"のコレクションのディレクトリ名と一致する必要があります。
export const collections = {
'blog': blogCollection,
};

tsconfig.jsonファイルでAstroのTypeScript設定をstrictまたはstrictestに設定していない場合は、tsconfig.jsonを更新してstrictNullChecksを有効にする必要があるかもしれません。

tsconfig.json
{
// 注意:"astro/tsconfigs/strict"または"astro/tsconfigs/strictest"を使用する場合は変更する必要はありません。
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"strictNullChecks": true
}
}

Astroプロジェクトで.jsまたは.mjsファイルを使用する場合、tsconfig.jsonallowJsを有効にすることで、インテリセンスとエディタでの型チェックを有効にできます。

tsconfig.json
{
// 注意:"astro/tsconfigs/strict"または"astro/tsconfigs/strictest"を使用する場合は変更する必要はありません。
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"strictNullChecks": true,
"allowJs": true
}
}

スキーマは、コレクション内の一貫したフロントマターまたはエントリーデータを強制します。スキーマは、このデータへの参照やクエリが必要なときに、それが予測可能な形で存在することを保証します。ファイルがコレクションスキーマに違反している場合、Astroは役にたつエラーを表示してお知らせします。

スキーマはAstroのコンテンツに対する自動的なTypeScriptの型付けにも力を発揮します。コレクションにスキーマを定義すると、Astroは自動的にTypeScriptインターフェイスを生成して適用します。その結果、コレクションをクエリする際には、プロパティの自動補完や型チェックを含むTypeScriptが完全にサポートされます。

最初のコレクションを定義するには、src/content/config.tsファイルがまだ存在しなければ、このファイルを作成します(.js.mjsの拡張子もサポートされています)。

  1. astro:contentから適切なユーティリティをインポートします
  2. 検証したい各コレクションを定義します。これには、コレクションがMarkdownのようなコンテンツオーサリングフォーマット(type: 'content')であるか、JSONやYAMLのようなデータフォーマット(type: 'data')であるかを指定するtype(Astro v2.5.0で導入)が含まれます。また、フロントマターやエントリーデータの形を定義するschemaも含まれます。
  3. コレクションを登録するために、単一のcollectionsオブジェクトをエクスポートします
src/content/config.ts
// 1. ユーティリティを`astro:content`からインポート
import { z, defineCollection } from 'astro:content';
// 2. 各コレクションに`type`と`schema`を定義
const blogCollection = defineCollection({
type: 'content', // v2.5.0以降
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
}),
});
// 3. コレクションを登録するために、単一の`collections`オブジェクトをエクスポート
export const collections = {
'blog': blogCollection,
};

defineCollection()は、複数のスキーマを作成するために何度でも使えます。 すべてのコレクションは、単一のcollectionsオブジェクトの内部からエクスポートする必要があります。

src/content/config.ts
const blogCollection = defineCollection({
type: 'content',
schema: z.object({ /* ... */ })
});
const newsletter = defineCollection({
type: 'content',
schema: z.object({ /* ... */ })
});
const authors = defineCollection({
type: 'data',
schema: z.object({ /* ... */ })
});
export const collections = {
'blog': blogCollection,
'newsletter': newsletter,
'authors': authors,
};

プロジェクトが成長するにつれて、コードベースを再編成し、src/content/config.tsファイルからロジックを移動することも自由にできます。スキーマを別々に定義することは、複数のコレクションでスキーマを再利用したり、プロジェクトの他の部分とスキーマを共有したりするのに便利です。

src/content/config.ts
// 1. ユーティリティとスキーマのインポート
import { defineCollection } from 'astro:content';
import { blogSchema, authorSchema } from '../schemas';
// 2. コレクションを定義
const blogCollection = defineCollection({
type: 'content',
schema: blogSchema,
});
const authorCollection = defineCollection({
type: 'data',
schema: authorSchema,
});
// 3. 複数のコレクションをエクスポートして登録
export const collections = {
'blog': blogCollection,
'authors': authorCollection,
};

サードパーティのコレクションスキーマの使用

セクションタイトル: サードパーティのコレクションスキーマの使用

外部のnpmパッケージなど、どこからでもコレクションスキーマをインポートできます。これは、独自のコレクションスキーマを提供するテーマやライブラリを使用するときに便利です。

src/content/config.ts
import { blogSchema } from 'my-blog-theme';
const blogCollection = defineCollection({ type: 'content', schema: blogSchema });
// 'my-blog-theme'の外部スキーマを使用して、ブログコレクションをエクスポート
export const collections = {
'blog': blogCollection,
};

AstroはZodを使ってコンテンツスキーマを動かしています。Zodを利用すると、Astroはコレクション内のすべてのファイルのフロントマターを検証し、プロジェクト内からコンテンツをクエリする際に自動的にTypeScriptの型を提供できます。

AstroでZodを使うには、"astro:content"からzユーティリティをインポートします。これはZodライブラリの再エクスポートで、Zodのすべての機能をサポートしています。Zodがどのように動作し、どのような機能が利用可能かについての完全なドキュメントは、ZodのREADMEを参照してください。

// 例:一般的なZodのデータ型のチートシート
import { z, defineCollection } from 'astro:content';
defineCollection({
schema: z.object({
isDraft: z.boolean(),
title: z.string(),
sortOrder: z.number(),
image: z.object({
src: z.string(),
alt: z.string(),
}),
author: z.string().default('Anonymous'),
language: z.enum(['en', 'es']),
tags: z.array(z.string()),
// オプションのフロントマター・プロパティ。非常に一般的です!
footnote: z.string().optional(),
// フロントマターでは、引用符で囲まずに書かれた日付はDateオブジェクトとして解釈されます。
publishDate: z.date(),
// 日付文字列(例えば "2022-07-08")をDateオブジェクトに変換できます。
// publishDate: z.string().transform((str) => new Date(str)),
// アドバンスド:文字列が電子メールであることを検証する。
authorContact: z.string().email(),
// アドバンスド:文字列がURLであることを検証する。
canonicalURL: z.string().url(),
})
})

コレクションエントリーは、他の関連するエントリーを「参照」することもできます。

Collections APIのreference()関数を使うと、コレクションスキーマのプロパティを別のコレクションのエントリーとして定義できます。たとえば、すべてのspace-shuttleエントリーに、型チェック、オートコンプリート、バリデーションにpilotコレクションのスキーマを使用するpilotプロパティを含めるように要求できます。

よくある例は、JSONとして保存された再利用可能な著者プロフィールや、同じコレクションに保存された関連投稿URLを参照するブログ投稿です。

import { defineCollection, reference, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
// `authors` コレクションから `id` で1人の著者を参照
author: reference('authors'),
// `blog`コレクションから`slug`による関連記事の配列を参照
relatedPosts: z.array(reference('blog')),
})
});
const authors = defineCollection({
type: 'data',
schema: z.object({
name: z.string(),
portfolio: z.string().url(),
})
});
export const collections = { blog, authors };

このブログ記事の例では、関連記事のslugと投稿者のidを指定しています。

src/content/blog/welcome.md
---
title: "私のブログへようこそ"
author: ben-holmes # `src/content/authors/ben-holmes.json`を参照
relatedPosts:
- about-me # `src/content/blog/about-me.md`を参照
- my-year-in-review # `src/content/blog/my-year-in-review.md`を参照
---

type: 'content'を使用している場合、すべてのコンテンツエントリーはファイルid (EN)からURLフレンドリーなslugプロパティを生成します。このスラグは、コレクションからエントリーを直接クエリするために使用されます。また、コンテンツから新しいページやURLを作成するときにも便利です。

ファイルのフロントマターに独自のslugプロパティを追加すると、エントリーの生成されたスラグをオーバーライドできます。これは他のWebフレームワークの”permalink”機能に似ています。"slug"は特別な予約されたプロパティ名で、カスタムコレクションschemaでは許可されず、エントリーのdataプロパティには表示されません。

---
title: 私のブログ記事
slug: my-custom-slug/supports/slashes
---
あなたのブログ記事の内容はこちら。

Astroには、コレクションにクエリを発行して1つ(または複数)のコンテンツエントリーを返す関数が2つあります。getCollection() (EN)getEntry() (EN)です。

import { getCollection, getEntry } from 'astro:content';
// コレクションからすべてのエントリーを取得します。
// 引数としてコレクション名が必要です。
// 例: `src/content/blog/**`を取得する。
const allBlogPosts = await getCollection('blog');
// コレクションから単一のエントリーを取得します。
// コレクションの名前と、以下のいずれかが必要です。
// エントリーの`slug`(コンテンツコレクション)または`id`(データコレクション)を指定する。
// 例: `src/content/authors/grace-hopper.json`を取得する。
const graceHopperProfile = await getEntry('authors', 'grace-hopper');

どちらの関数も、CollectionEntry (EN)型で定義されたコンテンツエントリーを返します。

スキーマで定義されている参照は、最初にコレクションエントリーをクエリした後で、個別にクエリする必要があります。getEntry()関数を再度使用するか、またはgetEntries()を使用して、返されたdataオブジェクトから参照されるエントリーを取得できます。

src/pages/blog/welcome.astro
---
import { getEntry, getEntries } from 'astro:content';
const blogPost = await getEntry('blog', 'welcome');
// 単一参照の解決
const author = await getEntry(blogPost.data.author);
// 参照の配列を解決
const relatedPosts = await getEntries(blogPost.data.relatedPosts);
---
<h1>{blogPost.data.title}</h1>
<p>著者: {author.data.name}</p>
<!-- ... -->
<h2>次もおすすめ</h2>
{relatedPosts.map(p => (
<a href={p.slug}>{p.data.title}</a>
))}

getCollection()はオプションの”filter”コールバックを受け取り、エントリーのiddata(フロントマター)プロパティに基づいてクエリをフィルタリングします。type: 'content'のコレクションについては、slugに基づいてフィルタリングもできます。

これを使用して、コンテンツを好きな基準でフィルタリングできます。たとえば、draftのようなプロパティでフィルタリングして、下書きのブログ記事がブログに公開されないようにできます。

// 例: `draft: true`を含むエントリーを除外
import { getCollection } from 'astro:content';
const publishedBlogEntries = await getCollection('blog', ({ data }) => {
return data.draft !== true;
});

開発サーバーの実行時にのみ閲覧可能で、本番用にはビルドされない下書き(draft)ページも作成できます。

// 例: 本番用にビルドするときにのみ、`draft: true`を含むエントリーを除外
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
});

filterの引数は、コレクション内の入れ子ディレクトリによるフィルタリングもサポートします。idにはネストされた完全なパスが含まれるので、各idの先頭でフィルタリングして、特定のネストされたディレクトリからのアイテムだけを返せます。

// 例: コレクション内のサブディレクトリによるエントリーのフィルタリング
import { getCollection } from 'astro:content';
const englishDocsEntries = await getCollection('docs', ({ id }) => {
return id.startsWith('en/');
});

コレクションエントリーをクエリすると、Astroコンポーネントテンプレートの内部で各エントリーに直接アクセスできます。これにより、コンテンツへのリンク(コンテンツslugを使用)やコンテンツに関する情報(dataプロパティを使用)などのHTMLをレンダリングできます。

コンテンツをHTMLにレンダリングする方法については、下記のコンテンツをHTMLにレンダリングするを参照してください。

src/pages/index.astro
---
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog');
---
<ul>
{blogEntries.map(blogPostEntry => (
<li>
<a href={`/my-blog-url/${blogPostEntry.slug}`}>{blogPostEntry.data.title}</a>
<time datetime={blogPostEntry.data.publishedDate.toISOString()}>
{blogPostEntry.data.publishedDate.toDateString()}
</time>
</li>
))}
</ul>

コンポーネントは、コンテンツエントリー全体をプロパティとして渡すこともできます。

この場合、CollectionEntry (EN)ユーティリティを使用して、TypeScriptでコンポーネントのプロパティを適切に型付けできます。 このユーティリティは、コレクションスキーマの名前と一致する文字列引数を取り、そのコレクションのスキーマのすべてのプロパティを継承します。

src/components/BlogCard.astro
---
import type { CollectionEntry } from 'astro:content';
interface Props {
post: CollectionEntry<'blog'>;
}
// `post`は'blog'コレクションのスキーマタイプにマッチする。
const { post } = Astro.props;
---

クエリされたエントリーのrender()関数プロパティを使用して、MarkdownおよびMDXエントリーをHTMLにレンダリングできます。この関数を呼び出すと、<Content />コンポーネントとレンダリングされたすべての見出しのリストを含む、レンダリングされたコンテンツとメタデータにアクセスできます。

src/pages/render-example.astro
---
import { getEntry } from 'astro:content';
const entry = await getEntry('blog', 'post-1');
const { Content, headings } = await entry.render();
---
<p>公開日: {entry.data.published.toDateString()}</p>
<Content />

コンテンツコレクションはsrc/pages/ディレクトリの外に保存されます。つまり、デフォルトではコレクション項目に対してルーティングは生成されません。コレクション項目からHTMLページを生成するには、手動で新しい動的ルーティングを作成する必要があります。動的ルーティングは、リクエストのパラメーター (例:src/pages/blog/[...slug].astroAstro.params.slug) をマッピングして、コレクション内の正しいエントリーを取得します。

ルーティングを生成する正確な方法は、ビルドの出力モードによって異なります。モードはstatic(デフォルト)またはserver(SSRの場合)です。

静的なウェブサイトを構築する場合(Astroのデフォルトの動作)、ビルド中に1つのsrc/pages/コンポーネントから複数のページを作成するには、getStaticPaths() (EN)関数を使用します。

getStaticPaths()内でgetCollection() (EN) を呼び出し、コンテンツをクエリします。それから、各コンテンツエントリーのslugプロパティを使用して、新しいURLパスを作成します。

src/pages/posts/[...slug].astro
---
import { getCollection } from 'astro:content';
// 1. コレクションエントリーごとに新しいパスを生成
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}
// 2. テンプレートでは、プロパティからエントリーを直接取得できる
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<h1>{entry.data.title}</h1>
<Content />

これにより、blogコレクションの各エントリーに新しいページが生成されます。たとえば、src/content/blog/hello-world.mdのエントリーはhello-worldというスラグを持つので、最終的なURLは/posts/hello-world/となります。

動的なウェブサイトを構築する場合(AstroのSSRサポートを使用する場合)、ビルド時にパスを生成する必要はありません。そのかわり、ページでは(Astro.requestあるいはAstro.paramsを使って)リクエストを調べてslugを見つけ、getEntry() (EN)を使って取得しなければなりません。

src/pages/posts/[...slug].astro
---
import { getEntry } from "astro:content";
// 1. 受信サーバーのリクエストからスラグを取得
const { slug } = Astro.params;
if (slug === undefined) {
throw new Error("Slug is required");
}
// 2. リクエストスラグを使ってエントリーを直接検索
const entry = await getEntry("blog", slug);
// 3. エントリーが存在しない場合はリダイレクト
if (entry === undefined) {
return Astro.redirect("/404");
}
// 4. (オプション) テンプレート内でエントリーをHTMLにレンダリング
const { Content } = await entry.render();
---

このガイドでは、src/pages/フォルダにMarkdownファイルがある既存のAstroプロジェクトを、コンテンツコレクションに変換する方法を紹介します。ブログを作るチュートリアルの完成したプロジェクトを例として使用しています。

  1. Astro v2.0以降にアップグレードし、すべてのインテグレーションを最新バージョンにアップグレードしてください。

  2. コンテンツコレクションにTypeScriptを設定します。

  3. 少なくとも1つのコレクション(src/content/内のフォルダ)を作成し、MarkdownページとMDXページをsrc/pages/から src/content/のこれらのサブディレクトリに移動します。コレクションは、同じコレクション内のすべてのファイルのフロントマタープロパティが似ている場合に、もっともうまく機能します。ですから、同じような種類のページを反映するように、新しいフォルダ構造を選択してください。

    たとえば、チュートリアルのブログ記事を移行するには、src/pages/posts/の内容をsrc/content/posts/に移動します。

  4. src/content/config.tsファイルを作成し、各コンテンツタイプのスキーマを定義します。ブログの場合、コンテンツタイプはpostsだけです。

    src/content/config.ts
    // ユーティリティを`astro:content`からインポート
    import { z, defineCollection } from "astro:content";
    // 各コレクションに`type`と`schema`を定義
    const postsCollection = defineCollection({
    type: 'content',
    schema: z.object({
    title: z.string(),
    pubDate: z.date(),
    description: z.string(),
    author: z.string(),
    image: z.object({
    url: z.string(),
    alt: z.string()
    }),
    tags: z.array(z.string())
    })
    });
    // コレクションを登録するために、単一の`collections`オブジェクトをエクスポート
    export const collections = {
    posts: postsCollection,
    };
  5. コレクションからルートを生成します。コレクション内では、MarkdownファイルやMDXファイルは、Astroのファイルベースルーティングを使用しても自動的にページにならないため、自分でページを生成する必要があります。

    このチュートリアルの場合は、src/pages/posts/[...slug].astroを作成します。このページは動的ルーティングを使用して、各コレクションのエントリーのページを生成します。

    このページはまた、ページスラグを取得し、各ルーティングでページコンテンツを利用できるようにするために、コレクションにクエリする必要があります。

    投稿の<Content />を、MarkdownまたはMDXページのレイアウト内にレンダリングします。これにより、すべての投稿に共通のレイアウトを指定できます。

    src/pages/posts/[...slug].astro
    ---
    import { getCollection } from 'astro:content';
    import MarkdownPostLayout from '../../layouts/MarkdownPostLayout.astro';
    export async function getStaticPaths() {
    const blogEntries = await getCollection('posts');
    return blogEntries.map(entry => ({
    params: { slug: entry.slug }, props: { entry },
    }));
    }
    const { entry } = Astro.props;
    const { Content } = await entry.render();
    ---
    <MarkdownPostLayout frontmatter={entry.data}>
    <Content />
    </MarkdownPostLayout>
  6. 個々の投稿のフロントマター内のlayout定義を削除します。レンダリング時にコンテンツがレイアウトに包まれるようになり、このプロパティは不要になりました。

    src/content/post-1.md
    ---
    layout: ../../layouts/MarkdownPostLayout.astro
    title: 'はじめてのブログ記事'
    pubDate: 2022-07-01
    ...
    ---
  7. Astro.glob()getCollection() (EN)に置き換えて、Markdownファイルからコンテンツとメタデータを取得します。また、返された post オブジェクトへの参照も更新する必要があります。なぜなら、dataプロパティにフロントマターの値が格納されるからです。

    チュートリアルのブログのインデックスページには、各記事のカードがリストアップされています。これは次のようになります。

    src/pages/blog.astro
    ---
    import { getCollection } from "astro:content";
    import BaseLayout from "../layouts/BaseLayout.astro";
    import BlogPost from "../components/BlogPost.astro";
    const pageTitle = "私のAstro学習ブログ";
    const allPosts = await getCollection("posts");
    ---
    <BaseLayout pageTitle={pageTitle}>
    <p>ここには、私がAstroを学んでいく旅の様子を投稿します。</p>
    <ul>
    {
    allPosts.map((post) => (
    <BlogPost url={'/posts/' + post.slug} title={post.data.title} />
    ))
    }
    </ul>
    </BaseLayout>

    チュートリアルのブログのプロジェクトは、各タグのページも動的に生成します。このページは次のようになります。

    src/pages/tags/[tag].astro
    ---
    import { getCollection } from "astro:content";
    import BaseLayout from "../../layouts/BaseLayout.astro";
    import BlogPost from "../../components/BlogPost.astro";
    export async function getStaticPaths() {
    const allPosts = await getCollection("posts");
    const uniqueTags = [...new Set(allPosts.map((post) => post.data.tags).flat())];
    return uniqueTags.map((tag) => {
    const filteredPosts = allPosts.filter((post) =>
    post.data.tags.includes(tag)
    );
    return {
    params: { tag },
    props: { posts: filteredPosts },
    };
    });
    }
    const { tag } = Astro.params;
    const { posts } = Astro.props;
    ---
    <BaseLayout pageTitle={tag}>
    <p>{tag}のタグが付いた記事</p>
    <ul>
    { posts.map((post) => <BlogPost url={'/posts/' + post.slug} title={post.data.title} />) }
    </ul>
    </BaseLayout>

    同じロジックがタグインデックスページにも現れています。次のようになります。

    src/pages/tags/index.astro
    ---
    import { getCollection } from "astro:content";
    import BaseLayout from "../../layouts/BaseLayout.astro";
    const allPosts = await getCollection("posts");
    const tags = [...new Set(allPosts.map((post) => post.data.tags).flat())];
    const pageTitle = "タグインデックス";
    ---
    ...
  8. layouts/MarkdownPostLayout.astroファイルの公開日を使用するコードを更新しました。

    以前は、pubDateは文字列でした。投稿のフロントマターに型を導入した結果、pubDateDateになりました。 日付をレンダリングするには、文字列に変換します。

    src/layouts/MarkdownPostLayout.astro
    ...
    <BaseLayout pageTitle={frontmatter.title}>
    <p>{frontmatter.pubDate.toDateString()}</p>
    <p><em>{frontmatter.description}</em></p>
    <p>著者: {frontmatter.author}</p>
    <img src={frontmatter.image.url} width="300" alt={frontmatter.image.alt} />
    ...

    最後に、チュートリアルのブログプロジェクトにはRSSフィードが含まれています。この関数もgetCollectiondataオブジェクトを使用し、非同期関数に変換する必要があります。

    src/pages/rss.xml.js
    import rss from "@astrojs/rss";
    import { getCollection } from "astro:content";
    export async function GET() {
    const posts = await getCollection('posts');
    return rss({
    title: 'Astro学習者 | ブログ',
    description: 'Astroを学ぶ旅',
    site: 'https://my-blog-site.netlify.app',
    items: posts.map((post) => ({
    title: post.data.title,
    pubDate: post.data.pubDate,
    description: post.data.description,
    link: `/posts/${post.slug}/`,
    })),
    customData: `<language>ja-jp</language>`,
    });
    }

コンテンツコレクションを使用したブログチュートリアルの完全なサンプルについては、チュートリアルリポジトリのコンテンツコレクションのブランチを参照してください。

Astroは、フロントマターを直接変更するremarkまたはrehypeプラグインをサポートしています。render()から返されるremarkPluginFrontmatterプロパティを使うと、コンテンツエントリー内でこの変更されたフロントマターにアクセスできます。

---
import { getEntry } from 'astro:content';
const blogPost = await getEntry('blog', 'post-1');
const { remarkPluginFrontmatter } = await blogPost.render();
---
<p>{blogPost.data.title}{remarkPluginFrontmatter.readingTime}</p>
関連レシピ: Add Reading Time (EN)

remarkやrehypeのパイプラインは、コンテンツがレンダリングされたときにのみ実行されます。そのため、render()をコールした後にremarkPluginFrontmatterを使用できるようになります。対照的に、getCollection()getEntry()はコンテンツをレンダリングしないので、これらの値を直接返すことはできません。