本サイトのフレームワークには Astro を使用していますが、2024 年 12 月 3 日に Astro 5.0 がリリースされたので、v4 から v5 へのアップグレードを実施しました。
本記事では、Astro v5 で追加された主要な機能の説明と、アップグレード作業において、特に気をつける点を取り上げていきます。
Astro v5 のハイライト
見出し「Astro v5 のハイライト」Astro 公式のプレスリリースは以下です。
ハイライトとしては、以下の 6 項目が挙げられています。
- Content Layer
- Server Islands
- 事前レンダリングの簡素化
- 型安全な環境変数(
astro:env
) - Vite 6
- 実験的な機能
Vite 6 については割愛しますが、それ以外の機能を簡単に説明します。
Content Layer
見出し「Content Layer」今回のバージョンにおける最大の特徴は、この Content Layer API の導入が挙げらます。
これまでは、Content Collections によって、ブログ記事などのコンテンツを型安全に管理することができましたが、Content Layer の導入によって、さらに自由かつ柔軟にデータを扱うことができるようになります。
以下の図が一目瞭然ですが、これまでのようにローカルファイルのデータ(md
、mdx
、json
など)に加えて、外部で管理している CMS や API のデータをローダで取得して、必要に応じて加工して使用することができるようになります。

例えば、ヘッドレス CMS からデータを取得したり、直接オープンデータ API からデータを取得して、ウェブページを構築するといったことが可能になります。
カスタムローダを経由して、さまざまなサービスのデータと連携することで、可能性はますます広がるので、これから使い道を考えていくのが楽しみな機能です。
Server Islands
見出し「Server Islands」Server Island は、ページのパフォーマンスを犠牲にすることなく、動的なコンテンツがレンダリングされる仕組みです。

まず、ページ内の静的なコンテンツがレンダリングされ、動的なコンテンツやユーザごとにパーソナライズされたコンテンツは、領域がプレースホルダーで確保されたうえで、準備ができた段階で差し替えられます。
これらのアイランドは独立しているため、ページ内に複数の動的なコンテンツが存在する場合にも、他のコンテンツによる遅延の影響を抑えられるようです。
事前レンダリングの簡素化
見出し「事前レンダリングの簡素化」Astro 2.0 にてハイブリッドレンダリングが導入されましたが、Astro の開発チームは、機能が追加されるとともに、ケースごとの説明や文書化が重荷になるという問題を抱えていたようです。
また、ユーザが必要以上に気軽に SSR(server-side rendering)を使用することで、パフォーマンスが犠牲になる傾向が見受けられたため、この仕組みが見直されることになりました。
Astro 5.0 からは、デフォルトではすべて静的ページとして事前レンダリングされるようになります。SSR でコンテンツを追加したい場合には、アダプタを追加するだけで対応できるとのことで、全体的な仕組みがシンプルでわかりやすくなりました。
型安全な環境変数(astro:env
)
見出し「型安全な環境変数(」環境変数(astro:env
)に、型安全なスキーマが定義できるようになりました。
型(string
、number
、boolean
、enum
)に加えて、使用されるのはクライアントかサーバか、公開か秘密か、任意であるか、そしてデフォルト値などを指定できます。
実験的な機能
見出し「実験的な機能」実験的な機能(Experimental)は以下の 3 点です。
- 画像の切り抜きサポート
- レスポンシブイメージのレイアウト(
srcset
、sizes
属性の自動生成) - SVG コンポーネント
最初の 2 つはレスポンシブイメージに関するもので、<Image />
と <Picture />
の 2 つのコンポーネントで使用できます。特に srcset
、sizes
属性の自動生成については待望の機能です。
簡単に試した限りでは、srcset
と sizes
が自動的に出し分けられ、それに合わせてリサイズされた画像が書き出されました。また、fit
属性の記述によって画像がトリミングされ、対応したスタイルが適用されることも確認できました。
まだ実験的な機能なので、実際のプロダクトに使用するには慎重さが求められますが、今後、レスポンシブイメージを指定するわずらわしさを軽減できるかもしれません。
最後の SVG コンポーネントは、読み込んだ SVG ファイルをインライン SVG として出力してくれるもので、シンプルですがこちらも待望の機能です。
本サイトではこれまで、Vite の raw
クエリを使用して SVG のコードを読み込んで、set:html
ディレクティブを使用して直接 HTML を出力する方法を採用していました1。
---
const { default: menu } = await import('../assets/img/btn-menu.svg?raw');
---
<Fragment set:html={menu} />
これを SVG コンポーネントに置き換えると、以下のようにシンプルな記述になるので、安定版になったタイミングでこちらへの切り替えを検討したいと考えています。
---
// Experimental
const SVGMenu = from '../assets/img/btn-menu.svg';
---
<SVGMenu />
Astro v5 へのアップグレード
見出し「Astro v5 へのアップグレード」Astro v5 へのアップグレードは以下のガイドに従い進めます。
本サイトにおいては、v3 から v4 へのアップグレードはスムーズに移行できたのですが、今回の v4 から v5 への移行では既存のコードへの影響がいくつかあり、個別の調整が必要でした。
アップグレード後、調整が不十分なまま不用意に公開してしまったため、一時的にスタイルが崩れる、機能が応答しない、RSS のリンク先が 404 Not Found
になるといった状況がありました 🙇♂️
以下、特に影響のあった変更をいくつか取り上げます。
レガシー: Content Collections API
見出し「レガシー: Content Collections API」Astro v5 で導入された Content Layer API にあわせて、コレクションの記述が変更になりました。legacy
フラグを有効にすることで、サポート期間内であればこれまでの記述でも使用できるようですが2、Content Layer を試す目的に加え、パフォーマンス面の向上も期待できるので新しい記述に変更しました。
ただ、変更箇所が多く、見落としによって記事ページへのリンクが undefined
になってしまうといったことがありました。そのため、時間的な余裕があるときに対応したほうがよさそうです。
具体的な進め方は、以下の記事の「Step-by-step instructions to update a collection(コレクションをアップデートする手順)」などを参考にしてください。
変更: <script>
タグの位置
見出し「変更: 」これまで、Astro ファイルに記述した <script>
要素は、出力される HTML の <head>
要素の下に移動していましたが、Astro v5 ではこの移動がなくなり、コンポーネントの直後に出力されるようになりました。
この仕様変更により、連続して出現するコンポーネントの CSS に影響がありました。
例えば、以下のようなコンポーネントがあるとします。<style>
には隣接セレクタを使用します。なお、<script>
はタグを出力させるためのもので、コードの内容に意味はありません。
---
const { num } = Astro.props;
---
<div>
{num}
</div>
<style>
/* 隣接セレクタ */
div + div {
margin-block-start: 2em;
}
</style>
<script>
const foo = 'bar';
</script>
このコンポーネントを連続で読み込みます。
---
import Num from './components/Num.astro';
---
<!-- コンポーネントを連続で 5 回呼び出し -->
{[Array.from({ length: 5 }).map((_, index) => (
<Num num={index + 1}>
))]}
このとき、出力される HTML と CSS は以下のようになります(簡略化しています)。
最初に呼び出されたコンポーネントの直後に <script>
タグが挿入されることで、CSS の隣接セレクタが期待どおりに適用されなくなります。
<div>1</div>
<script type="module" src="..."></script>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<!-- 隣接セレクタが一致しない「2」のマージンが反映されない -->
<style>
div + div {
margin-block-start: 2em;
}
</style>
このように、隣接セレクタや :nth-child()
のように、要素の出現順が前提となる疑似クラスを使用している場合には、特に注意が必要です。
変更: 論理属性以外の HTML 属性の値
見出し「変更: 論理属性以外の HTML 属性の値」HTML では論理属性(Boolean Attribute)と呼ばれる、存在すれば true
、存在しなければ false
になる属性があります3。
具体的には、checked
や disabled
などが挙げられ、値が指定されていなくても属性自体が存在すれば true
になるのが特徴です。
<!-- 属性が存在すれば、値に関わらず `true` になる -->
<label><input type="checkbox" name="check1" checked>1</label>
<label><input type="checkbox" name="check2" checked="">2</label>
<label><input type="checkbox" name="check3" checked="checked">3</label>
<label><input type="checkbox" name="check4" checked="true">4</label>
<label><input type="checkbox" name="check5" checked="false">5</label>
<!-- 属性が存在しなければ `false` になる -->
<label><input type="checkbox" name="check6">6</label>
Astro ファイルで属性の出し分けをする目的で、true
もしくは false
の指定をすることがありますが、v5 からはこの方法が有効になるのが論理属性に限定されます。
<!-- 論理属性 -->
<label><<input type="checkbox" name="check1" checked={true}>foo</label>
<label><<input type="checkbox" name="check2" checked={false}>bar</label>
<!-- 非論理属性 -->
<custom-element feat={true}>foo</custom-element>
<custom-element feat={false}>bar</custom-element>
<!-- 非論理属性 -->
<div data-stack={true}>foo</div>
<div data-stack={false}>bar</div>
最初の checked
は論理属性ですが、カスタム要素の独自属性である feat
や、 data-*
属性は論理属性ではありません。結果を見てみましょう。
<!-- 論理属性 -->
<label><input type="checkbox" name="check1" checked>foo</label>
<label><input type="checkbox" name="check2">bar</label>
<!-- 非論理属性 -->
<custom-element feat="true">foo<custom-element>
<custom-element feat="false">bar<custom-element>
<!-- 非論理属性 -->
<div data-stack="true">foo</div>
<div data-stack="false">bar</div>
このように、論理属性ではない場合、true
と false
がそのまま文字列として出力されてしまいます。属性自体の出し分けをしたい場合には、以下のように undefined
もしくは null
を使用することで実現できます。
<custom-element feat={undefined}>foo</custom-element>
<div data-stack={undefined}>bar</div>
より実用的な例としては、以下のように props
に応じて属性を出し分けます。
---
type Props = {
feat? : boolean;
stack? : boolean;
}
const { feat, stack } = Astro.props;
---
<custom-element feat={feat || undefined}>foo</custom-element>
<div data-stack={stack || undefined}>bar</div>
astro check
見出し「astro check」ひととおり対応が完了したら、astro check
を実行して、重大なエラーが発生していないかを確認するのがよいでしょう。
npx astro check
おわりに
見出し「おわりに」この記事では、Astro v5 で追加された主要な機能の説明や、本サイトを v5 にアップグレードすることで必要になった対応を紹介しました。
記事内では具体的な内容は割愛しましたが、Content Collections API の変更は影響範囲が大きく、今回のアップグレードで特に注意を払う作業でした。
また、論理属性以外の HTML 属性の値は、意図せずに false
を指定していてもエラーとして表示されないため探しづらく、まだ見落としている可能性もあります。
もし、サイト内での不具合など、お気づきの点がございましたらお知らせください。