Astro

Astro 5の新機能と移行手順|Content Layer・Server Islands・hybrid廃止を5.18.2と7.4.8で検証

Astro 5の新機能と移行手順|Content Layer・Server Islands・hybrid廃止を5.18.2と7.4.8で検証

Astro 5は、2024年12月3日に5.0.0が公開されたAstroのメジャーバージョンです。コンテンツ管理がContent Layerに置き換わり、ページの一部だけをサーバーで描画するServer Islandsが安定版になり、output: 'hybrid'が廃止されました。5系は2026年5月26日の5.18.2が最後のリリースで、2026年11月10日時点の現行版は7.4.8です。

この記事では、5系で何が変わったのか、v4から移行するときにコードのどこを直すのか、5系のまま残したコードが7系でどう壊れるのかを説明します。根拠には、astro 5.18.2(@astrojs/node 9.5.5・Vite 6.4.6)と7.4.8(@astrojs/node 11.1.11)を、Node.js v26.5.0で2026年11月10日にビルドした出力を使います。Astroそのものの特徴と現行版の全体像はAstroフレームワークの特徴と使い方の解説で扱っています。

まとめ:Astro 5の要点と今から使う場合の判断

  • 5.0の柱は、Content Layer(ローダーで任意のデータ源をコレクションにする)、Server Islands(server:defer)、hybridとstaticの統合、astro:env、Vite 6の5つです。
  • 5系のマイナー版では、5.7でSessionsとSVGコンポーネント、5.10でレスポンシブ画像が安定版になりました。CSP(5.9)とlive content collections(5.10)は5系では実験機能のままです。
  • 移行で最も壊れやすいのはslugからidへの変更です。サブディレクトリに置いたMarkdownはidにスラッシュが入り、[id].astroのままだとビルドが失敗します。
  • 5系はもうセキュリティ修正の対象外です。延長メンテナンスの対象は現行の1つ前のメジャー版だけで、2026年11月時点では6系です。
  • 5系に残すプロジェクトでも、旧形式のコレクション(src/content/config.ts)とAstro.glob()は5系のうちに消してください。7.4.8ではこの2つがビルドエラーになり、5系の書き方に揃えたコンテンツ定義はそのまま通ります。

Astro 5.0の主な変更点とリリース時期

項目 4系まで 5.0以降
コンテンツ管理 Content Collections(src/content/固定) Content Layer(loaderで取得元を指定)
部分的なサーバー描画 実験機能(4.12) Server Islands安定版
出力モード static / hybrid / server static / server
型付き環境変数 実験機能 astro:env安定版
Vite 5 6
画像サービス Sharp / Squoosh 標準はSharp(組み込みSquoosh削除)

Astro 5.0の公式ブログは、Content Layerによってコンテンツの多いサイトでMarkdownページのビルドが最大5倍、MDXが最大2倍速くなり、メモリ使用量が25〜50%減ると発表しています。

5.1〜5.18で安定版になった機能と追加された実験機能

5系はマイナー版のたびに実験機能を足し、数版後に安定版へ移す進め方でした。5系に残るプロジェクトで「この機能はフラグなしで使えるか」を判断できるよう、公式ブログの各リリース記事から主なものを並べます。

版 フラグなしで使える追加・安定化 実験機能として追加
5.1 getActionPath() Sessions
5.2 Tailwind CSS 4対応・TOML frontmatter astro:config
5.7 Sessions・SVGコンポーネント・astro:config Fonts API
5.9 ローダーのrenderMarkdown() CSP
5.10 レスポンシブ画像 live content collections
5.12 file()・glob()のTOML対応 rawEnvValues
5.16 previewの対話ショートカット SVGO最適化
5.18 actionBodySizeLimit –

出典はAstro 5.7のリリース記事(Sessions・SVGの安定化)、Astro 5.10のリリース記事(レスポンシブ画像の安定化とlive content collections)などの公式ブログです。5.18.0で入ったsecurity.actionBodySizeLimitは、Actionsのリクエスト本文の上限をバイト単位で指定する設定で、既定値は1048576バイト(1MB)です。5.16の追加機能はAstro 5.16の変更点の解説に、5.10で安定したレスポンシブ画像の使い方はAstroの画像最適化とpreload・prefetchの解説にまとめています。

6・7系のリリース日と5系の最終版

バージョン 公開日 位置づけ
5.0.0 2024-12-03 5系の最初のリリース
5.18.0 2026-02-25 5系最後のマイナー版
6.0.0 2026-03-10 Node.js 22.12.0以上・Vite 7・Zod 4
5.18.2 2026-05-26 5系の最終リリース
7.0.0 2026-06-22 Vite 8
7.4.8 2026-11-09 2026年11月10日時点のlatest

公開日はnpmレジストリの登録日時です。5.18.2は6.0.0より後に出た5系向けのリリースで、これ以降5系の版は出ていません。

Content Layer APIの書き方とidの付き方

content.config.tsとglob()・file()ローダーの定義

5系では設定ファイルがsrc/content.config.tsに移り、コレクションごとにloaderで取得元を指定します。組み込みローダーは、ファイル群を読むglob()と、1つのJSONやYAMLを読むfile()です。Markdownと外部データを同じgetCollection()で扱えます。

// src/content.config.ts
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod';
import { glob, file } from 'astro/loaders';

const blog = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/data/blog' }),
  schema: z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
  }),
});

const authors = defineCollection({
  loader: file('src/data/authors.json'),
  schema: z.object({ name: z.string() }),
});

export const collections = { blog, authors };

zはastro:contentからも読み込めますが、6.0でこの読み込み方は非推奨になりました。astro/zodから読み込む上の書き方は、5.18.2と7.4.8のどちらでも警告なしでビルドが通ったので、5系のうちからこちらに揃えておくと7系への移行で直す箇所が1つ減ります。file()に渡すJSON配列は各要素にidを持たせ、[{ "id": "alice", "name": "Alice" }]ならgetEntry('authors', 'alice')で1件を取り出せます。

インライン関数・ローダーオブジェクトによる外部データの取得

Content Layerの利点は、CMSやAPIのデータもloaderに書けば同じgetCollection()で扱える点です。書き方は2つあります。配列を返す非同期関数をそのまま渡すインラインローダーと、nameとload()を持つオブジェクトを渡すローダーオブジェクトです(5系のContent Loader APIリファレンス)。

// インラインローダー:idを持つ要素の配列を返す
const releases = defineCollection({
  loader: async () => {
    const res = await fetch('https://example.com/api/releases.json');
    return await res.json(); // [{ id: '5.0.0', date: '2024-12-03' }, ...]
  },
  schema: z.object({ date: z.coerce.date() }),
});

// ローダーオブジェクト:storeへ自分で書き込む
const tags = defineCollection({
  loader: {
    name: 'tag-loader',
    load: async ({ store, parseData, logger }) => {
      store.clear();
      for (const t of ['astro', 'vite']) {
        const data = await parseData({ id: t, data: { label: t.toUpperCase() } });
        store.set({ id: t, data });
      }
      logger.info('loaded 2 tags');
    },
  },
  schema: z.object({ label: z.string() }),
});

export const collections = { blog, authors, releases, tags };

インラインローダーの戻り値を固定の配列にし、2つのコレクションを5.18.2と7.4.8でビルドすると、どちらもビルドログに[tag-loader] loaded 2 tagsが出て、ページには5.0.0=2024-12-03とastro:ASTROが描画されました。parseData()を通すとスキーマの検証と型変換がかかるので、store.set()に生のデータを直接渡さないでください。

使い分けの基準は、取得に手間がかかるかどうかです。APIを1回呼ぶだけならインラインで足ります。差分だけを取り込む、ダイジェストで変更を検知する、Markdownを描画する(5.9で追加されたrenderMarkdown())といった処理が要るなら、ローダーオブジェクトにします。どちらもビルド時にデータストアへ読み込む方式で、開発中は変更に応じて更新される場合があります。本番のリクエストごとに取り直すには5.10で実験機能として入ったlive content collections(オンデマンド描画のアダプターとexperimental.liveContentCollectionsが必要)を使います。ヘッドレスCMS側の構成はSanity CMSの使い方の解説が参考になります。

slugからidへの置き換えとURLが変わる条件

移行で最も影響が大きいのは、エントリーの識別子がpost.slugからpost.idに変わる点です。描画もpost.render()から、astro:contentから読み込むrender(post)に変わります。

---
// src/pages/blog/[...id].astro
import { getCollection, render } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({ params: { id: post.id }, props: { post } }));
}

const { post } = Astro.props;
const { Content } = await render(post);
---
<h1>{post.data.title}</h1>
<Content />

glob()が付けるidを、3種類のファイルを置いて5.18.2で確かめた結果が次の表です。7.4.8でも同じidになりました。

ファイル frontmatterのslug 生成されたid
hello-world.md 指定なし hello-world
custom.md slug: my-custom-url my-custom-url
2024/Hello World.md 指定なし 2024/hello-world

frontmatterのslugはidとして引き継がれるので、独自スラッグを付けていた記事のURLは保てます。注意すべきはサブディレクトリです。idにスラッシュが入るため、ルートファイルが[id].astroだとビルドがMissing parameter: idで終了コード1になりました(5.18.2・7.4.8とも)。ファイル名を[...id].astro(レストパラメーター)に変えると、/blog/2024/hello-world/が生成されます。

もう1つの落とし穴は、旧形式のコレクションを残したまま5系に上げた場合です。loaderの無いコレクションは後方互換のglob()で読み込まれ、5.18.2ではpost.idがhello-world.mdと拡張子付きで返りました(post.slugは従来どおりhello-world)。idをURLに使うコードへ書き換える途中でこの状態になると、URLに.mdが入ります。v5の移行ガイドは、この互換モードではgetCollection()の並び順が決まらないとも書いているため、一覧ページは日付などで明示的に並べ替えてください。

Server Islandsとは:server:deferで一部だけをサーバー描画する仕組み

server:deferとfallbackスロットの出力

Server Islandsは、静的に生成したページの中で、server:deferを付けたコンポーネントだけを、ページ表示後に別のリクエストでサーバー描画して差し込む仕組みです。ログイン中のユーザー名やカートの件数のように、ページ全体はCDNにキャッシュしたいが一部だけ利用者ごとに変わる箇所に使います。ブラウザでJavaScriptを動かすクライアントアイランドとの違いはアイランドアーキテクチャの仕組みの解説を参照してください。

---
import UserBadge from '../components/UserBadge.astro';
---
<UserBadge server:defer userId="u-123">
  <p slot="fallback">loading...</p>
</UserBadge>

島として描画するUserBadge.astroは、受け取ったpropsと描画時刻を返すだけの最小実装です。

---
// src/components/UserBadge.astro
const { userId } = Astro.props;
---
<p>user={userId} at {new Date().toISOString()}</p>

このページを5.18.2でビルドすると、静的HTMLにはfallbackスロットの中身と、島を取得するリクエストだけが出力されます(クエリの値は省略)。

<link rel="preload" as="fetch" href="/_server-islands/UserBadge?e=...&p=...&s=" crossorigin="anonymous">
<!--[if astro]>server-island-start<![endif]--><p>loading...</p>
<script type="module" data-astro-rerun data-island-id="...">
let response = await fetch('/_server-islands/UserBadge?e=...&p=...&s=', { headers });

生成されたHTMLを検索すると、propsに渡したu-123は0件でした(7.4.8でも0件)。値はpパラメーターに暗号化されて載っています。Node.jsアダプターのサーバーでこのURLを叩くとcontent-type: text/htmlの200で<p>user=u-123 at ...</p>が返り、pの先頭5文字を書き換えたURLは400になりました。

差し替えに失敗したときの挙動も出力から読めます。差し込み処理のreplaceServerIsland()は、応答が200でcontent-typeがtext/htmlのときだけ置き換え、それ以外は何もしません。島のサーバーが落ちてもページは壊れず、fallbackの中身が残り続けます。fallbackには「読み込み中」だけでなく、取得できなかったときにも意味が通る内容を置いてください。

アダプター必須の条件とASTRO_KEY

Server Islandsは実行時にサーバーが要るため、アダプターが無いとビルドが止まります。5.18.2で出たエラーは次のとおりです。

[NoAdapterInstalledServerIslands] [astro:server-islands] Cannot use server islands without an adapter. Please install and configure the appropriate server adapter for your final deployment.

propsを暗号化する鍵はビルドごとにランダムに作られます。ローリングデプロイや複数リージョンへの配信のように、ビルドをまたいで同じ鍵を使う必要がある場合はastro create-keyで鍵を生成し、ビルド環境の環境変数ASTRO_KEYに設定します。Server Islandsの公式ドキュメントに書かれている制約は次の3点です。

  • URLが2048バイトを超えるとPOSTに切り替わり、ブラウザにキャッシュされない
  • 関数はシリアライズできないため、propsに渡せない
  • 島の中のAstro.urlは内部のルートを指すので、元のページのURLはRefererヘッダーから読む

GETで取得される島の応答はCache-Controlヘッダーでキャッシュを制御できます。propsを小さく保つことが、キャッシュを効かせる前提です。

output: ‘hybrid’の廃止とprerenderによる切り替え

5系では出力モードがstaticとserverの2つになりました。4系のhybridを指定したままビルドすると、設定の検証で止まります(5.18.2・7.4.8とも同じ文面)。

! The output: "hybrid" option has been removed. Use output: "static" (the default) instead, which now behaves the same way.

既定のstaticのままアダプターを入れ、動的にしたいページやAPIだけにexport const prerender = falseを書くのが、旧hybridと同じ構成です。

// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'static',
  adapter: node({ mode: 'standalone' }),
});

// src/pages/api/now.ts
export const prerender = false;

export function GET() {
  return new Response(JSON.stringify({ now: Date.now() }), {
    headers: { 'content-type': 'application/json' },
  });
}

この構成でビルドすると、ログにはoutput: "static"とmode: "server"が並び、トップページとブログはdist/client/に静的HTMLとして書き出され、/api/nowだけがリクエストごとに値を返しました。prerenderに書けるのはtrueかfalseの固定値だけで、変数や式で切り替える書き方は5.0で削除されています。静的生成とサーバー描画の使い分けそのものはSSR・CSR・SSG・ISR・ISGの違いの解説で比較しています。

astro:env・compiledContentなどその他の破壊的変更

astro:envは、必要な環境変数を設定ファイルのスキーマで宣言し、astro:env/clientとastro:env/serverから型付きで読み込む機能で、5.0で安定版になりました。ほかに、v5の移行ガイドにある変更のうち、ビルドは通るのに動きが変わるものを中心に並べます。

変更 影響するコード 直し方
compiledContent()が非同期に Markdownのimport awaitを付ける
paginate()のpage.urlにbaseを含む page.url.prev等へのbase前置 前置を削除
paramsを自動でデコードしない getStaticPathsのparams 必要なら手でデコード
リダイレクト等の優先順位を統一 injected routes・redirects 重複ルートを確認
script要素を書いた位置に出力 head巻き上げ前提のscript 出力位置を確認
checkOriginの既定がtrue オンデマンド描画ページへのPOST Originヘッダーを確認
ViewTransitionsをClientRouterに改名 astro:transitionsのimport コンポーネント名を変更

このうち2つは5.18.2で挙動を確かめました。base: '/docs'を設定したサイトではpage.url.nextが/docs/list/2と返り、4系の書き方のままBASE_URLを前置すると/docs/docs/list/2と二重になりました。checkOriginは、prerender = falseのエンドポイントへ別オリジンのOriginヘッダーを付けてapplication/x-www-form-urlencodedでPOSTするとCross-site POST form submissions are forbiddenの403を返し、application/jsonのPOSTは検証の対象外で200でした。別ドメインのフォームから受ける構成は、5系に上げた時点で止まります。

型と依存関係の変更もあります。src/env.d.tsの代わりにtsconfig.jsonのincludeへ.astro/types.d.tsを追加します(独自の型宣言を含むsrc/env.d.tsは残す)。Squoosh画像サービスとLitインテグレーションは削除され、astro:contentはクライアント側のスクリプトから使えなくなりました。Astro.glob()は非推奨で、5.18.2ではAstro.glob is deprecated and will be removed in a future major version of Astro.という警告が出るだけでビルドは通ります。

Astro 4から5へのアップグレード手順

  1. 現行のv4でビルドし、生成されたページの一覧を保存する
  2. 作業用のブランチを切り、npm install astro@5でAstro本体を5系に上げ、使っている公式インテグレーションも5系対応版に揃える
  3. 設定からoutput: 'hybrid'を消し、動的にするページへexport const prerender = falseを書く
  4. コンテンツ定義をsrc/content.config.tsへ移し、loader、id、render()に書き換える
  5. tsconfig.json、ClientRouter、Astro.glob()、compiledContent()、paginate()の前置を直し、警告が消えるまでビルドする
  6. 保存しておいたページ一覧と比べ、URLが変わっていないか確認する

公式のnpx @astrojs/upgradeは、5系で止める移行には使いません。@astrojs/upgradeは引数をbetaのようなnpmのdist-tagとしてだけ解決し、該当しない値や引数なしの場合はlatestに更新します。いま実行するとv4から7系まで一気に上がります。5.18.2と組み合わせてビルドを確認したNode.jsアダプターは@astrojs/node 9.5.5です(7.4.8は11.1.11)。

手順1と6は、生成されたHTMLファイルの一覧を比べる次のコマンドで確認できます。prerender = falseのページはHTMLが書き出されないため、比較には含まれません。アダプターを入れると出力先がdist/client/に変わるので、比較する前にパスの先頭を揃えてください。

npm run build && find dist -name '*.html' | sed 's#^dist/client/#dist/#' | sort > routes-v4.txt
git switch -c astro-5
npm install astro@5
# ここで手順3〜5(設定・コンテンツ定義・型設定の修正)を行う
npm run build && find dist -name '*.html' | sed 's#^dist/client/#dist/#' | sort > routes-v5.txt
diff routes-v4.txt routes-v5.txt

コレクションの数が多く一度に書き換えられない場合は、astro.config.mjsにlegacy: { collections: true }を書くと旧形式のまま動きます(5.18.2の設定スキーマで既定値はfalse)。このフラグは6.0.0で削除され、7系では残してもエラーにならず無視されます。5系のうちに外す前提の足場です。

Astro 5のまま運用する場合の注意点と7系で壊れる箇所

Astroのサポート方針は「The Core team will provide extended maintenance for security fixes only for one previous major version.」で、セキュリティ修正が出るのは現行の1つ前のメジャー版だけです。7.0.0が出た2026年6月22日以降、対象は6系になり、5系には脆弱性の修正も出ません。公開サイトを5系に据え置く理由は、移行工数を確保するまでのつなぎ以外にありません。

移行先の判断材料として、5系で書いたコードを7.4.8でビルドした結果を並べます。

5系でのコード 5.18.2 7.4.8
src/content/config.ts(旧形式) 互換モードで成功 ビルドエラー
Astro.glob() 警告のみ ビルドエラー
output: ‘hybrid’ 設定エラー 設定エラー
content.config.ts+glob() 成功 変更なしで成功
インライン・オブジェクトローダー 成功 変更なしで成功
server:defer+Nodeアダプター 成功 アダプター更新で成功

7.4.8で出たエラーはそれぞれ[LegacyContentConfigError] Found legacy content config file in "src/content/config.ts". Please move this file to "src/content.config.ts" and ensure each collection has a loader defined.とTypeError: Astro.glob is not a functionです。どちらもv6の移行ガイドで削除と明記されています。

この2つのほかに、6系・7系への移行で対応が要るものが3つあります。開発環境とデプロイ環境のNode.jsを、6.0では22.12.0以上、移行先の7.4.8では22.23.4以上にすること、Zod 4への更新に合わせて独自スキーマを見直すこと(transform()と併用するdefault()の既定値は、変換後の型に合わせる)、<ViewTransitions />が削除されたので<ClientRouter />に替えることです(7.4.8のパッケージにはViewTransitions.astro自体がありません)。5系のうちにContent Layer、import.meta.glob()、astro/zod、ClientRouterへ移っておけば、7系への移行はアダプターとNode.jsの更新が中心になります。

5系のままのサイトを、デザインや構成の見直しと合わせて作り直す場合は、Webサイトリニューアルの支援でフレームワークの更新を含めて相談を受けています。新しくプロジェクトを始めるなら、5系を選ぶ理由はありません。npm create astro@latestで現行版から始めてください。

よくある質問

Astro 5はいつリリースされましたか?

5.0.0は2024年12月3日に公開されました。6.0.0が2026年3月10日、7.0.0が2026年6月22日に出ています。

Astro 5系の最新バージョンはいくつですか?

5系の最終リリースは5.18.2(2026年5月26日)です。2026年11月10日時点のAstro全体のlatestは7.4.8です。

Content CollectionsとContent Layerは何が違いますか?

Content Collectionsはsrc/content/内のファイルだけが対象でした。Content Layerはloaderで取得元を指定するため、任意のディレクトリのファイルやJSON、CMSやAPIのデータも同じAPIで扱えます。識別子はslugからidになりました。

output: ‘hybrid’の代わりはどう書けばよいですか?

outputは既定のstaticのままアダプターを追加し、サーバーで描画したいページにexport const prerender = falseを書きます。

Server Islandsは静的ホスティングだけで使えますか?

使えません。島の描画にサーバーが必要なため、アダプターが無いとNoAdapterInstalledServerIslandsエラーでビルドが止まります。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

RELATED POSTS 関連記事

目次