Astro新時代! 再ビルド不要のLive Content Collections
はじめに
これから説明するのは、astro@5.10.0
以降にて提供される予定の機能です。
2025-06-14時点では、astro
のセルフビルドが必要になることに注意してください。
公式のRFCやPRで、この機能に関するディスカッションやフィードバックを受け付けているので、気になった方はぜひ
Live Content collections RFC
Add docs for experimental live collections
実際の動作例を見たい場合は、以下のYoutube配信のアーカイブから
Live Content Collectionsとは?
これまでAstroのコンテンツCollectionsは、Markdownやjsonをもとにビルド時に静的なHTMLを生成するのが基本で、これはAstroの代名詞でもあるパフォーマンスに優れる一方、頻繁に更新されるデータを扱うには、都度々々サイトを再ビルドする必要があった。
しかし、Live Content Collectionsは、この常識を根底から覆すことができる。
- 実行タイミング: ビルド時ではなく、リクエスト時に
- データソース: ローカルファイルだけでなく、外部のCMS、API、データベースなども対象に
- 目的: 常にリアルタイムの情報(在庫、価格、ユーザーデータなど)をサイトに反映可能に
どう使い分けるべきか?
Live Collections
- リアルタイム情報: ユーザー固有のデータ、現在の在庫レベル、気象情報など。
- 頻繁なデータ更新: 数分おきに変わる商品価格やニュース速報など、再ビルドが追いつかないコンテンツ。
- 動的なフィルタリング: ユーザーの操作に応じて、APIへのクエリを動的に変更したい場合。
- CMSのプレビュー機能: 編集者が下書きコンテンツを保存後、即座にプレビューで確認したい場合。
従来(ビルド時)
- パフォーマンス最優先: ビルド時にレンダリングすることで、最速の表示速度を実現したい場合。
- 静的なコンテンツ: ブログ記事、ドキュメント、製品の基本情報など、頻繁には変更されないデータ。
- ビルド時の最適化: MDXの処理や画像の最適化を行いたい場合。
基本的な使い方
1. 機能を有効化
まず、astro.config.mjs
でexperimental
フラグを有効化。
export default {
experimental: {
liveContentCollections: true,
},
}
2. Live Collectionsを定義
次に、src/live.config.ts
をsrc/content.config.ts
とは別に作成し、defineLiveCollection
でどのLoaderを使うかを定義。
import { defineLiveCollection } from 'astro:content';
// 例として、自社のECストアから商品データを取得するLoaderを使用
import { storeLoader } from '@mystore/astro-loader';
// 'products' Live Collectionsを定義
const products = defineLiveCollection({
type: 'live', // `live`を指定
loader: storeLoader({
apiKey: process.env.STORE_API_KEY,
endpoint: 'https://api.mystore.com/v1',
}),
});
export const collections = { products };
3. ページでデータを取得・表示
あとは、Astroコンポーネント内で専用のgetLiveCollection()
やgetLiveEntry()
関数を使ってデータを取得するだけ。
---
import { getLiveCollection, getLiveEntry } from 'astro:content';
// 単一の商品データをIDで取得
const { entry: product, error } = await getLiveEntry('products', Astro.params.id);
if (error) {
return Astro.redirect('/404');
}
const { entries: electronics } = await getLiveCollection('products', { category: 'electronics' }); // フィルタも可能
---
<h1>{product.data.name}</h1>
<p>価格: {product.data.price}円</p>
<p>在庫: {product.data.stock > 0 ? 'あり' : 'なし'}</p>
Live Loaderを作ってみる
Live Collectionsの心臓部となるのがLive Loaderで、これは実際に外部と通信してデータを取得する責務を担う。
従来のContent Loaderと同じく、コミュニティが作ったLoaderを使うこともできるし、自分で作ることも可。
-
loadCollection()
: 複数のエントリを取得 -
loadEntry()
: 単一のエントリを取得
import type { LiveLoader } from 'astro/loaders';
import { fetchFromCMS } from './cms-client';
interface Article {
id: string;
title: string;
content: string;
htmlContent: string; // レンダリング用のHTML
}
export function articleLoader(config: { apiKey: string }): LiveLoader<Article> {
return {
name: 'article-loader',
// 複数記事を取得
loadCollection: async ({ filter }) => {
try {
const articles = await fetchFromCMS({ ... });
return {
entries: articles.map((article) => ({
id: article.id,
data: article,
})),
};
} catch (e) {
return { error: new Error(${e.message}) };
}
},
// 単一記事を取得
loadEntry: async ({ filter }) => {
try {
const article = await fetchFromCMS({ id: filter.id, ... });
if (!article) {
// エントリが見つからない場合はnullやundefinedを返すと、
// Astroが自動でNotFoundErrorを返す
return null;
}
return {
id: article.id,
data: article,
// `rendered`プロパティを返すことで<Content />が使用可能
rendered: {
html: article.htmlContent,
},
};
} catch (e) {
return { error: new Error(${e.message}) };
}
},
};
}
<Content />
でコンテンツをレンダリング
LoaderのloadEntry
がrendered
プロパティを返すことで、取得したコンテンツを<Content />
でレンダリング。
---
import { getLiveEntry, render } from 'astro:content';
const { entry, error } = await getLiveEntry('articles', Astro.params.id);
if (error) {
return Astro.redirect('/404');
}
// render()でContentコンポーネントを準備
const { Content } = await render(entry);
---
<article>
<h1>{entry.data.title}</h1>
<hr />
<Content />
</article>
そのほか
Zodによるスキーマ定義
従来のCollectionsと同様に、zodを使ってデータのバリデーションや変換が可能。
これもsrc/live.config.ts
でschema
を定義。
import { z, defineLiveCollection } from 'astro:content';
import { apiLoader } from './loaders/api-loader';
const products = defineLiveCollection({
type: 'live',
loader: apiLoader({ ... }),
schema: z.object({
id: z.string(),
name: z.string(),
price: z.number(),
// APIから来た日付文字列をDateオブジェクトに変換
createdAt: z.coerce.date(),
})
.transform((data) => ({
...data,
// 表示用の価格文字列を新たに生成
displayPrice: `${data.price.toLocaleString()}円`,
})),
});
export const collections = { products };
キャッシュヒント
LoaderがcacheHint
を返すことで、データがどれくらいの期間キャッシュ可能かを示す。
これにより、ページのレスポンスヘッダーにCache-Control
などを設定し、ホスティング先(Vercel, Netlifyなど)のCDNキャッシュを制御が可能になる。
// ...Loaderの実装
return {
entries: [/* ... */],
cacheHint: {
tags: ['products'],
maxAge: 300, // 5分間キャッシュ可能
},
};
注意点
以下の要件が必要な場合は、Live Content Collectionsを使用できない。
-
MDXサポートなし: MDXのレンダリングはランタイムでは実行不可(
@astrojs/mdx
が必要)。 - 画像最適化なし: Astroによる画像の自動最適化は利用不可。
- パフォーマンス: データはリクエストごとに取得するため、キャッシュ戦略によって大幅にパフォーマンスが変化。
最後に
最初はdev serverでしか使えない(Nuxt Contentみたいなやつ)だと思ってたんですが、普通にprodでもリアルタイム更新できるみたいで、もうよく分からないところまで来ましたね。
Discussion