Astro Web Framework
概要
Astroは、ブログ、ドキュメント、ポートフォリオ、マーケティングサイト、Eコマースなど、コンテンツが豊富なWebサイト向けに設計されたWebフレームワークです。その中核的な革新はアイランドアーキテクチャです。デフォルトでは、AstroはブラウザにJavaScriptを一切送信しません。インタラクティブなコンポーネントは、選択的に独立した「アイランド」としてハイドレーションされます。Astroは、React、Vue、Svelte、Solid、その他のUIフレームワークを同じプロジェクト内で同時にサポートし、コンポーネントごとに適切なツールを選択できます。
このスキルを使用するタイミング
- ブログ、ドキュメントサイト、マーケティングページ、ポートフォリオを構築する場合
- パフォーマンスとCore Web Vitalsが最優先事項である場合
- MarkdownまたはMDXファイルを使用したコンテンツ主体のプロジェクトの場合
- 動的ルートにオプションのSSRを備えたSSG(静的)出力が必要な場合
- ユーザーが
.astroファイル、Astro.props、コンテンツコレクション、client:ディレクティブについて質問した場合
仕組み
ステップ 1: プロジェクトのセットアップ
npm create astro@latest my-site
cd my-site
npm install
npm run dev
必要に応じてインテグレーションを追加:
npx astro add tailwind # Tailwind CSS
npx astro add react # Reactコンポーネントのサポート
npx astro add mdx # MDXサポート
npx astro add sitemap # 自動 sitemap.xml
npx astro add vercel # Vercel SSRアダプター
プロジェクト構造:
src/
pages/ ← ファイルベースルーティング (.astro, .md, .mdx)
layouts/ ← 再利用可能なページシェル
components/ ← UIコンポーネント (.astro, .tsx, .vue, など)
content/ ← 型安全なコンテンツコレクション (Markdown/MDX)
styles/ ← グローバルCSS
public/ ← 静的アセット(そのままコピー)
astro.config.mjs ← フレームワーク設定
ステップ 2: Astroコンポーネントの構文
.astroファイルは、上部にコードフェンス(サーバー専用)があり、その下にテンプレートがあります:
---
// src/components/Card.astro
// このブロックはサーバー上でのみ実行されます — ブラウザでは決して実行されません
interface Props {
title: string;
href: string;
description: string;
}
const { title, href, description } = Astro.props;
---
<article class="card">
<h2><a href={href}>{title}</a></h2>
<p>{description}</p>
</article>
<style>
/* このコンポーネントに自動的にスコープされます */
.card { border: 1px solid #eee; padding: 1rem; }
</style>
ステップ 3: ファイルベースのページとルーティング
src/pages/index.astro → /
src/pages/about.astro → /about
src/pages/blog/[slug].astro → /blog/:slug (動的)
src/pages/blog/[...path].astro → /blog/* (キャッチオール)
getStaticPaths を使用した動的ルート:
---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<h1>{post.data.title}</h1>
<Content />
ステップ 4: コンテンツコレクション
コンテンツコレクションを使用すると、MarkdownおよびMDXファイルへの型安全なアクセスが可能になります:
// src/content/config.ts
import { z, defineCollection } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
const posts = (await getCollection('blog'))
.filter(p => !p.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---
<ul>
{posts.map(post => (
<li>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
<time>{post.data.date.toLocaleDateString()}</time>
</li>
))}
</ul>
ステップ 5: アイランド — 選択的ハイドレーション
デフォルトでは、UIフレームワークコンポーネントはJSなしで静的なHTMLにレンダリングされます。client:ディレクティブを使用してハイドレーションします:
---
import Counter from '../components/Counter.tsx'; // Reactコンポーネント
import VideoPlayer from '../components/VideoPlayer.svelte';
---
<!-- 静的HTML — ブラウザにJavaScriptは送信されません -->
<Counter initialCount={0} />
<!-- ページ読み込み時に即座にハイドレーション -->
<Counter initialCount={0} client:load />
<!-- コンポーネントがビューポート内にスクロールされたときにハイドレーション -->
<VideoPlayer src="/demo.mp4" client:visible />
<!-- ブラウザがアイドル状態のときのみハイドレーション -->
<Analytics client:idle />
<!-- 特定のメディアクエリでのみハイドレーション -->
<MobileMenu client:media="(max-width: 768px)" />
ステップ 6: レイアウト
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
description?: string;
}
const { title, description = 'My Astro Site' } = Astro.props;
---
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<nav>...</nav>
<main>
<slot /> <!-- ページコンテンツはここにレンダリングされます -->
</main>
<footer>...</footer>
</body>
</html>
---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="私たちについて">
<h1>私たちについて</h1>
<p>会社へようこそ...</p>
</BaseLayout>
ステップ 7: SSRモード(オンデマンドレンダリング)
アダプターを設定して、動的ページのSSRを有効にします:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'hybrid', // 'static' | 'server' | 'hybrid'
adapter: vercel(),
});
export const prerender = false を使用して、個々のページをSSRにオプトインします。
例
例 1: RSSフィード付きブログ
// src/pages/rss.xml.ts
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: 'My Blog',
description: '最新の投稿',
site: context.site,
items: posts.map(post => ({
title: post.data.title,
pubDate: post.data.date,
link: `/blog/${post.slug}/`,
})),
});
}
例 2: APIエンドポイント (SSR)
// src/pages/api/subscribe.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
const { email } = await request.json();
if (!email) {
return new Response(JSON.stringify({ error: 'メールアドレスが必要です' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
await addToNewsletter(email);
return new Response(JSON.stringify({ success: true }), { status: 200 });
};
例 3: アイランドとしてのReactコンポーネント
// src/components/SearchBox.tsx
import { useState } from 'react';
export default function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
async function search(e: React.FormEvent) {
e.preventDefault();
const data = await fetch(`/api/search?q=${query}`).then(r => r.json());
setResults(data);
}
return (
<form onSubmit={search}>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button type="submit">検索</button>
<ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
</form>
);
}
---
import SearchBox from '../components/SearchBox.tsx';
---
<!-- 即座にハイドレーション — このアイランドはインタラクティブです -->
<SearchBox client:load />
ベストプラクティス
- ✅ ほとんどのコンポーネントは静的な
.astroファイルとして維持する — インタラクティブでなければならないものだけをハイドレーションする - ✅ すべてのMarkdown/MDXコンテンツにコンテンツコレクションを使用する — 型安全性と自動バリデーションが得られます
- ✅ 画面外のコンポーネントには
client:loadよりもclient:visibleを優先する — 初期JSを削減できます - ✅ 環境変数には
import.meta.envを使用する — 公開変数にはPUBLIC_プレフィックスを付ける - ✅ 完全なSPAを必要とせずスムーズなページ遷移を実現するために、
astro:transitionsから<ViewTransitions />を追加する - ❌ すべてのコンポーネントに
client:loadを使用しない — これによりAstroのパフォーマンス上の利点が失われます - ❌ クライアント向けテンプレートで使用される
.astroフロントマターに秘密情報を入れない - ❌ 静的モードで動的ルートに
getStaticPathsを省略しない — ビルドが失敗します
セキュリティと安全性に関する注意事項
.astroファイルのフロントマター内のコードはサーバーサイドでのみ実行され、ブラウザに公開されることはありません。- 機密性の低い値にのみ
import.meta.env.PUBLIC_*を使用してください。プライベート環境変数(PUBLIC_プレフィックスなし)はクライアントに送信されることはありません。 - SSRモードを使用する場合は、データベースクエリやAPI呼び出しの前にすべての
Astro.request入力を検証してください。 set:htmlでレンダリングする前に、ユーザーが提供したコンテンツをサニタイズしてください。これは自動エスケープをバイパスします。
よくある落とし穴
-
問題: React/VueコンポーネントからのJavaScriptがブラウザで実行されない 解決策:
client:ディレクティブ(client:load、client:visibleなど)を追加する — これがないと、コンポーネントは静的なHTMLとしてのみレンダリングされます。 -
問題: 開発中にコンテンツを更新した後、
getStaticPathsのデータが古いままである 解決策: Astroの開発サーバーはコンテンツファイルを監視しています —content/config.tsへの変更が反映されない場合は再起動してください。 -
問題:
Astro.propsの型がanyであり、オートコンプリートが機能しない 解決策: フロントマターでPropsインターフェースまたは型を定義すると、Astroが自動的に推論します。 -
問題:
.astroコンポーネントからのCSSが他のコンポーネントに漏れる 解決策:.astroの<style>タグ内のスタイルは自動的にスコープされます。子要素を意図的にターゲットにする場合にのみ:global()を使用してください。
関連スキル
@sveltekit— リアクティブなUIを備えたフルスタックフレームワークが必要な場合(Astroのコンテンツ重視に対して)@nextjs-app-router-patterns— Reactファーストのフルスタックフレームワークが必要な場合@tailwind-patterns— Tailwind CSSを使用したAstroサイトのスタイリング@progressive-web-app— AstroサイトへのPWA機能の追加
制限事項
- このスキルは、タスクが上記で説明された範囲に明確に一致する場合にのみ使用してください。
- 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
- 必要な入力、権限、安全性の境界、または成功基準が不足している場合は、立ち止まって明確化を求めてください。