web-meta-framework-astro について
Astro 開発者向けに、コンポーネント、コンテンツコレクション、ルーティング、レンダリングのパターンをまとめたガイドです。静的出力を標準とし、インタラクティブアイランドやページ単位の制御、Astro 6 の移行時の注意点を扱います。
主な機能
- 01Astro コンポーネントとレイアウト
- 02アイランドとクライアントディレクティブ
- 03Zod 検証付きコンテンツコレクション
- 04静的・オンデマンドルーティング
- 05ページ単位のレンダリング制御
- 06Astro 6 の移行時の注意点
活用シーン
- 01ページの静的・オンデマンド方式の選択
- 02インタラクティブコンポーネントの水和時期設定
- 03型付き Markdown コンテンツの定義と検索
- 04Astro 6 プロジェクトの移行課題の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-meta-framework-astroインストール後、Claude Code で/web-meta-framework-astroを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/content.mdcore.mdintegrations.mdislands.mdrouting.md詳細
Astro のパターン
クイックガイド: Astro はデフォルトで、クライアント側の JavaScript を一切使わずにページを静的 HTML としてレンダリングします。
.astroコンポーネントには静的コンテンツを記述します。フレームワークのコンポーネントは、client:*ディレクティブを指定した場合にのみ、インタラクティブなアイランドになります。コンテンツコレクションを使うと、Zod スキーマによって、構造化された Markdown のフロントマターに型安全性を持たせることができます。レンダリングモードはページごとに設定します。export const prerender = falseを指定しない限り、静的にレンダリングされます。Astro 6 では<ViewTransitions />が<ClientRouter />に、Astro.glob()がimport.meta.glob()に置き換わり、zのインポート元がastro:contentからastro/zodに変更され(Zod 4 になりました)、getStaticPaths()のパラメーターに文字列が必須となり、Node 22.12.0 以降が必要になりました。
詳細なリソース:
- examples/core.md — 型付き props、式、ネストされたレイアウト、スコープ付きスタイルとグローバルスタイル、スクリプトの扱い
- examples/islands.md — ディレクティブの選択、
client:only、複数フレームワークのアイランド、サーバーアイランド、アイランド間の状態 - examples/content.md — コレクションのスキーマ、クエリ、レンダリング、参照、カスタムローダー、ライブコレクション
- examples/routing.md — 静的ルートと動的ルート、残余パラメーター、ページネーション、オンデマンドルート、エンドポイント、404
- examples/integrations.md — フレームワーク統合、View Transitions、状態の保持、アニメーション、Starlight
- reference.md — プロジェクトのレイアウト、ルートの優先順位、コレクション API、アダプター、レンダリングのチェックリスト
該当するパス
- すべてのページが静的 — デフォルトの設定です。
output設定もアダプターも不要です。動的ルートにはgetStaticPaths()が必要です。examples/routing.md を参照してください。 - 一部のページでリクエスト時のデータが必要 — デフォルト設定のままにして、該当するページに
export const prerender = falseを追加し、サーバーアダプターをインストールします。同じファイルに加え、reference.md のアダプター表も参照してください。 - ほとんどのページでリクエスト時のデータが必要 —
output: "server"を設定し、個別のページでexport const prerender = trueを指定して静的レンダリングに戻します。
<critical_requirements>
Astro コードを書く前に
ページは静的なままにし、リクエスト時のデータを読み取るページに限って export const prerender = false を追加してください。 デフォルトは静的であり、フレームワークの高速性はここから生まれます。
ページのプリレンダリングを無効にする前に、サーバーアダプターをインストールしてください。 アダプターがない場合、prerender = false を含むビルドは失敗します。
フレームワークのコンポーネントでインタラクティブな動作が必要な場合は、client:* ディレクティブを指定してください。 ディレクティブがない場合、静的 HTML としてレンダリングされ、イベントハンドラーは実行されません。
Zod スキーマを使って、src/content.config.ts でコレクションを定義してください。 スキーマはビルド時にフロントマターを検証し、クエリが返す型を生成します。
静的モードの動的ルートには getStaticPaths() を使ってください。 オンデマンドルートでは Astro.params を直接読み取るため、このエクスポートは不要です。
</critical_requirements>
自動検出: Astro、.astro ファイル、astro.config、アイランドアーキテクチャ、client:load、client:visible、client:idle、client:only、client:media、server:defer、コンテンツコレクション、defineCollection、defineLiveCollection、getCollection、getLiveCollection、getEntry、getLiveEntry、render、astro:content、astro:transitions、ClientRouter、getStaticPaths、Astro.props、Astro.params、Astro.cookies、Astro.redirect、prerender、astro add、@astrojs/react、@astrojs/vue、@astrojs/svelte、Starlight
対象:
.astroコンポーネントの構文 — フロントマター、テンプレート式、スロット、レイアウト、スコープ付きスタイル- アイランド:クライアントディレクティブ、ハイドレーションのタイミング、サーバーアイランド、1 ページ内での UI フレームワークの混在
- コンテンツコレクション:スキーマ、ローダー、クエリ、レンダリング、コレクション間の参照
- ファイルベースのルーティング:静的、動的、残余パラメーター、ページネーション、API エンドポイント
- レンダリングモード:静的、オンデマンド、ページごとの
prerender制御 - View Transitions:
<ClientRouter />、トランジションディレクティブ、要素の保持
別途扱う項目:
- アイランドが内部でどのようにレンダリングされるか — ハイドレーションされたコンポーネントは、それを書いた UI ライブラリの規約に従い、Astro が調整するのは境界だけです。
<style>ブロックにどの CSS アプローチを使うか — ここで決まるのは Astro のスコープ規則であり、スタイリングシステムではありません。- アイランド間の継ぎ目をどのストアライブラリで埋めるか — アイランドがツリーを共有しないこと、そしてモジュールスコープのストアが橋渡しの方法であることは examples/islands.md で定められていますが、どのストアを使うかは定められていません。
- すべてのルートがユーザー固有の、完全にインタラクティブなアプリケーション — この形には、デフォルトが静的出力ではなくオンデマンドレンダリングであるフレームワークが適しています。
Astro は、デフォルトでは JavaScript を一切出力しない、コンテンツファーストのフレームワークです。ページの大部分は静的 HTML であり、小さなインタラクティブな「アイランド」はそれぞれ独立してハイドレーションされ、必要な分だけ負担します。
4 つの帰結があり、API の大部分はこれで説明できます。
- コンポーネントは、指示があるまで静的です —
client:*がオプトインのため、インタラクティブ性のコストは常に呼び出し箇所で確認できます。 - UI ライブラリは細部にすぎません — React、Vue、Svelte、Solid、Preact のコンポーネントはいずれも単なるアイランドの中身であり、1 つのページに複数を含めることができます。
- コンテンツは型付きデータです — コレクションは、Markdown のフロントマターとそれを読み取るコードの間に Zod スキーマを置くため、投稿の誤字はページではなくビルド時にエラーになります。
- レンダリングモードはページごとに設定します — 静的ページとオンデマンドページを 1 つのプロジェクト内で共存させられます。そのため、
prerenderはグローバル設定ではなくエクスポートになっています。
<decision_framework>
静的レンダリングとオンデマンドレンダリング、どちらを選ぶべきですか? リクエスト時に Cookie、ヘッダー、またはユーザー固有のデータを読み取るページや、データの変更頻度が再ビルドの許容範囲を超えるページには、オンデマンドレンダリングが必要です。それ以外はすべて静的です。毎時間変わるデータも含まれます。アダプターを使うより、再ビルドする方が低コストです。
.astro とフレームワークコンポーネントのどちらを選ぶべきですか? クライアント側のインタラクティブ性が不要なら、JavaScript がゼロの .astro を使います。単純なインタラクティブ性(トグルや表示/非表示など)には、.astro と <script> タグの組み合わせを使います。これはフレームワークより軽量です。そのフレームワーク独自の状態モデルが必要なインタラクションには、フレームワークコンポーネントを使います。
どのクライアントディレクティブを選ぶべきですか? パターン 4 の表に、それぞれの用途がまとめられています。client:only は、サーバーでまったくレンダリングできないコンポーネントに使う最終手段です。
ビルド時コレクションとライブコレクション、どちらを選ぶべきですか? リクエストごとに内容が変わらないもの(投稿、ドキュメント、変更履歴、著者プロフィール)には defineCollection を使います。defineLiveCollection は、データをリクエストごとに最新にする必要がある場合(在庫、価格)に限って使います。これを使うと、そのページもオンデマンドレンダリングになります。
</decision_framework>
コアパターン
パターン 1:Astro コンポーネントの構造
.astro ファイルは、--- の区切りに挟まれたサーバー専用スクリプトと、それに続く HTML テンプレートで構成されます。スクリプトがブラウザーに送られることはないため、データの取得はここに記述します。
---
interface Props { title: string; description?: string }
const { title, description = "Default" } = Astro.props;
const posts = await getCollection("blog");
---
<h1>{title}</h1>
{posts.map((post) => <a href={`/blog/${post.id}`}>{post.data.title}</a>)}
<style>h1 { color: navy; }</style>
<style> ブロックのスタイルは、is:global が指定されていない限り、コンポーネントにスコープされます。
コード全体:examples/core.md
パターン 2:コンポジションに使うスロット
<slot /> は親からコンテンツを受け取ります。名前付きスロットは、一致する slot="name" 属性を受け取り、<slot> 要素内のコンテンツは、何も渡されなかった場合のフォールバックコンテンツになります。
<article>
<header><slot name="header"><h2>{title}</h2></slot></header>
<slot />
<footer><slot name="footer" /></footer>
</article>
コード全体:examples/core.md
パターン 3:レイアウト
レイアウトは、デフォルトスロットを通じてページコンテンツをラップする通常のコンポーネントです。レイアウトは入れ子にできます。たとえば、ブログレイアウトがベースレイアウトをラップし、そのベースレイアウトがページをラップします。
---
const { title } = Astro.props;
---
<html lang="en">
<head><title>{title}</title></head>
<body><main><slot /></main></body>
</html>
コード全体:examples/core.md
### パターン 4:アイランドとクライアントディレクティブフレームワークのコンポーネントは、client:* ディレクティブでハイドレーションされるまで静的 HTML としてレンダリングされます。このディレクティブで、ハイドレーションが いつ 行われるかを指定します。
| ディレクティブ | ハイドレーションのタイミング | 用途 |
|---|---|---|
client:load | ページの読み込み直後 | 重要なインタラクティブ UI |
client:idle | ブラウザーがアイドル状態のとき | 優先度の低いインタラクティブ機能 |
client:visible | ビューポートにスクロールして表示されたとき | ファーストビュー以外のコンテンツ |
client:media | メディアクエリに一致したとき | レスポンシブなインタラクティブ機能 |
client:only | サーバーでのレンダリングを省略し、クライアントのみ | ブラウザーに依存するコンポーネント |
<Header />
<SearchBar client:load />
<Comments client:visible />
<Analytics client:media="(max-width: 768px)" />
コード全文:examples/islands.md
パターン 5:サーバーアイランド
server:defer を指定すると、ページの他の部分をキャッシュ可能な状態に保ったまま、リクエストごとにコンポーネントをレンダリングします。コンポーネントが読み込まれるまで、その場所には fallback スロットの内容が表示されます。
<ProductInfo product={product} />
<UserReviews server:defer>
<div slot="fallback">Loading reviews...</div>
</UserReviews>
コード全文:examples/islands.md
パターン 6:コンテンツコレクション
コレクションは、ローダーと Zod スキーマを組み合わせたものです。getCollection で任意のフィルターを指定してクエリを実行でき、render() でエントリーの Markdown をコンポーネントに変換できます。
// src/content.config.ts
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
pubDate: z.coerce.date(),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
const posts = await getCollection("blog", ({ data }) => !data.draft);
const { Content, headings } = await render(post);
ライブコレクションは別の仕組みです。content.config.ts ではなく src/live.config.ts の defineLiveCollection で定義し、getLiveCollection() / getLiveEntry() でクエリを実行します。また、オンデマンドページからのみ利用できます。
コード全文:examples/content.md
パターン 7:ファイルベースのルーティング
src/pages/ が URL に対応します。[param] は単一の動的セグメント、[...rest] は任意の階層に一致し、先頭にアンダースコアが付いたファイルはルーティングの対象外になります。
src/pages/
├── index.astro → /
├── blog/[id].astro → /blog/:id
├── docs/[...slug].astro → /docs and /docs/*
└── _helpers.ts → not a route
静的モードでは、各動的ルートが { params, props } の組を返す getStaticPaths() をエクスポートします。この中で paginate() を使うと、番号付きのページを生成できます。
コード全文:examples/routing.md
パターン 8:オンデマンドレンダリングとエンドポイント
export const prerender = false を指定すると、ページはリクエスト時にレンダリングされます。この場合、Astro.cookies、Astro.redirect()、Response の戻り値を利用できます。API エンドポイントは、HTTP メソッドごとに 1 つのハンドラーをエクスポートする .ts ファイルです。他の機能と同様、デフォルトでは静的に処理されます。
export const prerender = false;
export const GET: APIRoute = async ({ url }) => {
const query = url.searchParams.get("q");
if (!query)
return new Response(JSON.stringify({ error: "Missing query" }), {
status: 400,
});
return new Response(JSON.stringify(await searchDatabase(query)));
};
コード全文:examples/routing.md
パターン 9:ビュートランジション
ドキュメントのヘッドに <ClientRouter /> を置くと、ページ間のトランジションが有効になります。transition:name を指定した要素同士はペアになって形を変え、transition:persist を指定すると、ナビゲーションをまたいで要素が維持されます。
<head><ClientRouter /></head>
<img src={post.data.heroImage} transition:name={`hero-${post.id}`} transition:animate="slide" />
<audio controls transition:persist><source src="/music.mp3" type="audio/mp3" /></audio>
コード全文:examples/integrations.md
<red_flags>
問題点
実行時にエラーになります:
getStaticPaths()のない、静的モードの動的ルート — ビルド時に「getStaticPaths() is required」というエラーが発生します。- サーバーアダプターをインストールせずに
prerender = falseを設定する — ビルドに失敗します。 <ViewTransitions />— Astro 6 で削除されました。astro:transitionsから<ClientRouter />をインポートしてください。Astro.glob()— Astro 6 で削除されました。import.meta.glob()を使用してください。import { z } from "astro:content"— Astro 6 で削除されました。astro/zodからzをインポートしてください。これは Zod 4 であるため、z.string().email()はz.email()になりました。- 数値の
getStaticPaths()パラメーター — Astro 6 では文字列が必要です。String(id)でラップしてください。 astro.config.cjs— Astro 6 では ESM(.mjsまたは.ts)が必要です。- ハイドレーションされるコンポーネントの props に渡された関数、クラスインスタンス、またはシンボル — アイランドの props はシリアライズ可能でなければなりません。
- フロントマターの区切りの外で
Astro.propsを読み取る、または.astroコンポーネントをフレームワークコンポーネントにインポートする — どちらもその場所では利用できません。 - Node 22.12.0 未満での Astro 6 — サポート対象外です。
予想外の動作:
- すべてに
client:loadを指定すると、アーキテクチャの利点が損なわれます。インタラクティブ性のないコンポーネントには、ディレクティブは不要です。 client:onlyはサーバーでのレンダリングをスキップするため、クローラー向けの HTML に何も含まれません。コンポーネントをサーバーでレンダリングできる場合は、client:loadを優先してください。<script>タグ内でデータを取得すると、クライアント側でウォーターフォールが発生します。フロントマターはサーバー側で実行されるため、そのようなコストはありません。- スキーマのないコレクションでは、ビルド時の検証と生成された型の両方が失われます。
- ほとんどが静的なサイトで
output: "server"を指定すると、動的配信を必要としないページに対するデフォルトのキャッシュが利用できなくなります。 - フィルターを指定しない
getCollectionは、公開済みエントリーとともに下書きも返します。 <style>はデフォルトでスコープが適用されます。意図的に<style is:global>または:global()セレクターを使用してください。<script>はバンドルされ、重複排除されます。is:inlineを指定するとその処理が無効になり、View Transitions のナビゲーションごとにスクリプトが再実行されます。client:visibleは IntersectionObserver による検出を待機するため、画面に表示されないコンポーネントはハイドレーションされません。Astro.redirect()はオンデマンドページでのみ機能します。静的ページはリクエスト時にリダイレクトできません。- アイランド間で状態は共有されません。アイランド間の通信には、独自に用意したストアまたはイベントが必要です。
transition:persistを使用するには、両方のページで同じtransition:nameが必要です。そうでない場合、要素は再作成されます。- コレクションエントリーの ID は、フロントマターで上書きされない限り、ファイル名から取得されます。
- ライブコレクションでは MDX をレンダリングできません。また、
import.meta.envはビルド時にインライン化されるため、ランタイムのシークレットはprocess.envから読み取ってください。 [...slug]ルートでは、params エントリーにundefinedを渡すと、ルート自身のベースパスにもマッチします。