- Published on
Astro 5 徹底解剖: Server Islands・Content Layer・View Transitions が描き直す『コンテンツサイトの標準』
- Authors

- Name
- Youngju Kim
- @fjvbn20031
「『静的』を定義し直そう。キャッシュできるものは全部キャッシュし、本当に人ごとに違う部分だけサーバーで遅く描く」 — Astro 5 Server Islands の設計を一行に圧縮するとこうなる。
プロローグ — 『ただの静的サイトジェネレーター』の時代は終わった
2022 年の Astro は Markdown ブログ用の静的サイトジェネレーターでした。2024 年 12 月 3 日に公開された Astro 5 は、同じ名前を冠した別物です。コンテンツ駆動サイトの事実上の標準 と呼んでも誇張ではありません。
この記事で答えたい問いは少なめに絞ります。
- Astro 5 で具体的に何が変わったのか
- Server Islands と Content Layer は何を解いているのか
- 同じ土俵で戦う Next.js の RSC / Server Actions、SvelteKit と何が違うのか
- コンテンツサイト、マーケサイト、ドキュメント、コマースのストアフロント — どこで Astro が勝ち、どこで負けるのか
- Astro 4 から 5 へどう移るのか
この記事は広告ではありません。Astro が得意なことと不得意なことを同じ重みで書きます。判断は最後の表でしてください。
1. アイランドアーキテクチャの復習 — Astro が最初から違っていた一点
軽くおさらいします。他のフレームワークが SPA → SSR → RSC と回り道する間、Astro は最初から別の道を進みました。
1.1 「ページはまず HTML」
既定の出力は HTML です。JavaScript は必要なコンポーネントに対してだけ明示的に付けます。この方針の名前が アイランドアーキテクチャ です。
ページ全体が単一の React ツリーなのではなく、静的な HTML の海に「インタラクティブな島」がぽつぽつ浮かぶ図です。島ごとに独立したバンドルとハイドレーションのタイミングを持ちます。結果として平均的なコンテンツページが配信する JS は競合より圧倒的に少なくなります。
1.2 クライアントディレクティブ
島を作る方法は、コンポーネントの横に一行のディレクティブを置くだけです。
---
import Counter from '../components/Counter.tsx'
import LikeButton from '../components/LikeButton.svelte'
import SearchBox from '../components/SearchBox.vue'
---
<h1>島の外側は静的な HTML</h1>
<Counter client:load />
<LikeButton client:visible />
<SearchBox client:idle />
client:load— ページロード時に即ハイドレートclient:idle— ブラウザがアイドルなときclient:visible— ビューポートに入ったらclient:media="(max-width: 600px)"— メディアクエリに合致したらclient:only="react"— サーバーでは描画せず、クライアントだけで描く
この一行の差が「このページの JS 4KB」と「このページの JS 300KB」を分けます。
1.3 フレームワーク非依存
同じページに React、Svelte、Vue、Solid、Preact、Lit を混在できます。実務でフル活用しているチームは多くないですが、レガシーウィジェットを段階的に取り込む という効きが大きいです。デザインシステムは Svelte、インタラクティブデモは React、といった分業も自然です。
2. Astro 5 の全体像 — ヘッドラインを 6 つに圧縮
Astro 5 を一画面に押し込むとこうなります。
| 領域 | Astro 4 | Astro 5 |
|---|---|---|
| コンテンツモデル | Content Collections (ファイルベース) | Content Layer API (任意のソース) |
| 動的ページ | ページ全体 SSR か静的 | Server Islands (静的の上の島) |
| バンドラ | Vite 5 | Vite 6、後の 6.x ラインで Vite 7 合流 |
| フォーム/ミューテーション | 手書きハンドラ | Astro Actions (型安全) |
| ルーティング | 静的 + 部分 SSR | ルート単位 prerender の精緻化 |
| i18n | 実験的 | 安定化 + フォールバック / ドメインルーティング |
一文で言うなら — 「静的優先を保ったまま、フルスタックの一部を受け入れた」 です。Next.js とは反対方向から同じ地点に近づいています。Next.js はクライアント React から出発して RSC で静的/サーバー境界を書き直し、Astro は静的 HTML から出発して Server Islands でサーバーを再導入しました。
3. Server Islands — 静的優先を崩さずパーソナライズを差し込む
Server Islands は Astro 5 の顔です。一文で:
「ページを CDN で静的にキャッシュし、人ごとに違う小さな破片だけサーバーで遅く描いて、クライアントが後追いで差し込む」
3.1 問題 — 「キャッシュできる 99% と できない 1%」
典型的なコンテンツページを思い浮かべてください。ヘッダー、本文、サイドバー、フッター — ピクセルのほぼ全てがどの訪問者にも同じです。違うのは右上の「ログイン中/未ログイン」「カート (3)」「あなたへのおすすめ」みたいな小さな破片だけ。
従来の選択肢:
- ページ全体 SSR — 1% のために 99% を毎回描き直す。キャッシュ無意味。
- 完全静的 + クライアントフェッチ — JS ロード → fetch → 描画 → レイアウトシフト。Core Web Vitals が悪化。
- ESI / Edge Side Includes — 理屈は良いが CDN 依存、デバッグ地獄。
Server Islands は 4 番目の道です。
3.2 使い方 — server:defer 一行
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro'
import Avatar from '../components/Avatar.astro'
import AvatarFallback from '../components/AvatarFallback.astro'
---
<Layout>
<h1>今日のおすすめ</h1>
<p>この本文はビルド時に作られ、CDN にキャッシュされます。</p>
<Avatar server:defer>
<AvatarFallback slot="fallback" />
</Avatar>
</Layout>
---
// src/components/Avatar.astro
import { getUserFromCookie } from '../lib/auth'
const user = await getUserFromCookie(Astro.request)
---
{user ? (
<a href="/me"><img src={user.avatar} alt={user.name} /></a>
) : (
<a href="/login">ログイン</a>
)}
server:defer が付いたコンポーネントは:
- ビルド時には 描画されません。 代わりに
slot="fallback"が場所を埋めます。 - ページは静的 HTML として CDN にキャッシュされます。0.5KB 前後のスクリプトとプレースホルダだけ。
- ブラウザは差し込む HTML を専用エンドポイント (
/_server-islands/Avatar) から取得します。 - 応答が届くとプレースホルダが本物の HTML に置き換わります。
肝は — 本文はキャッシュ、島だけパーソナライズ という分離です。CDN を 100% 活かしながら、本物のパーソナライズが回ります。
3.3 GA で加わった細部
ベータ期間中に以下が積まれて 5.0 GA に乗りました。
- 島ごとのヘッダー — Server Island 応答に
Cache-ControlやSet-Cookieを個別に付与可能 - 自動圧縮環境互換 — 圧縮を強制するホスティングでも動作
- props 自動暗号化 — Server Island に渡された props は自動で暗号化され、URL やプレースホルダから覗き見できない。権限情報を載せても安全
3.4 いつ使い、いつ使わないか
使うとき:
- 99/1 ページ — 本文は全員同じ、ヘッダーのユーザー状態やカートバッジ的な小さな差分のみ
- ウィジェット単位のパーソナライズ — 「最近見た記事」「あなたへのおすすめ 3 商品」
- ログイン有無による小さな UI 差分
使わないとき:
- ページ全体が人ごとに違う (ダッシュボード、受信箱、コンソール) — 素直に SSR か SPA
- LCP のフォールド上 — 遅延描画は体感品質を直撃
- 同期的に遅い外部 API に依存 — むしろクライアントフェッチ + スケルトンが良い
4. Content Layer API — 『コレクション』を『任意のソース』へ一般化
二つ目の大きな変更が Content Layer です。Astro 4 の Content Collections は ファイルシステム上の Markdown/MDX が一級市民でした。Astro 5 は一段抽象を上げて、ローダーがコレクション という形にしました。
4.1 モデル — Loader + Schema
// src/content.config.ts
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(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
})
export const collections = { blog }
loader— データの取得元schema— Zod で型と検証を一度に
glob 以外に公式の file ローダーがあり、その先は自由です。
4.2 カスタムローダー — HTTP、DB、CMS すべて
ここが面白い部分。任意のデータソースをコレクションにできます。
// src/loaders/notion.ts
import type { Loader, LoaderContext } from 'astro/loaders'
export function notionLoader(opts: { databaseId: string }): Loader {
return {
name: 'notion-loader',
async load({ store, parseData, meta, generateDigest }: LoaderContext) {
const lastSynced = meta.get('last-synced')
const pages = await fetchNotionPages(opts.databaseId, { since: lastSynced })
for (const p of pages) {
const data = await parseData({
id: p.id,
data: {
title: p.properties.Title.title[0].plain_text,
slug: p.properties.Slug.rich_text[0].plain_text,
body: p.body,
updatedAt: p.last_edited_time,
},
})
const digest = generateDigest(data)
store.set({ id: p.id, data, digest })
}
meta.set('last-synced', new Date().toISOString())
},
}
}
// src/content.config.ts
import { defineCollection, z } from 'astro:content'
import { notionLoader } from './loaders/notion'
const articles = defineCollection({
loader: notionLoader({ databaseId: process.env.NOTION_DB_ID! }),
schema: z.object({
title: z.string(),
slug: z.string(),
body: z.string(),
updatedAt: z.coerce.date(),
}),
})
export const collections = { articles }
ページでの消費はファイルベースと変わりません。
---
import { getCollection } from 'astro:content'
const articles = await getCollection('articles')
---
<ul>
{articles.map(a => <li><a href={`/articles/${a.data.slug}`}>{a.data.title}</a></li>)}
</ul>
データが Markdown だろうが Notion だろうが Sanity だろうが、自社の PostgreSQL だろうが — ページ側は知りません。これが Content Layer の本質です。
4.3 ビルドキャッシュとインクリメンタル
Content Layer は SQLite 製キャッシュを持ち、変更分のエントリだけ再処理します。1 万件規模のコンテンツサイトの 2 回目以降のビルドが圧倒的に速い理由です。ローダーは meta.get/set で自分の最終同期時刻を覚えられ、generateDigest でコンテンツハッシュを作って差分検出します。
4.4 外部ローダーのエコシステム
5.0 リリース後、公式・サードパーティのローダーが短期間にずらりと並びました — Storyblok、Hygraph、WordPress、Ghost、Strapi、Sanity など。「Astro = どんなヘッドレス CMS にも合う普遍的フロントエンド」というポジションが、製品レベルで実際に効き始めました。
4.5 Live Loaders — ランタイムコンテンツ
GA 後の動きで重要なのが Live Content Loaders です。ビルド時ではなくリクエスト時にデータを引いてくるローダーで、「静的 80% + ライブ 20%」のシナリオ (価格、在庫、為替) にフィットします。getCollection の API を維持したまま、コレクション単位でライブに切り替えられるのが秀逸です。
5. Astro Actions — フォームとミューテーションを型安全に
三つ目の大きな変更が Astro Actions。要は「Zod 入力スキーマ付きの型安全なサーバー関数」です。
5.1 定義
// src/actions/index.ts
import { defineAction } from 'astro:actions'
import { z } from 'astro:schema'
export const server = {
subscribe: defineAction({
accept: 'form',
input: z.object({
email: z.string().email(),
utm: z.string().optional(),
}),
handler: async (input, ctx) => {
const ip = ctx.request.headers.get('x-forwarded-for')
await saveSubscriber({ email: input.email, utm: input.utm, ip })
return { ok: true }
},
}),
}
5.2 呼び出し — そのまま HTML フォーム
---
import { actions } from 'astro:actions'
const result = Astro.getActionResult(actions.subscribe)
---
<form method="POST" action={actions.subscribe}>
<input type="email" name="email" required />
<input type="hidden" name="utm" value="blog-banner" />
<button type="submit">購読する</button>
{result?.data?.ok && <p>ありがとうございます!</p>}
{result?.error && <p class="err">エラー — もう一度お試しください。</p>}
</form>
JS を切っていてもフォームは動きます。JS が有効なら段階的に強化された UX になります。クライアント JS から actions.subscribe(input) で直接呼び出すこともできます。
5.3 何が嬉しいか
- 入力検証と型の単一ソース — Zod スキーマ一つでランタイム検証、TS 型、補完が同時に揃う
- プログレッシブエンハンスメント親和 — 素の HTML フォームで動く
- Server Islands と相性が良い — アクション後に島を無効化/再レンダーできる
Next.js の Server Actions と概念的には兄弟ですが、Astro は「静的ページにフォーム一つ」のような軽量シナリオによりきれいにはまります。
6. Vite 6 / Vite 7 統合 — ビルドの内部
Astro 5 は Vite 6 上で出荷され、その後の 5.x/6.x ラインで Vite 7 への合流作業が進みました。開発者目線で変わる点:
- Environment API — クライアント/サーバー/エッジといった複数環境を同じビルドグラフで統一的に扱える。Server Islands に必須のサーバーコード分離が綺麗に
- コールドスタートが軽い — 大規模サイトの初回
astro devの体感が軽くなる - CSS HMR の精度向上 — CSS を 1 文字変えてもフルリロードしない
- Rollup 4.x / 5.x ラインを採用
ここで効くのは — Astro チームは Vite コアとほぼ同じテンポで動いている ということ。Vite の進化が Astro に素早く流れ込みます。
7. View Transitions と prefetching — SPA のように滑らかに、MPA のように軽く
コンテンツサイトの弱点の一つは、ページ遷移ごとに白い画面がチラつく点でした。View Transitions API とコアの prefetching がこれを解消します。
7.1 View Transitions
---
import { ClientRouter } from 'astro:transitions'
---
<head>
<ClientRouter />
</head>
レイアウトにこの一行を入れると、サイト内ナビゲーションが MPA のまま ブラウザネイティブの View Transitions が画面切替に重なります。ヘッダーは固定、本文だけクロスフェード、サムネイルが次ページのヒーロー画像に自然にモーフィング — そんな見た目になります。
要素に名前を付ければページ間で同じ要素を追跡できます。
<img src={post.cover} transition:name={`cover-${post.slug}`} />
7.2 prefetching — コアに吸収
prefetch は Astro 3.5 でコアに取り込まれ、5 では既定 ON のヒューリスティクスがよりまともになりました。
// astro.config.mjs
export default defineConfig({
prefetch: {
prefetchAll: true,
defaultStrategy: 'viewport',
},
})
戦略は tap、hover、viewport、load。リンク単位で data-astro-prefetch="hover" のように上書きできます。結果は — ユーザーがクリックする前に次のページが既にそこにある。
7.3 i18n の改善
i18n は 4.x で安定化に入り、5.x で磨かれました。
- 既定ロケールとロケール一覧を一度に宣言
- URL 戦略 — prefix-other-locales (既定ロケールだけ prefix 無し)、prefix-always、ドメインベース
getRelativeLocaleUrl、getAbsoluteLocaleUrlといったヘルパー- ページ不在時のフォールバック — 日本語ページが無ければ英語へ
コンテンツサイトはほぼ確実に多言語に行き着くので、ここが一級扱いになった意味は大きいです。
8. Astro vs Next.js vs SvelteKit — 判断マトリクス
ここからが本論です。どこで何を使うか。
8.1 一行の哲学
- Astro 5 — 静的優先。必要な部分だけサーバー/クライアントの島に。
- Next.js (App Router + RSC) — サーバー優先。静的はキャッシュの一形態。
- SvelteKit — ルーター優先。コンパイラでランタイムを削る統合フルスタック。
8.2 項目別比較
| 項目 | Astro 5 | Next.js (RSC) | SvelteKit |
|---|---|---|---|
| 既定 JS ペイロード | 最少 (既定でゼロ) | 中〜高 | 少 |
| フルスタックの深さ | 浅〜中 | 深 | 中 |
| コンテンツモデル | Content Layer (一級) | App Router + MDX (アドオン) | 自前構成 |
| フォーム/ミューテーション | Actions | Server Actions | form actions |
| 動的パーソナライズ | Server Islands | RSC + Suspense | streaming + load |
| 多言語 | コア i18n | ライブラリ | ライブラリ |
| ホスティング | 静的 + 任意のアダプタ | Vercel 最適 | アダプタ多様 |
| 学習曲線 | 低 | 中〜高 | 低〜中 |
| コンポーネント互換 | React/Svelte/Vue/Solid 等 | React 専用 | Svelte 専用 |
8.3 シナリオ別の推奨
- ブログ、ドキュメント、マーケサイト、メディア: Astro 5。Server Islands でヘッダーのパーソナライズも自然に処理。Core Web Vitals でほぼ負ける場所がない
- e コマースストアフロント (ヘッドレス CMS + 決済): Astro 5 優勢。商品ページは静的、カート/レコメンドだけ Server Islands。ただし決済の深い部分は他のフレームワークが向くことも
- フルスタック SaaS、ダッシュボード: Next.js。認証、認可、リアルタイム、複雑なフォーム、深いルートツリーは RSC が自然
- ソーシャル/リアルタイムアプリ: Next.js か SvelteKit。Astro の領域ではない
- アプリっぽい SPA + 少しのサーバー: SvelteKit。コード量が最少になりやすい
- 多言語コンテンツハブ、超大規模サイト: Astro 5。Content Layer + i18n はこのシナリオのために作られたよう
一行で — コンテンツ最優先なら Astro、アプリケーション最優先なら Next.js / SvelteKit。
9. 実適用例 — コンテンツ重視サイトでの形
仮想のメディア企業を仮定します。月 1 億 PV のニュースサイト。Astro 5 でどう組むかを見ます。
9.1 ページ単位の判断
/ホーム — 静的ビルド、Server Island で「ログイン済みヘッダー」と「あなたへのおすすめ」だけ/articles/[slug]— 静的ビルド (Content Layer が CMS から取得)、Server Island で「ヘッダー + コメント数 + いいね状態」/search— SSR (検索クエリがキャッシュキーを壊す)/me、/me/bookmarks— SSR またはクライアントフェッチ/auth/*— SSR + Actions
CDN ヒット率は 95% を超えます。99/1 ページに 99/1 のコスト構造が付いてきます。
9.2 データフロー
- CMS (Sanity、Storyblok など) — Content Layer ローダーでビルド時同期。変更分のみリビルド (インクリメンタル)
- ユーザー/セッション — Server Island が Cookie を直接読む。本文キャッシュとは独立
- レコメンドアルゴリズム — Server Island がリクエスト時に推薦 API を叩く
- コメント — Live Loader またはクライアントコンポーネント
9.3 ビルド時間
- 1 万記事のコールドビルド: 数分
- 1 万記事 + 100 件変更のインクリメンタル: 数十秒
- キャッシュは SQLite — CI キャッシュにマウントしてそのまま継承
同規模の Next.js 静的ビルドより速い可能性が高いです。ただし Next.js の ISR (Incremental Static Regeneration) + オンデマンド再検証モデルも別の魅力があり、絶対的優位ではありません。
10. Astro 4 から 5 への移行 — 率直な話
最も現実的な問い — 4 から 5 への移行はどれだけ痛いか。
10.1 良い知らせ
- ページ/コンポーネント構文はそのまま
- ルーティングもそのまま
- インテグレーションの大半もそのまま
10.2 手間がかかる部分
-
Content Collections から Content Layer へ
src/content/config.tsにloader: glob({...})を追加getEntry/getCollectionの一部シグネチャ変更- ビルドキャッシュが新設されるので
.astro/をはじめとするキャッシュディレクトリを CI に載せる
-
Astro.globの削除- Content Layer に吸収。Markdown 収集を手書きしていたコードは
getCollectionへ
- Content Layer に吸収。Markdown 収集を手書きしていたコードは
-
画像処理の方針変化
astro:assetsのオプション整理。外部画像ドメインのホワイトリストが厳格化
-
アダプタの更新
- Vercel、Netlify、Cloudflare、Node アダプタは全て 5.x が必要
-
Vite 6 環境の互換
- 独自 Vite プラグインがある場合は 5/6 互換性を確認
10.3 移行手順
- Astro 4 を最新パッチに上げ、deprecation 警告をゼロに
pnpm dlx @astrojs/upgradeで一括メジャージャンプastro checkを通す- 1 ページに Server Island を試験導入
- 1 つのコレクションを Content Layer へ
- 段階的に拡張
大規模サイトでも通常数日で完了します。Next.js の Pages から App Router への移行よりはるかに軽いです。
11. 率直な限界 — Astro が依然強い場所、負ける場所
宣伝抜きで書きます。
11.1 依然強い場所
- コンテンツサイト/ドキュメント/マーケページで Core Web Vitals はほぼ無敗
- ヘッドレス CMS との統合がクラス最良 (Content Layer 効果)
- 多言語サイトの一級サポート
- ビルド時間 (インクリメンタルキャッシュ)
- 複数フレームワークのウィジェット共存
11.2 弱い場所
- 深いフルスタック — 認証、認可、ワークフロー、キュー、バックグラウンドジョブがページコードと強結合するシナリオ。Astro は意図的に浅い
- アプリ型 SPA — 全ページが動的で状態共有が深いと、静的優先モデルの優位が消える
- エコシステムの厚み — Next.js と比べてライブラリ/事例/求人市場が小さい
- ホスティングロックインが無いことの影 — Vercel ほど滑らかに統合されたホスティングは存在しない。アダプタの品質がホスティングによって差がある
- 開発ツール — 強力だが、Next.js の RSC デバッグ/キャッシュ可視化ほど成熟していない
- Server Islands の罠 — 大量に敷くとページ上半分がプレースホルダだらけになり体感品質が落ちる。「本文は静的、小さな島だけ動的」の線を守る必要あり
11.3 結論を一行で
「あなたのサイトが本ならば Astro、道具ならば Next.js。その中間なら SvelteKit も併せて評価しなさい」
エピローグ — チェックリストと次回予告
12.1 Astro 5 導入時のチェックリスト
- ページのキャッシュ可能比率は 80% 以上か (Server Islands の効きどころ)
- コンテンツソースは 1 つ以上で、一部は外部 CMS / DB か (Content Layer 適合)
- 多言語は必要か (コア i18n を活用)
- ビルド時間が伸びると困るか (インクリメンタルキャッシュを活用)
- コンポーネントライブラリは React / Svelte / Vue のいずれかに固まっているか (互換性確認)
- ホスティングアダプタ (Vercel / Netlify / Cloudflare / Node) は安定版か
- フォームは Actions、部分パーソナライズは Server Islands、本物の動的ページのみ SSR — というルールに合意したか
12.2 アンチパターン
- ページの 70% を Server Island にする (それなら素直に SSR が良い)
- LCP のフォールド上に Server Island を置く (遅延がユーザーに直撃)
- Content Layer を迂回してページコンポーネントから直接
fetchを撒く (キャッシュも型も失う) - Actions を経由せず API ルートだけ手書きする (プログレッシブエンハンスメントできない)
- 全コンポーネントを
client:loadでハイドレートする (Astro を使う意味が消える) - View Transitions を入れつつページごとに別レイアウトを使う (要素追跡が壊れる)
12.3 次回予告
- 「Astro Server Islands で e コマースストアフロントを作る — 本文キャッシュ 95% を守る方法」
- 「Content Layer カスタムローダーパターン — Notion、Postgres、S3 からコレクションを引く」
- 「Astro Actions と段階的強化フォーム — JS 0KB のフォームが可能な理由」
- 「Astro 6 に向けたロードマップ — Live Loaders 安定化とその先」
このシリーズの一行の約束 — コンテンツサイトの標準は再び動き、その名は Astro 5 である。
参考 / References
- Astro 5.0 Release Blog (2024-12-03)
- Astro 5.0 Beta Release
- Server Islands — Astro Docs
- Server Islands Announcement
- Islands Architecture — Astro Docs
- Content Layer — Deep Dive
- Content Collections — Astro Docs
- Content Loader API Reference
- Astro Actions — Docs
- Actions API Reference
- View Transitions — Docs
- View Transitions Router Reference
- Astro 2024 Year in Review
- Live Content Loaders Roadmap Issue #1151
- Storyblok Loader for Content Layer
- Hygraph Astro Content Loader
- Content Layer for Headless WordPress
- Comparing JS Frameworks for Content Sites — DatoCMS
- Astro 4.0 Release
- Astro 3.5 i18n Routing