コンテンツにスキップ

コンテンツコレクション

追加: astro@2.0.0

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

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

コレクションエントリーは、コンテンツコレクションディレクトリ内に保存されたコンテンツのことです。エントリーには、Markdown(.md)やMDX (.mdx MDXインテグレーションを使用)などのコンテンツオーサリングフォーマットや、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からURLフレンドリーなslugプロパティを生成します。このスラグは、コレクションからエントリーを直接クエリするために使用されます。また、コンテンツから新しいページやURLを作成するときにも便利です。

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

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

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

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オブジェクトから参照されるエントリーを取得できます。

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ユーティリティを使用して、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()関数を使用します。

getStaticPaths()内でgetCollection()を呼び出し、コンテンツまたはデータををクエリします。それから、各コンテンツエントリーのslugプロパティ(コンテンツコレクション)またはidプロパティ(データコレクション)を使用して、新しい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()を使って取得しなければなりません。

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またはMDXファイルを使用するブログなどの既存のAstroプロジェクトがある場合は、関連するコンテンツまたはデータファイルをコンテンツコレクションに移行することを検討してください。

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

追加: astro@4.13.0

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

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

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

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

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

追加: astro@3.5.0 Experimental

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

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

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

npm run astro build -- --force

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()はコンテンツをレンダリングしないので、これらの値を直接返すことはできません。

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

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

src/pages/posts/example-post.md
---
title: My Blog Post
pubDate: 2021-07-08
---

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

src/pages/posts/example-post.md
---
title: My Blog Post
pubDate: 2021-07-08T12:00:00-04:00
---

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

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

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

貢献する

どんなことを?

GitHub Issueを作成

チームに素早く問題を報告できます。

コミュニティ