Skip to content

✍️ 필사 모드: Astro 5 徹底解剖: Server Islands・Content Layer・View Transitions が描き直す『コンテンツサイトの標準』

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

「『静的』を定義し直そう。キャッシュできるものは全部キャッシュし、本当に人ごとに違う部分だけサーバーで遅く描く」 — 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 4Astro 5
コンテンツモデルContent Collections (ファイルベース)Content Layer API (任意のソース)
動的ページページ全体 SSR か静的Server Islands (静的の上の島)
バンドラVite 5Vite 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)」「あなたへのおすすめ」みたいな小さな破片だけ。

従来の選択肢:

  1. ページ全体 SSR — 1% のために 99% を毎回描き直す。キャッシュ無意味。
  2. 完全静的 + クライアントフェッチ — JS ロード → fetch → 描画 → レイアウトシフト。Core Web Vitals が悪化。
  3. 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 が付いたコンポーネントは:

  1. ビルド時には 描画されません。 代わりに slot="fallback" が場所を埋めます。
  2. ページは静的 HTML として CDN にキャッシュされます。0.5KB 前後のスクリプトとプレースホルダだけ。
  3. ブラウザは差し込む HTML を専用エンドポイント (/_server-islands/Avatar) から取得します。
  4. 応答が届くとプレースホルダが本物の HTML に置き換わります。

肝は — 本文はキャッシュ、島だけパーソナライズ という分離です。CDN を 100% 活かしながら、本物のパーソナライズが回ります。

3.3 GA で加わった細部

ベータ期間中に以下が積まれて 5.0 GA に乗りました。

  • 島ごとのヘッダー — Server Island 応答に Cache-ControlSet-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',
  },
})

戦略は taphoverviewportload。リンク単位で data-astro-prefetch="hover" のように上書きできます。結果は — ユーザーがクリックする前に次のページが既にそこにある

7.3 i18n の改善

i18n は 4.x で安定化に入り、5.x で磨かれました。

  • 既定ロケールとロケール一覧を一度に宣言
  • URL 戦略 — prefix-other-locales (既定ロケールだけ prefix 無し)、prefix-always、ドメインベース
  • getRelativeLocaleUrlgetAbsoluteLocaleUrl といったヘルパー
  • ページ不在時のフォールバック — 日本語ページが無ければ英語へ

コンテンツサイトはほぼ確実に多言語に行き着くので、ここが一級扱いになった意味は大きいです。


8. Astro vs Next.js vs SvelteKit — 判断マトリクス

ここからが本論です。どこで何を使うか。

8.1 一行の哲学

  • Astro 5 — 静的優先。必要な部分だけサーバー/クライアントの島に。
  • Next.js (App Router + RSC) — サーバー優先。静的はキャッシュの一形態。
  • SvelteKit — ルーター優先。コンパイラでランタイムを削る統合フルスタック。

8.2 項目別比較

項目Astro 5Next.js (RSC)SvelteKit
既定 JS ペイロード最少 (既定でゼロ)中〜高
フルスタックの深さ浅〜中
コンテンツモデルContent Layer (一級)App Router + MDX (アドオン)自前構成
フォーム/ミューテーションActionsServer Actionsform actions
動的パーソナライズServer IslandsRSC + Suspensestreaming + 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.jsSvelteKit。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 手間がかかる部分

  1. Content Collections から Content Layer へ

    • src/content/config.tsloader: glob({...}) を追加
    • getEntry / getCollection の一部シグネチャ変更
    • ビルドキャッシュが新設されるので .astro/ をはじめとするキャッシュディレクトリを CI に載せる
  2. Astro.glob の削除

    • Content Layer に吸収。Markdown 収集を手書きしていたコードは getCollection
  3. 画像処理の方針変化

    • astro:assets のオプション整理。外部画像ドメインのホワイトリストが厳格化
  4. アダプタの更新

    • Vercel、Netlify、Cloudflare、Node アダプタは全て 5.x が必要
  5. Vite 6 環境の互換

    • 独自 Vite プラグインがある場合は 5/6 互換性を確認

10.3 移行手順

  1. Astro 4 を最新パッチに上げ、deprecation 警告をゼロに
  2. pnpm dlx @astrojs/upgrade で一括メジャージャンプ
  3. astro check を通す
  4. 1 ページに Server Island を試験導入
  5. 1 つのコレクションを Content Layer へ
  6. 段階的に拡張

大規模サイトでも通常数日で完了します。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

현재 단락 (1/300)

2022 年の Astro は Markdown ブログ用の静的サイトジェネレーターでした。2024 年 12 月 3 日に公開された Astro 5 は、同じ名前を冠した別物です。**コンテンツ駆動...

작성 글자: 0원문 글자: 13,977작성 단락: 0/300