import { FileTree } from '@astrojs/starlight/components'; import Since from '/components/Since.astro' import RecipeLinks from "/components/RecipeLinks.astro" import Badge from "~/components/Badge.astro"

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

コンテンツコレクションとは?

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

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

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

".astro"ディレクトリ

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

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

:::tip バージョン管理にGitを使っている場合は、.gitignore.astroを追加して.astroディレクトリを無視することをおすすめします。以下は、このディレクトリとその中のファイルを無視するようにGitに伝えます。 :::

複数のコレクションによる整理

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

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

サブディレクトリを使った整理

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

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

コレクションの定義

:::note src/content/config.tsファイルは省略できます。しかし、コレクションを定義しないと、フロントマターのスキーマ検証やTypeScriptの自動型付けなど、コレクションのいくつかの優れた機能が使えなくなります。 :::

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

ts
// 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,};

TypeScriptのセットアップ

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

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

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

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オブジェクトをエクスポートします
ts
// 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オブジェクトの内部からエクスポートする必要があります。

ts
// src/content/config.tsconst 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ファイルからロジックを移動することも自由にできます。スキーマを別々に定義することは、複数のコレクションでスキーマを再利用したり、プロジェクトの他の部分とスキーマを共有したりするのに便利です。

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

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

Zodによるデータ型の定義

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

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

ts
// 例:一般的な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を参照するブログ投稿です。

ts
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を指定しています。

yaml
---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からURLフレンドリーなslugプロパティを生成します。このスラグは、コレクションからエントリーを直接クエリするために使用されます。また、コンテンツから新しいページやURLを作成するときにも便利です。

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

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

コレクションのクエリ

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

js
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型で定義されたコンテンツエントリーを返します。

参照データへのアクセス

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

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に基づいてフィルタリングもできます。

:::note slugプロパティはコンテンツコレクションに固有のもので、JSONやYAMLのコレクションをフィルタリングするときには利用できません。 :::

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

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

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

js
// 例: 本番用にビルドするときにのみ、`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の先頭でフィルタリングして、特定のネストされたディレクトリからのアイテムだけを返せます。

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

Astroテンプレートでのコンテンツの使用

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

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

astro
---// src/pages/index.astroimport { 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ユーティリティを使用して、TypeScriptでコンポーネントのプロパティを適切に型付けできます。 このユーティリティは、コレクションスキーマの名前と一致する文字列引数を取り、そのコレクションのスキーマのすべてのプロパティを継承します。

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

コンテンツをHTMLにレンダリングする

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

astro
---// src/pages/render-example.astroimport { 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()関数を使用します。

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

astro
---// src/pages/posts/[...slug].astroimport { 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/となります。

:::note カスタムスラグに/文字が含まれ、複数のパスセグメントを持つURLを生成する場合は、この動的ルーティングページの.astroファイル名にレストパラメータ([...slug]を使用する必要があります。 :::

サーバー出力のビルド(SSR)

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

astro
---// src/pages/posts/[...slug].astroimport { 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();---

:::tip GitHubのブログチュートリアルデモコード または StackBlitzで開くsrc/pages/ フォルダを探せば、ブログ記事のリストやタグページなど、コレクションからページを作成する完全な例を見れます! :::

ファイルベースのルーティングからの移行

src/pages/内のサブフォルダーでMarkdownまたはMDXファイルを使用するブログなどの既存のAstroプロジェクトがある場合は、関連するコンテンツまたはデータファイルをコンテンツコレクションに移行することを検討してください。

ブログ作成チュートリアルの完成プロジェクトのコードベースを使用するステップバイステップのチュートリアルで、基本的なブログの例をsrc/pages/posts/からsrc/content/postsに変換する方法を確認してください。

JSONスキーマの生成を有効にする

data型のコレクションを使用している場合、Astroはインテリセンスと型チェックを行うために、エディタ用のJSONスキーマファイルを自動生成します。src/content/config.tsで定義されたコレクションに基づいて、プロジェクト内の各データコレクションごとに、zod-to-json-schemaと呼ばれるライブラリを使用して個別のファイルが作成されます。

この機能では、コレクションの各データ入力ファイルで、スキーマのファイルパスを $schema の値として手動で設定する必要があります。

json
{  "$schema": "../../../.astro/collections/authors.schema.json",  "name": "Armand",  "skills": ["Astro", "Starlight"]}

または、エディタの設定でこの値を設定することもできます。例えば、VSCodeのjson.schemas設定でこの値を設定するには、マッチするファイルのパスとJSONスキーマの場所を指定します。

json
{  "json.schemas": [    {      "fileMatch": [        "/src/content/authors/**"      ],      "url": "./.astro/collections/authors.schema.json"    }  ]}

ビルドキャッシュを有効にする

大規模なコレクションを扱っている場合は、experimental.contentCollectionCacheフラグを使用してキャッシュされたビルドを有効にするとよいでしょう。この実験的な機能は、Astroのビルドプロセスを最適化し、変更されていないコレクションを保存してビルド間で再利用できるようにします。

多くの場合、これによりビルドパフォーマンスが大幅に向上します。

この機能が安定するまで、保存されたキャッシュで問題が発生する可能性があります。次のコマンドを実行すると、いつでもビルドキャッシュをリセットできます。

npm run astro build -- --force

Remarkでフロントマターを修正

:::caution 非推奨。Remarkとrehypeプラグインは生のMarkdownまたはMDXドキュメントのフロントマターにアクセスします。これはremarkPluginFrontmatterのフロントマターがあなたの型セーフschemaとは別に扱われ、Astroを通して適用された変更やデフォルトは反映されません。使用は自己責任です! :::

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

astro
---import { getEntry } from 'astro:content';const blogPost = await getEntry('blog', 'post-1');const { remarkPluginFrontmatter } = await blogPost.render();---<p>{blogPost.data.title} — {remarkPluginFrontmatter.readingTime}</p>

<RecipeLinks slugs={["ja/recipes/reading-time" ]}/>

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

フロントマターでの日付の扱い

コンテンツコレクションではいくつかの日付フォーマットが可能ですが、コレクションのスキーマはMarkdownまたはMDX YAMLのフロントマターで使用されているフォーマットと一致しなければなりません。

YAMLは日付を表現するためにISO-8601の標準を使います。yyyy-mm-dd(例 2021-07-28)というフォーマットとz.date()というスキーマタイプを使います:

markdown
---title: My Blog PostpubDate: 2021-07-08---

タイムゾーンが指定されていない場合、日付の書式はUTCで指定されます。タイムゾーンを指定する必要がある場合は、ISO 8601 形式を使うことができます。

markdown
---title: My Blog PostpubDate: 2021-07-08T12:00:00-04:00---

完全なUTCタイムスタンプからYYYY-MM-DDだけをレンダリングするには、JavaScriptのsliceメソッドを使用してタイムスタンプを削除します:

astro
---const { frontmatter } = Astro.props;---<h1>{frontmatter.title}</h1><p>{frontmatter.pubDate.toISOString().slice(0,10)}</p>

toLocaleDateStringを使って日、月、年をフォーマットする例を見るには、Astro公式ブログテンプレートの<FormattedDate />コンポーネントを参照してください。