この記事の要点
• Astroの島アーキテクチャでJS最小化の高速サイトを構築する
• コンテンツコレクションでMarkdownを型安全に管理する
• ファイルベースルーティングとレイアウトで効率的にページを作る
このチュートリアルで学ぶこと
- Astroプロジェクトの作成と基本構造
- .astroコンポーネントの書き方
- レイアウトとスロット
- ルーティングとページ
- Markdownとコンテンツコレクション
- Tailwind CSSの統合
- 本番ビルドとデプロイ準備
Astroはコンテンツ中心のWebサイトを爆速で配信するための静的サイトジェネレータです。デフォルトでJavaScriptを最小化し、必要な部分だけインタラクティブにする「島アーキテクチャ」が特徴です。
前提条件・必要な環境
- Node.js 18.20.8、20.3.0、22.0.0 以上
- npm 10以上 もしくは pnpm
- HTML / CSS / JavaScriptの基本知識
- ターミナルとエディタ(VS Code推奨)
確認:
node -v
npm -v
インストール / セットアップ
実践メモ: npm create astro@latestで対話的にプロジェクトを作成できます。テンプレートは「Empty」から始めるのがおすすめです。
公式CLIで対話的に作成できます。
npm create astro@latest my-blog
選択肢の例:
- Template:
Empty - TypeScript:
Strict - Install dependencies:
Yes - Initialize git:
Yes
完了後、開発サーバーを起動します。
cd my-blog
npm run dev
ブラウザで http://localhost:4321 にアクセスすると、Astroのスタートページが見えます。
ディレクトリ構成
my-blog/
├── public/
├── src/
│ ├── components/
│ ├── layouts/
│ ├── pages/
│ └── content/
├── astro.config.mjs
├── tsconfig.json
└── package.json
src/pages/配下のファイルがそのままURLになりますsrc/components/は再利用するUI部品src/layouts/は共通レイアウトsrc/content/はコンテンツコレクション(後述)
基本概念
.astroコンポーネント
ポイント: .astroファイルの---で囲まれた部分はサーバー側で実行されるため、APIフェッチやファイル読み込みも自由に行えます。
--- で囲まれたフロントマターにJavaScript / TypeScript、その下にHTMLを書きます。
---
const name = "Astro";
---
<h1>Hello, {name}!</h1>
サーバー側で実行されるため、ファイル読み込みやfetchも自由に行えます。
島アーキテクチャ
デフォルトでは静的HTMLとして出力。Reactなどのコンポーネントを使うときは client:load などの指示子を付けたときだけJSが配信されます。
ファイルベースルーティング
src/pages/about.astro → /about、src/pages/blog/[slug].astro で動的ルートになります。
ステップバイステップ実装
シンプルなブログサイトを作っていきます。
Step 1: レイアウトを作る
src/layouts/BaseLayout.astro:
---
interface Props {
title: string;
description?: string;
}
const { title, description = "My Astro Blog" } = Astro.props;
---
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} />
<title>{title}</title>
</head>
<body>
<header>
<nav>
<a href="/">Home</a> |
<a href="/blog">Blog</a> |
<a href="/about">About</a>
</nav>
</header>
<main>
<slot />
</main>
<footer>
<p>© 2026 My Astro Blog</p>
</footer>
</body>
</html>
<slot /> の位置に子コンポーネントの中身が入ります。これがレイアウトの核心です。
Step 2: トップページ
src/pages/index.astro:
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="Home">
<h1>Welcome to My Blog</h1>
<p>Astroで作った静的サイトの例です。</p>
</BaseLayout>
Step 3: コンポーネントを切り出す
src/components/Card.astro:
---
interface Props {
title: string;
href: string;
body: string;
}
const { title, href, body } = Astro.props;
---
<a href={href} class="card">
<h2>{title}</h2>
<p>{body}</p>
</a>
<style>
.card {
display: block;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
text-decoration: none;
color: inherit;
}
.card:hover {
background: #f5f5f5;
}
</style>
ポイント: <style>はそのコンポーネントに自動スコープされるため、スタイルの衝突を気にする必要がありません。
Step 4: コンテンツコレクションをセットアップ
実践メモ: content.config.tsでZodスキーマを定義すると、frontmatterの型チェックが自動で行われます。
src/content.config.ts を作成 (Astro 5 以降):
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).optional(),
}),
});
export const collections = { blog };
Step 5: Markdown記事を追加
src/content/blog/hello-astro.md:
---
title: "Hello Astro"
description: "Astroで初めて書いた記事"
pubDate: 2026-04-01
tags: ["astro", "introduction"]
---
# Hello Astro
これは **Markdown** で書いた記事です。
- 簡単
- 高速
- 楽しい
src/content/blog/island-architecture.md も同様に追加してみてください。
Step 6: 記事一覧ページ
src/pages/blog/index.astro:
---
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import Card from '../../components/Card.astro';
const posts = (await getCollection('blog')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<BaseLayout title="Blog">
<h1>記事一覧</h1>
<ul>
{posts.map((post) => (
<li>
<Card
title={post.data.title}
href={`/blog/${post.id}`}
body={post.data.description}
/>
</li>
))}
</ul>
</BaseLayout>
Step 7: 記事詳細ページ(動的ルート)
src/pages/blog/[...slug].astro:
---
import { getCollection, render } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.id },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await render(post);
---
<BaseLayout title={post.data.title} description={post.data.description}>
<article>
<h1>{post.data.title}</h1>
<time datetime={post.data.pubDate.toISOString()}>
{post.data.pubDate.toLocaleDateString('ja-JP')}
</time>
<Content />
</article>
</BaseLayout>
注意: 動的ルート([slug].astro)では必ずgetStaticPathsをエクスポートしてください。忘れるとビルドエラーになります。
Step 8: Tailwind CSSを追加 (任意)
npx astro add tailwind
設定ファイルが自動更新されます。src/styles/global.css をimportして使用します。
Step 9: ビルド
npm run build
npm run preview
dist/ に静的ファイルが出力されます。これをそのままNetlify、Vercel、Cloudflare Pagesなどにデプロイできます。
完成コード全体
my-blog/
├── public/
│ └── favicon.svg
├── src/
│ ├── components/
│ │ └── Card.astro
│ ├── content/
│ │ └── blog/
│ │ ├── hello-astro.md
│ │ └── island-architecture.md
│ ├── layouts/
│ │ └── BaseLayout.astro
│ ├── pages/
│ │ ├── blog/
│ │ │ ├── [...slug].astro
│ │ │ └── index.astro
│ │ ├── about.astro
│ │ └── index.astro
│ └── content.config.ts
├── astro.config.mjs
├── package.json
└── tsconfig.json
各ファイルは前のステップのコードで動作確認できます。
よくあるエラーと対処
Cannot find module 'astro:content'
注意: Astroの自動生成型がまだ作られていない場合に発生します。npx astro syncを実行するか、開発サーバーを再起動してください。
Astroの自動生成型がまだ作られていない可能性があります。一度開発サーバーを再起動するか、
npx astro sync
を実行してください。
getStaticPaths() is required for dynamic routes
動的ルート([slug].astro)では必ず getStaticPaths をエクスポートします。SSRモードでは prerender = false に切り替えることもできます。
Markdownのfrontmatterスキーマ違反
Zodスキーマと一致しないと開発サーバーがエラーを出します。日付は ISO 形式 (2026-04-01) を使うのが確実です。
<style> がグローバルに漏れる
.astro の <style> は自動でスコープされますが、<style is:global> を使うと意図的にグローバルになります。意図せず使っていないか確認しましょう。
ベストプラクティス
- デフォルトはサーバーレンダリング: JSが必要なときだけクライアント指示子(
client:load/client:idle/client:visible)を使う - コンテンツはコレクションに: 型安全になり、frontmatterのミスを早期に発見できる
- 画像は
<Image />コンポーネント: 自動最適化が効く (astro:assets) - 公式インテグレーションを活用:
npx astro add <name>でTailwindやMDXなどを安全に追加できる - ページレベルのSEO: layoutでmetaタグを受け取れるようpropsで制御
- Lighthouseで計測: Astroの強みは速度。CIに組み込んでスコアを保つ
次のステップ(発展課題)
- MDXを導入してJSXコンポーネントを記事中で使う
- RSSフィードを
@astrojs/rssで生成 - サイトマップを
@astrojs/sitemapで出力 - Reactアイランドを試す: 検索フォームだけClient-side renderingにしてみる
- View Transitions API: ページ遷移を滑らかに
- Cloudflare Pagesにデプロイ: GitHub連携で自動ビルド
- i18n対応: 多言語ブログ化
まとめ
Astroは「速い」「シンプル」「コンテンツ中心」の三拍子が揃ったフレームワークです。
- ファイルベースルーティングで学習コストが低い
- コンテンツコレクションでMarkdownを型安全に扱える
- JS最小化で抜群のパフォーマンス
- 必要に応じてReact/Vue/Svelteを混ぜられる柔軟さ
ブログ、ドキュメントサイト、マーケティングLPなど、コンテンツ重視のサイトを作るならAstroは第一候補になるでしょう。
補足: クライアントコンポーネントの統合
Astroは公式インテグレーションでReact / Vue / Svelte / Solid / Preactなどを混ぜて使えます。例えばReactを追加する場合:
npx astro add react
src/components/Counter.tsx:
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
.astro から呼び出すときに client directive を付けます。
---
import Counter from '../components/Counter.tsx';
---
<Counter client:visible />
| ディレクティブ | タイミング |
|---|---|
client:load | ページロード直後 |
client:idle | ブラウザがアイドル状態になったとき |
client:visible | コンポーネントが画面に入ったとき |
client:media | メディアクエリにマッチしたとき |
client:only | クライアントレンダリングのみ |
必要なものだけJSが配信されるため、ページの初期表示は静的HTMLのままで高速です。
補足: 環境変数の扱い
Astroはビルド時の環境変数を import.meta.env 経由で参照します。
const apiUrl = import.meta.env.PUBLIC_API_URL;
PUBLIC_ プレフィックスが付いたものだけクライアントに露出します。サーバー専用は PUBLIC_ を付けないこと。.env ファイルで定義します。
参考リソース
- Astro Documentation
- Astro GitHub Repository
- Astro Build a Blog Tutorial
- Content Collections Guide
- Astro Integrations Directory