Astro Operations
最終確認日: 2026-07
Astro フレームワーク開発のための包括的なパターン集: アイランドアーキテクチャ、コンテンツコレクション、レンダリング戦略、ビュートランジション、マルチプラットフォームデプロイ。
レンダリング戦略 決定木
どのレンダリング戦略を選ぶべきか?
│
├─ コンテンツは主に静的か(ブログ、ドキュメント、マーケティングサイト)?
│ ├─ YES → 更新頻度は1日に1回未満か?
│ │ ├─ YES → SSG (output: 'static')
│ │ │ 最速のTTFB、CDNキャッシュ可、ランタイムコストゼロ
│ │ └─ NO → ハイブリッド (output: 'hybrid')
│ │ デフォルトは静的 + ルートごとにSSRを選択
│ └─ NO → すべてのページでパーソナライズが必要か?
│ ├─ YES → SSR (output: 'server')
│ │ リクエストごとに動的、認証対応、リアルタイムデータ
│ └─ NO → ハイブリッド (output: 'hybrid')
│ 静的なシェル + 動的な部分はサーバーアイランド
│
├─ アプリにリアルタイムのインタラクティビティが必要か(ダッシュボード、SPA)?
│ ├─ YES → クライアントサイドルーティングを持つ完全なSPAか?
│ │ ├─ YES → React/Vue SPA、または Astro + client:only を検討
│ │ └─ NO → ハイブリッド + アイランドアーキテクチャ
│ │ 静的ページ内のインタラクティブなアイランド
│ └─ NO → SSG (output: 'static')
│
├─ ビルド時間が懸念されるか(1万ページ以上)?
│ ├─ YES → オンデマンドレンダリングを使用したハイブリッド
│ │ 人気のページはプリレンダリング、ロングテールはSSR
│ └─ NO → SSGで十分
│
└─ エッジコンピューティングが必要か(グローバルな低レイテンシー)?
├─ YES → SSR + Cloudflare/Vercel Edge アダプター
└─ NO → SSR + Node アダプター、または SSG
設定
// astro.config.mjs
import { defineConfig } from 'astro/config';
// SSG (デフォルト) - すべてのページをビルド時にプリレンダリング
export default defineConfig({
output: 'static',
});
// SSR - すべてのページをリクエスト時にレンダリング
export default defineConfig({
output: 'server',
adapter: cloudflare(), // または vercel(), netlify(), node()
});
// ハイブリッド - デフォルトは静的、ページごとにSSRを選択
export default defineConfig({
output: 'hybrid',
adapter: cloudflare(),
});
---
// ハイブリッドモードでは、特定のページでプリレンダリングを無効にする:
export const prerender = false;
// SSRモードでは、プリレンダリングを有効にする:
export const prerender = true;
---
アイランドアーキテクチャ クイックリファレンス
| ディレクティブ | ハイドレーションのタイミング | 送信されるJS | 使用例 |
|---|---|---|---|
client:load | ページ読み込み直後 | 完全バンドル | ファーストビューのインタラクティブ要素 (ナビ、ヒーローCTA) |
client:idle | ページがアイドル状態になった後 (requestIdleCallback) | 完全バンドル | ファーストビュー以下のインタラクティブ要素 (コメントフォーム、チャット) |
client:visible | ビューポートにスクロールされた時 | 完全バンドル | ページ下部の要素 (フッターウィジェット、カルーセル) |
client:media | メディアクエリが一致した時 | 完全バンドル | モバイル専用ナビ、レスポンシブコンポーネント |
client:only="react" | 即時、SSRを完全にスキップ | 完全バンドル | SSRできないコンポーネント (canvas、WebGL) |
| (なし) | なし - 静的HTMLのみ | JSゼロ | 静的コンテンツ、カード、ヘッダー |
---
import NavBar from '../components/NavBar.tsx';
import CommentForm from '../components/CommentForm.tsx';
import ImageCarousel from '../components/ImageCarousel.svelte';
import MobileMenu from '../components/MobileMenu.vue';
import ThreeScene from '../components/ThreeScene.tsx';
---
<!-- すぐに読み込む - 重要なインタラクティブ要素 -->
<NavBar client:load />
<!-- ページがアイドル状態になったら読み込む - 重要ではない -->
<CommentForm client:idle />
<!-- ビューポートにスクロールされたら読み込む - 遅延読み込み -->
<ImageCarousel client:visible />
<!-- モバイルでのみ読み込む -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- クライアントのみ、SSRなし (WebGLはサーバーで実行不可) -->
<ThreeScene client:only="react" />
コンテンツコレクション クイックスタート
スキーマを定義する
// src/content.config.ts (Astro 5) または src/content/config.ts (Astro 4)
import { defineCollection, z, reference } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string().max(160),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
author: reference('authors'), // 別のコレクションを参照
}),
});
const authors = defineCollection({
loader: glob({ pattern: '**/*.json', base: './src/content/authors' }),
schema: z.object({
name: z.string(),
avatar: z.string(),
bio: z.string(),
socials: z.object({
twitter: z.string().optional(),
github: z.string().optional(),
}).optional(),
}),
});
export const collections = { blog, authors };
コレクションをクエリする
---
import { getCollection, getEntry } from 'astro:content';
// 下書きではないすべてのブログ投稿を日付順に取得
const posts = (await getCollection('blog', ({ data }) => !data.draft))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
// 1つのエントリを取得
const post = await getEntry('blog', 'my-first-post');
// 参照を解決
const author = await getEntry(post.data.author);
// コンテンツをレンダリング
const { Content, headings } = await post.render();
---
<Content />
コンテンツコレクション vs 外部CMS
| 基準 | コンテンツコレクション | 外部CMS (Payload など) |
|---|---|---|
| コンテンツタイプ | ローカルのMarkdown/MDX、ドキュメント、ブログ | リレーショナルデータモデル |
| 作成者 | 開発者 (バージョン管理による) | 編集者 (管理UI、マルチユーザー認証) |
| バリデーション | ビルド時のZodによる型安全 | CMS側のスキーマ + API契約 |
| 更新頻度 | サイトと一緒にデプロイ | デプロイとは独立 |
| API要件 | なし (ビルド時のクエリ) | 他のコンシューマ向けREST/GraphQL |
| ワークフロー | Git PR、シンプルなレビュー | 編集ワークフロー、下書き、ロール |
経験則: 最初はコンテンツコレクションから始め、デプロイなしで公開する必要がある非開発者、またはコンテンツが本格的に関連している場合のみCMSの利用を検討する。
プロジェクト構造 リファレンス
project-root/
├── astro.config.mjs # Astro 設定
├── tsconfig.json # TypeScript 設定 (astro/tsconfigs を継承)
├── package.json
├── public/ # 静的アセット (そのままコピー)
│ ├── favicon.svg
│ ├── robots.txt
│ └── og-image.png
├── src/
│ ├── pages/ # ファイルベースルーティング
│ │ ├── index.astro # → /
│ │ ├── about.astro # → /about
│ │ ├── blog/
│ │ │ ├── index.astro # → /blog
│ │ │ └── [slug].astro # → /blog/:slug (動的)
│ │ ├── api/
│ │ │ └── search.ts # → /api/search (APIエンドポイント)
│ │ └── [...slug].astro # → キャッチオール/404
│ ├── layouts/
│ │ ├── BaseLayout.astro # HTMLシェル、ヘッド、グローバルスタイル
│ │ └── BlogPost.astro # ブログ投稿レイアウト
│ ├── components/
│ │ ├── Header.astro # 静的Astroコンポーネント
│ │ ├── Footer.astro
│ │ ├── NavBar.tsx # Reactアイランド
│ │ └── Counter.svelte # Svelteアイランド
│ ├── content/ # コンテンツコレクションのソースファイル
│ │ ├── blog/
│ │ │ ├── post-one.md
│ │ │ └── post-two.mdx
│ │ └── authors/
│ │ └── jane.json
│ ├── content.config.ts # コレクションスキーマ (Astro 5)
│ ├── middleware.ts # リクエスト/レスポンスミドルウェア
│ ├── styles/
│ │ └── global.css
│ └── lib/ # 共有ユーティリティ
│ ├── utils.ts
│ └── constants.ts
└── .env # 環境変数
ビュートランジション クイックリファレンス
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>
トランジションディレクティブ
<!-- ページ間で要素を維持 (状態を保持、再レンダリングを回避) -->
<audio transition:persist id="player">
<source src="/music.mp3" />
</audio>
<!-- アニメーションペアリングのための名前付きトランジション -->
<img transition:name="hero" src={post.heroImage} />
<!-- カスタムアニメーション -->
<div transition:animate="slide">コンテンツ</div>
<div transition:animate="fade">コンテンツ</div>
<div transition:animate="none">アニメーションなし</div>
<!-- 名前を指定して維持 (複数の永続要素の場合) -->
<video transition:persist="media-player" />
ライフサイクルイベント
<script>
document.addEventListener('astro:before-preparation', (e) => {
// 新しいページが取得される前 - ナビゲーションをキャンセル、ローディング表示
});
document.addEventListener('astro:after-preparation', (e) => {
// 新しいページが取得された後、交換前
});
document.addEventListener('astro:before-swap', (e) => {
// DOM交換動作をカスタマイズ
});
document.addEventListener('astro:after-swap', () => {
// DOM更新完了 - スクリプトを再初期化
});
document.addEventListener('astro:page-load', () => {
// ページが完全に読み込まれた (初回 + すべてのナビゲーションで発火)
// View Transitionsでは、DOMContentLoadedの代わりにこれを使用する
});
</script>
戻る/進む処理
// astro.config.mjs
export default defineConfig({
prefetch: {
prefetchAll: true, // ホバー時にすべてのリンクをプリフェッチ
defaultStrategy: 'hover', // 'hover' | 'tap' | 'viewport' | 'load'
},
});
<!-- リンクごとのプリフェッチ制御 -->
<a href="/about" data-astro-prefetch>ホバーでプリフェッチ (デフォルト)</a>
<a href="/blog" data-astro-prefetch="viewport">表示されたらプリフェッチ</a>
<a href="/contact" data-astro-prefetch="load">すぐにプリフェッチ</a>
<a href="/external" data-astro-prefetch="false">プリフェッチしない</a>
デプロイ決定木
どこにデプロイするか?
│
├─ エッジコンピューティング + Cloudflareエコシステム (KV、D1、R2) が必要?
│ └─ Cloudflare Pages/Workers
│ アダプター: @astrojs/cloudflare
│ 最適: グローバルエッジ、Workersバインディング、コスト効率
│
├─ サーバーレス + Vercelエコシステム (ISR、アナリティクス) が必要?
│ └─ Vercel
│ アダプター: @astrojs/vercel
│ 最適: Next.jsからの移行、画像最適化、ISR
│
├─ サーバーレス + Netlifyエコシステム (フォーム、認証) が必要?
│ └─ Netlify
│ アダプター: @astrojs/netlify
│ 最適: JAMstack、組み込みフォーム、分割テスト
│
├─ 完全なサーバー制御 (Docker、カスタムランタイム) が必要?
│ └─ Node.js (スタンドアロン、またはExpress/Fastify)
│ アダプター: @astrojs/node
│ 最適: セルフホスト、WebSocket、長時間実行プロセス
│
└─ 純粋な静的サイト (SSR不要)?
└─ 任意の静的ホスト (GitHub Pages、S3、Cloudflare Pages)
アダプター不要、output: 'static'
最適: ブログ、ドキュメント、マーケティングサイト
アダプターのインストール
# Cloudflare
npx astro add cloudflare
# Vercel
npx astro add vercel
# Netlify
npx astro add netlify
# Node.js
npx astro add node
よくある落とし穴
| 落とし穴 | 原因 | 修正方法 |
|---|---|---|
| ハイドレーションの不一致エラー | サーバーHTMLとクライアントレンダリングの差異 (日付、ランダムID、ブラウザAPI) | ブラウザ依存のコンポーネントには client:only を使用するか、決定論的なレンダリングを保証する |
クライアントで import.meta.env が未定義 | PUBLIC_ プレフィックス付きの変数のみがクライアントサイドコードに公開される | PUBLIC_MY_VAR に名前を変更するか、サーバーからプロップ経由で渡す |
| SSGで動的ルートが404になる | getStaticPaths() がすべての可能なパラメータを返していない | getStaticPaths() がすべての有効なパスを返すことを確認するか、ハイブリッド/SSRに切り替える |
| 画像が最適化されない | Astroの <Image /> コンポーネントの代わりに <img> を使用している | astro:assets からインポート: import { Image } from 'astro:assets'、srcにはローカルインポートを使用する |
| アダプターなしでSSRが失敗する | output: 'server' または 'hybrid' にはデプロイアダプターが必要 | アダプターをインストール: npx astro add cloudflare (または vercel, netlify, node) |
| MDXコンポーネントがレンダリングされない | カスタムコンポーネントがMDXコンテンツに渡されていない | <Content components={{ MyComponent }} /> でコンポーネントを渡すか、astro.config.mjs のMDX設定を使用する |
| コンテンツコレクションのスキーマ変更が反映されない | 型生成がキャッシュされ、古い .astro 型が残っている | astro sync を実行して型を再生成し、開発サーバーを再起動する |
Astroコンポーネントでの client:* | クライアントディレクティブはフレームワークコンポーネント (React、Vue、Svelte) でのみ機能する | Astroコンポーネントは静的にのみ使用可能。インタラクティブな部分をフレームワークコンポーネントに抽出する |
document / window が定義されていない | サーバーサイドコードはブラウザのグローバル変数にアクセスできない | if (typeof window !== 'undefined') でガードするか、client:only に移動する |
| コンポーネント間でスタイルが漏れる | スコープ付きスタイルの代わりにグローバルCSSを使用している | <style> (.astroではデフォルトでスコープ付き) または意図的に <style is:global> を使用する |
| View Transitionsがスクリプトを壊す | DOMContentLoaded はView Transitionsで一度しか発火しない | 代わりに astro:page-load イベントを使用する。これはすべてのナビゲーションで発火する |
| 環境変数が本番環境で欠落する | .env が読み込まれていない、またはプラットフォームの環境変数が設定されていない | astro.config.mjs で envField を使用してバリデーションする。プラットフォームのダッシュボードで変数を設定する |
プロダクションセキュリティチェックリスト
すべてのプロダクションデプロイでは、以下に対処すること:
- CSPヘッダー - 制限的な
Content-Security-Policyを設定する (references/deployment.mdのミドルウェアパターンを参照) - リモート画像の制限 - 明示的な
image.domains/remotePatterns許可リストを適用する。ユーザー入力から画像URLを導出しないこと (SSRFリスク) - ホストヘッダーの検証 - リクエストホストが期待されるドメインと一致することをミドルウェアで検証する (SSR/ハイブリッドのみ)
- シークレット管理 - Cloudflareでは、コードに焼き付ける環境変数ではなく、Workers バインディング (
wrangler secret put) を使用する。その他のプラットフォームでは、プラットフォームのシークレットストアを使用する - HTTPSのみ - すべての外部リソース (スクリプト、画像、フォント) がHTTPS経由で読み込まれることを確認する
- 入力バリデーション - SSRコンテキスト内のすべてのユーザー入力をサニタイズする (クエリパラメータ、フォーム本文、Cookie)
リファレンスファイル
| ファイル | 内容 | 行数 |
|---|---|---|
references/content-collections.md | スキーマパターン、Zod型、クエリ、MDX、コンテンツレイヤーAPI、移行 | ~500 |
references/islands-rendering.md | ハイドレーション戦略、パフォーマンス、フレームワーク間の考慮事項 | ~400 |
references/view-transitions.md | アニメーション、ライフサイクル、アクセシビリティ、アダプティブな動作 | ~350 |
references/deployment.md | アダプター設定、環境変数、ミドルウェア、CSP、CI/CD | ~450 |
references/dev-tools.md | Astro Dev Toolbar、インテグレーション、プラグイン、デバッグ | ~200 |
references/astro-cheatsheet.md | クイックリファレンス (CLIコマンド、ディレクティブ、設定、テンプレート構文) | ~150 |