Astro入門:Islandsで必要な所だけJSにして爆速サイトを作る
Astroが速い理由はJSをほぼ送らないから。Islands Architectureと型安全なContent Collections、出力モードの選び方を、このブログ自体をAstroで回す僕の実体験とコードで解説。
Reactでブログを作り直したとき、トップページを開くたびに何百KBものJavaScriptがダウンロードされていました。やってることは「記事を一覧で出す」だけ。動く部品はゼロなのに、です。
スマホの実機で開くと、文字が出るまでワンテンポ遅れる。あの「待たされてる感」が、どうしても消せませんでした。
そこで試したのがAstroです。同じ一覧ページを移植したら、ブラウザに送られたJavaScriptは0バイトになりました。表示はパッと出る。スコアも跳ね上がる。「あれ、今まで何のためにあんなにJSを配ってたんだ?」と本気で考え込んだのを覚えています。
今このサイト(claudecode-lab.com)はAstroで動いています。記事数は数百本。多言語。それでもビルドは通るし、ページは軽い。この記事では、なぜAstroがそんなに速いのか、その中心にある Islands Architecture(アイランドアーキテクチャ) と Content Collections(型安全な記事管理) を、実際に手を動かしてきた目線で書きます。
この記事の要点
- Astroが速い最大の理由は デフォルトでJavaScriptを1バイトも送らない こと。動く部品にだけ後からJSを足す。
- その「動く部品だけ島にする」発想が Islands Architecture。
client:visibleのような目印を1つ付けるだけで、必要な所だけブラウザで動く。 - 記事は Content Collections で「型のあるデータ」として管理する。frontmatterの書き間違いをビルド時に弾けるので、本数が増えても壊れない。
- 出力モードは
static(全部HTMLで出す)/server(毎回サーバーで作る)/ ページ単位の併用、の3択。ブログなら基本はstaticでいい。 - 機能はnpmの インテグレーション(MDX・サイトマップ・画像最適化など)を足して組む。最初から全部入れない。
Astroが速い理由は、JSを送らないから
普通のSPA(React等で全ページを組む作り)は、サーバーから空っぽのHTMLとデカいJavaScriptを送り、ブラウザ側で画面を組み立てます。だから「文字が出るまで一瞬待つ」。
Astroは逆です。ビルドの時点でHTMLを完成させて送る。ブラウザは届いたHTMLをそのまま表示するだけなので、すぐ読める。そして動く部品が無いページには、JavaScriptを一切付けません。これがAstroのデフォルトでありAstro公式も「zero JavaScript by default」と言い切っている強みです。
ここで多くの人が誤解します。「JSを送らない=Reactが使えない静的サイトジェネレータでしょ?」と。違います。ReactもVueもSvelteも、コンポーネントとして書けます。ただ書いただけではブラウザに出ていかない。サーバー側でHTMLに変換されて終わり、です。「動かしたい部品」だけ、明示的に動かす。この線引きがAstroの肝です。
ブログ、ドキュメント、LP、ポートフォリオ——「読むページ」が中心のサイトだと、この差は体感でわかります。僕のサイトは記事ページの90%以上がJSゼロで、目次のハイライトや検索ボックスといった「本当に動く所」にだけJSが乗っています。
Islands Architectureを一言で
Islands(島)は、静的なHTMLという海の中に、動く部品だけポツンと浮かべるイメージです。
ページ全体は静かなHTML。その中で、検索ボックスやカルーセル、いいねボタンといった「触ると反応する部品」だけが島として独立して動く。島と島は別々に動くので、検索ボックスのJSが重くても、ページの表示そのものは待たされません。
Astroではこれを client directive(クライアントディレクティブ) という目印で指定します。コンポーネントに1語添えるだけ。Astro公式が挙げている主なものはこれです。
| 目印 | 動き出すタイミング | 向いている部品 |
|---|---|---|
client:load | ページ読み込みと同時 | 最初から操作される検索バー・ヘッダーのメニュー |
client:idle | ブラウザが手すきになってから | 急がない部品(フィードバックボタン等) |
client:visible | 画面にスクロールで入ってから | ページ下のカルーセル・コメント欄 |
client:media | 画面幅などの条件を満たしたとき | スマホだけ出すメニュー |
client:only | サーバー描画せずブラウザだけで動かす | サーバーで動かせない部品 |
迷ったら client:visible が一番安全です。見えてから初めてJSを読むので、最初の表示を邪魔しません。逆に「とりあえず全部 client:load」をやると、Astroを使う意味が半分消えます。僕はこれで一度サイトを重くしました(後述)。
ちなみに最近のAstroには server:defer(サーバーアイランド)もあります。ページの大半は静的キャッシュで出しつつ、「ログイン中のユーザー名」みたいな個人ごとに変わる部分だけ後からサーバーで差し込む、という芸当もやってのけます。ブログ運用ではまだ出番は少ないですが、覚えておくと引き出しが増えます。
出力モードは static / server を使い分ける
Astroには出力モード(output)という設定があり、ページを「いつ作るか」を決めます。
static(デフォルト): ビルド時に全ページをHTMLにして出す。配信は速く、サーバー代も安い。ブログ・ドキュメント・LPはまずこれ。server: リクエストが来るたびにサーバーで作る。ログイン後の画面や、毎回中身が変わるページ向け。- ページ単位の併用: 基本
staticにしておき、動的にしたいページだけexport const prerender = falseでサーバー描画に切り替える、という混在も可能。
判断に迷ったら、「このページの中身は人によって・時間によって変わるか?」で考えます。変わらないなら static。SSG(静的生成)とSSR(毎回生成)の使い分けをもっと深掘りしたいなら、Claude CodeでSSRとSSGを選ぶ判断基準に詳しくまとめてあります。
ブログCMSの設計そのものを「自作するか、microCMSのような外部サービスに任せるか」で迷っている人は、このサイトをファイルベースで回している実例をブログCMSは自作かヘッドレスかに書いたので、出力モードの話とセットで読むと判断しやすいはずです。
Content Collectionsで記事を「型のあるデータ」にする
記事が増えると、必ず誰かがfrontmatterを壊します。pubDate を書き忘れる、tags を配列じゃなく文字列で書く、description が長すぎる。手作業のチェックでは、いつか見落とします。
Content Collectionsは、MarkdownやMDXの記事に**スキーマ(型のルール)**を持たせる仕組みです。ルールに合わない記事があると、npm run build が「○○行目のここが変だよ」とビルド時に止めてくれる。公開後に気づくより100倍ラクです。
Astro 5系では設定ファイルは src/content.config.ts、データの読み込みは glob() ローダー、ルール定義はZodで書くのが今の形です(古い記事には src/content/config.ts という旧パスが出てきますが、新規はこちら)。
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
// src/content/blog/ 配下の .md / .mdx を記事として読み込む
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
schema: z.object({
title: z.string().max(80), // 80字を超えたらビルドで止まる
description: z.string().max(120), // メタ説明は120字以内に強制
pubDate: z.coerce.date(), // "2026-02-03" を自動でDate型に
updatedDate: z.coerce.date().optional(),
tags: z.array(z.string()).default([]), // 配列でなければエラー
draft: z.boolean().default(false), // 下書きフラグ
heroImage: z.string().optional(),
}),
});
export const collections = { blog };
このスキーマを置くだけで、description の文字数オーバーや日付の打ち間違いが自動で弾かれます。多言語サイトでは翻訳作業中にfrontmatterを壊しがちなので、この門番は効きます。
記事の取り出しは getCollection() を使います。下書きを除き、公開日の新しい順に並べる定番の書き方がこれです。getCollection() の戻り順は保証されないので、自分でsortするのがポイント。
---
// src/pages/blog/[...page].astro — 記事一覧(ページネーション付き)
import { getCollection } from 'astro:content';
import PostCard from '../../components/PostCard.astro';
export async function getStaticPaths({ paginate }) {
// draft が true の記事は除外
const posts = (await getCollection('blog', ({ data }) => data.draft !== true))
// 公開日の降順に明示的に並べ替える
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
return paginate(posts, { pageSize: 12 });
}
const { page } = Astro.props;
---
<section class="mx-auto max-w-5xl px-4 py-10">
<h1 class="text-3xl font-bold">Blog</h1>
<div class="mt-8 grid gap-6 md:grid-cols-2">
{page.data.map((post) => <PostCard post={post} />)}
</div>
<nav class="mt-10 flex justify-between">
{page.url.prev ? <a href={page.url.prev}>前へ</a> : <span />}
{page.url.next ? <a href={page.url.next}>次へ</a> : <span />}
</nav>
</section>
型が効いているので、PostCard 側で post.data.title を打ち間違えればエディタが赤線を引いてくれます。記事という「ただのファイル」が、補完の効くデータに変わる。これがContent Collectionsの一番おいしい所です。
まず動かす:30分で雛形からIslandsまで
説明より動かすのが早いです。新規プロジェクトを作り、検索ボックスだけを島にする、という最小の流れをやってみます。Node.jsがあれば動きます。
# 1. 雛形を作る(対話で「Empty」や「Blog」を選ぶ)
npm create astro@latest my-astro-site
cd my-astro-site
# 2. よく使うインテグレーションを足す(MDX・サイトマップ・React)
npx astro add mdx sitemap react
# 3. 開発サーバーを起動
npm run dev
npx astro add は、必要な設定を astro.config.mjs に自動で書き足してくれます。手で設定をいじる前に、まずこれで足すのが安全です。出来上がる設定の最小形はこんな感じになります。
// astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import react from '@astrojs/react';
export default defineConfig({
site: 'https://example.com', // 本番URL。sitemapやcanonicalの生成に使われる
output: 'static', // ブログなら static で十分
integrations: [mdx(), sitemap(), react()],
});
site を本番URLにしておくのを忘れずに。仮URLのまま公開すると、サイトマップやcanonicalのURLがずれて検索エンジンが混乱します。僕は一度これで「なぜか正規URLが localhost になってる」事故をやりました。
そして肝心のIslands。Reactで作った検索ボックスを、トップページで見えてから動かすには、client:visible を1語足すだけです。
---
// src/pages/index.astro
import SearchBox from '../components/SearchBox.tsx'; // 普通のReactコンポーネント
import LatestPosts from '../components/LatestPosts.astro';
---
<h1>My Astro Site</h1>
<!-- この1語が無いと、ただのHTMLとして出るだけで動かない -->
<SearchBox client:visible />
<!-- 動く必要のない一覧は、目印を付けない=JSゼロのまま -->
<LatestPosts />
これだけで、検索ボックスは画面に入った瞬間にJSを読んで動き出し、記事一覧はJSゼロのまま静かに表示されます。「動く所だけ島にする」が、コード上ではこの1語に化けるわけです。
僕がAstroでやらかした失敗3つ
正直に書きます。最初のAstroサイトは、Astroの強みを自分で潰していました。
ひとつ目は、全コンポーネントに client:load を付けたこと。「動いた方が安心」と全部に目印を付けたら、結局ページ表示と同時に全部のJSが走って、SPA時代と変わらない重さに戻りました。Astroを使う意味、ほぼ消滅です。今は原則 client:visible、本当に最初から要るものだけ client:load にしています。
ふたつ目は、古いContent Collectionsの記事をそのままコピーしたこと。ネットには src/content/config.ts と type: 'content' という旧API(Astro 5より前)の例が大量に残っています。それを新規プロジェクトに貼ったらビルドが通らず、原因にしばらく気づけませんでした。新規は src/content.config.ts + glob() + astro/zod。これだけは公式で確認してください。
みっつ目は、画像とOGPを後回しにしたこと。記事は書けた、ビルドも通った、で満足して公開したら、SNSでシェアされたとき画像も説明も出ない味気ないカードになっていました。heroImage のパス、alt相当の説明、OGP生成は、本文と同じタイミングで決めるべきでした。検索流入まで含めた仕上げはClaude CodeでのSEO最適化にまとめてあります。
よくある質問
Q. Astroは結局ただの静的サイトジェネレータ? ReactやVueは使えない?
使えます。React・Vue・Svelte・Preact・Solidをそのままコンポーネントとして書けて、しかも混在もできます。違いは「書いただけではブラウザに出ない」点で、client:* を付けた部品だけが動きます。
Q. SSG(static)とSSR(server)、ブログはどっちにすべき?
中身が人や時間で変わらないブログは static で十分です。配信が速くサーバー代も抑えられます。ログイン後の画面など毎回中身が変わるページが必要になったら、そのページだけサーバー描画に切り替えれば足ります。
Q. Content Collectionsは記事が少なくても入れる価値ある?
あります。frontmatterの型ミスをビルド時に弾けるのと、post.data.title のような取り出しで補完と型チェックが効くのが大きい。むしろ少ないうちに入れておくと、増えてから慌てません。
Q. client:load と client:visible はどう使い分ける?
最初の画面で即操作される部品(検索バー等)は client:load、スクロールで初めて見える部品(下部のカルーセル・コメント欄)は client:visible。迷ったら表示を邪魔しない client:visible を選んでおくのが無難です。
Q. Tailwindや画像最適化はどう足す?
npx astro add でインテグレーションとして足します。設定を自動で書き込んでくれるので、手で astro.config.mjs をいじる前にまずこれ。最初から全部入れず、必要になった順に足すのが軽さを保つコツです。
実際に試した結果
ReactのSPAからAstroに移して一番効いたのは、奇抜なテクニックではなく**「JSを送らないのがデフォルト」という前提そのもの**でした。何もしなければ軽い。重くしたい時だけ、自分の意思で client:* を1語足す。この向きが、サイトを軽いまま保つうえで効きました。
Content Collectionsも、入れた当初は「型なんて大げさかな」と思っていました。でも記事が数百本・多言語に増えた今、frontmatterの打ち間違いを公開前にビルドが止めてくれるたびに、入れておいてよかったと実感します。
これからAstroを触るなら、順番はシンプルです。npm create astro@latest で雛形を作る → 記事をContent Collectionsで型付けする → 動かしたい部品にだけ client:visible を足す。この3歩で、SPAで悩んでいた「重さ」の大半は消えます。手を動かすための雛形やコマンド集が欲しい人は、教材一覧も覗いてみてください。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
制作会社がClaude Codeに触らせる前に決める権限チェックリスト
クライアントサイトを壊さずにAI編集を使うための、制作会社向け権限と確認の型です。
SaaSサポートのバグ報告をClaude Codeで再現手順に変える実務フロー
問い合わせ文をそのまま開発へ投げず、再現手順、証拠、次の一手に整えるサポート向け手順です。
Obsidianの古いメモをClaude Codeの指示書に変える10分ルーチン
Obsidianに溜めたメモが毎回ゴミになる人へ。事実・決定・未確認に仕分けして、Claude Codeがそのまま動ける指示書に変える朝の10分の型を紹介します。