Skip to content
Cloud AI エンジニア入門ガイド
Go back

Astro 7.4の新機能:ビルド時間の内訳表示と並列プリレンダリング

Astro 7.4 が2026年10月15日に出ました。7.3は前のリリースの穴埋めが中心でしたが、7.4は「ビルドが遅い」という悩みに向けた変更が中心です。

ほかに、自前のエントリポイント向けの notFound() ハンドラー、フォント設定の警告、静的ビルドのリダイレクトページを自作できる実験的機能も入りました。

先に一つだけ注意しておきます。10月21日に出た7.4.2で、必要なNode.jsのバージョンが 22.23.4以上 に上がりました。同じ日に出たNode.jsのセキュリティリリースに合わせた変更です。Node.js 22系でこれより古いバージョンを使っていると、astro コマンドは起動直後にエラーで止まります。CIやホスティングのNode.jsのバージョンを固定している人は、Astroより先にそちらを確認してください。

このブログのようなアダプターなしの静的サイトでも、--timings、並列プリレンダリング、Node.jsの件の3つは関係します。画像サービスの分離と notFound() はオンデマンドレンダリングや自前のエントリポイントを使っている人向けです。

Astroを触ったことがある方向けですが、出てくる用語はその都度説明します。

Astro 7.4の変更点を、ビルドを速くする機能、サーバー側の機能、設定の見直し、Node.jsの最低バージョン引き上げに分けてまとめたマップ

Table of contents

Open Table of contents

リリースの中身

マイナーリリースなので、既存のコードが動かなくなる変更は基本的にありません。ただし、Node.jsの最低バージョンは上がりました。7.4.0で入った変更は次のとおりです。

変更点触る場所ひとことで言うと
ビルド時間の内訳表示astro build --timingsどこが遅いかを数字で見る
画像サービスの分離image.service.build / runtimeビルド時はSharp、実行時は画像CDN、と分けられる
notFound() ハンドラーastro/fetch、astro/hono自前のエントリポイントでも404ページを返す
フォント設定の警告フォントプロバイダーの getFontProperties()存在しないweightなどを指定すると警告が出る
Node.jsの最低バージョン実行環境22.22.0以上(7.4.2で22.23.4以上)
並列プリレンダリング(実験的)experimental.parallelPrerender静的ページを複数スレッドで描画する
カスタムリダイレクトページ(実験的)experimental.redirectPage静的ビルドのリダイレクト用HTMLを自作する

どの変更が関係するかは、Astroの使い方で変わります。使い方ごとに分けると次のとおりです。

静的サイト、サーバーで動かすサイト、拡張を作るインテグレーション作者の3列に関係する変更を並べ、全員に関係するNode.jsの最低バージョンとフォント設定の警告を下にまとめた図

その後、変更履歴によると、10月19日に7.4.1、10月21日に7.4.2が出ています。2026年10月24日時点の最新は7.4.2です。

アップグレードは公式のアップグレード手順どおり専用コマンドを使うのが楽です。Astro本体と公式インテグレーションをまとめて更新してくれます。

npx @astrojs/upgrade        # npm
pnpm dlx @astrojs/upgrade   # pnpm
yarn dlx @astrojs/upgrade   # Yarn

@astrojs/cloudflare か @astrojs/markdoc を使っている場合は、Astro本体と同時に上げてください。7.4.0と同じ日に出た @astrojs/cloudflare 14.4.0と @astrojs/markdoc 2.1.0は、どちらも astro@^7.4.0 を必要とします。

ビルド時間の内訳を出す —timings

何が出るのか

ビルドが遅くなったとき、原因の候補はたくさんあります。Viteのプラグインかもしれないし、シンタックスハイライト(コードの色付け)かもしれないし、画像の変換や、特定の重いページかもしれません。これまでは、原因を探すのに設定を一つずつ外して試すしかありませんでした。

7.4から、astro build に --timings フラグを付けられます。

astro build --timings

ビルドが終わると、どこに何秒かかったかの要約が表示されます。CLIリファレンスによると、要約に入る項目は次のとおりです。

項目中身
ビルドフェーズ設定の読み込み、コンテンツの同期、バンドル、ページ生成などの段階ごとの時間
Viteプラグインプラグインごとの時間と、バンドルに時間がかかったファイル
Markdownremark/rehypeプラグインと、シンタックスハイライトの時間
ページのレンダリングページごとの描画時間と、遅いページの一覧
画像画像の最適化にかかった時間
インテグレーションインテグレーションのフックごとの時間

どこかを高速化する前に、まず遅い場所を確かめるための機能です。

リリース記事の例を読む

リリース記事では公式のblogスターターに <CommentSection> というコンポーネントを足した例で使い方を見せています。記事ごとにコメントを取得し、その中の <Comment> が投稿者のプロフィールを1件ずつ取得する作りです。

---
// src/components/Comment.astro
const { comment } = Astro.props;
// コメントごとに投稿者のプロフィールを取得する
const res = await fetch(`https://jsonplaceholder.typicode.com/users?email=${comment.email}`);
const [user] = await res.json();
---
<article>
  <strong>{user?.name ?? comment.name}</strong>
  <p>{comment.body}</p>
</article>

コードに間違いはありません。ただ、プロフィールの取得はコメント一覧が返ってくるまで始められません。そのため、どのページもネットワークの往復を2回、順番に待つことになります。この状態で --timings を付けてビルドすると、次のような要約が出ます。リリース記事の出力から一部を抜き出し、横棒のグラフは省きました。

 build timings
total 2.61s · 9 pages · output "static"

Build phases
  setup                  225ms   8.6%
  content sync           206ms   7.9%
  prerender bundle       151ms   5.8%
  generate pages         2.10s  80.6%

Page rendering (2.09s across 9 pages)
  component render       2.08s  79.6%
  page pipeline           13ms   0.5%

Slowest pages (median 297ms · p95 571ms)
  /about                 571ms
  /blog/first-post       307ms   /blog/[...slug]
  /blog/using-mdx        303ms   /blog/[...slug]
  /rss.xml                10ms
  /blog                    6ms

上から順に読むと原因が絞れます。

  1. generate pages(ページ生成)がビルド全体の約81%を占めている
  2. その内訳を見ると、ほぼ全部が component render(コンポーネントの中の処理)で、Astro自身の処理である page pipeline は0.5%しかない
  3. 遅いページはどれも記事レイアウトを使うページで、/blog や /rss.xml は数ミリ秒で終わっている

この3段階の読み方と、たどり着いた原因を1枚にまとめます。

--timingsの要約を、ページ生成が80.6%、コンポーネント内が79.6%、遅いのは記事レイアウトのページ、の3段階で絞り込み、原因の直列取得とPromise.all()による改善を下にまとめた図

median は中央値、p95 は95パーセンタイル(遅い方から5%の位置の値)です。ここまで分かれば、記事レイアウトの中で描画している何かが遅い、と見当が付きます。

修正前はコメント取得とプロフィール取得が順番に2往復し、修正後はPromise.all()で1往復にまとめた様子を示すシーケンス図

リリース記事は <CommentSection> の中で Promise.all() を使ってコメントと全員分のプロフィールを同時に取得し、プロフィールを <Comment> へ渡す形に直しています。Promise.all() は複数の非同期処理を同時に始めて、全部が終わるのを待つ関数です。これでページ生成は2.10秒から1.13秒になり、記事ページ1枚あたりの時間は半分になりました。

修正後のコードはリリース記事に載っていません。たとえば次のように書けます。

---
// src/components/CommentSection.astro(筆者が書いた一例)
import Comment from './Comment.astro';
const { postId } = Astro.props;

// 2つのリクエストを同時に送り、両方の結果を待つ
const [comments, users] = await Promise.all([
  fetch(`https://jsonplaceholder.typicode.com/comments?postId=${postId}`).then((r) => r.json()),
  fetch('https://jsonplaceholder.typicode.com/users').then((r) => r.json()),
]);
const userByEmail = new Map(users.map((user) => [user.email, user]));
---
{comments.map((comment) => (
  <Comment comment={comment} user={userByEmail.get(comment.email)} />
))}

<Comment> 側は fetch() をやめて、Astro.props から user を受け取るだけにします。

使いどころ

リリース記事はこの後で紹介する並列プリレンダリングや、7.2で入ったインクリメンタルビルドを試す前の最初の一歩として --timings を勧めています。並列化で速くなるのは描画が遅いときで、遅いのが画像の変換やバンドルなら効果は小さくなります。どこが遅いかを先に確かめておけば、どの対策を選ぶべきかが分かります。

自分なら、Astroや依存パッケージを上げる前後に1回ずつ --timings を付けてビルドし、数字を残しておきます。上げた後に遅くなっても、どの段階が遅くなったのかをすぐ比べられるからです。

ビルド用と実行時用で画像サービスを分ける

プリレンダリングとオンデマンドレンダリング

Astroのページの作り方は2通りあります。

画像サービスは <Image /> や getImage() の裏で画像を縮小したり形式を変えたりする部品です。組み込みの画像サービスはSharpという画像処理ライブラリを使います。

2種類のページでは、画像処理の向き不向きが違います。ビルド時は時間に余裕があり、Node.jsの機能もすべて使えるので、Sharpのように手元で変換する方式が向いています。一方、オンデマンドのページはサーバーやエッジで動きます。Cloudflare Workersのように、ネイティブモジュール(C言語などで書かれた部品)を含むSharpを動かせない環境も多く、画像CDN(画像の変換と配信を引き受けるサービス)に任せる方が向いています。

これまでは、画像サービスを1つだけ選ぶか、アダプターに選んでもらうしかありませんでした。お弁当屋さんで言えば、作り置きの弁当と注文を受けてから作る弁当を、同じ厨房で作るしかなかったようなものです。2種類のページの条件と、向いている画像サービスを並べると次のようになります。

作り置きの弁当にあたるプリレンダリングと、注文を受けてから作る弁当にあたるオンデマンドレンダリングの条件を左右に並べ、buildにはSharpなど、runtimeには画像CDNなどが向くことを対比した図

7.4での変更

7.4から、image.service に build と runtime の2つを指定できます。

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

export default defineConfig({
  image: {
    service: {
      build: { entrypoint: 'astro/assets/services/sharp' },
      runtime: { entrypoint: 'my-image-cdn-service' },
    },
  },
});

my-image-cdn-service は、使いたい画像CDN向けの画像サービスに置き換えてください。

プリレンダリングするページと開発サーバーはbuildサービスへ、オンデマンドのページと本番の画像エンドポイントはruntimeサービスへ向かい、それぞれビルド時の生成とリクエストごとの変換につながる対応図

サービス担当する場面向いている実装
buildプリレンダリングするページの画像(ビルド時)と、開発サーバーSharpなど手元で変換するもの
runtimeオンデマンドのページの画像と、本番の画像エンドポイント画像CDNなど

これまでどおり、サービスを1つだけ指定する書き方も使えます。その場合は両方の場面で同じサービスが使われます。

設定リファレンスによると、プリレンダリングするページの画像は、ビルド時に必ずNode.jsで生成されます。そのため、アダプターが別の実行環境でプリレンダリングする場合でも、build にはSharpを指定できます。

この変更のために、ビルド中に使われた画像を追跡する仕組みが作り直されました。プリレンダラー(ビルド時にページを描画する部品)は、画像の処理を自前で再現せず、Astro本体の画像パイプラインに乗るようになりました。その結果、@astrojs/cloudflare も、プリレンダリングするページの画像をほかのアダプターと同じ経路で処理するようになっています。

インテグレーション作者向けの変更

インテグレーションを自作していない人は、この節を読み飛ばして構いません。

一つ目は設定オブジェクトの形です。config.image.service は利用者が書いた形のまま渡されます。利用者が build と runtime を指定した場合、config.image.service.entrypoint と config.image.service.config は undefined になります。画像サービスを読むインテグレーションは、runtime プロパティがあるかを確かめてください。

'astro:config:done': ({ config }) => {
  const { service } = config.image;
  const runtimeService = 'runtime' in service ? service.runtime : service;
  // runtimeService.entrypoint を読む
},

updateConfig() で image.service を設定するときも注意が要ります。設定済みの値と形が違う場合は、マージ(結合)されずに置き換わります。たとえば build と runtime を使っているプロジェクトへ単一のサービスを設定すると、両方とも置き換わります。

二つ目はカスタムプリレンダラーです。setPrerenderer() でAstro標準のプリレンダラーを包んでいる場合は、getStaticPaths() と render() が返すメタデータ(ページで使った画像の情報)を、そのまま引き継いでください。

setPrerenderer((defaultPrerenderer) => ({
  name: 'my-prerenderer',
  async setup() {
    await defaultPrerenderer.setup?.();
  },
  async getStaticPaths() {
    const result = await defaultPrerenderer.getStaticPaths();
    const { paths, metadata } = Array.isArray(result) ? { paths: result } : result;
    // 下書きのページを除外しつつ、metadata は引き継ぐ
    return { paths: paths.filter(({ pathname }) => !pathname.startsWith('/drafts')), metadata };
  },
  render(request, options) {
    return defaultPrerenderer.render(request, options);
  },
  async teardown() {
    await defaultPrerenderer.teardown?.();
  },
}));

setup() と teardown() も、Astro標準のプリレンダラーへそのまま渡してください。Astro標準のプリレンダラーは、setup() の中でビルド済みのアプリを読み込みます。setup() を書かないと、アプリを読み込まないまま getStaticPaths() が呼ばれ、ビルドが失敗します。

getStaticPaths() から配列だけを返す書き方や、render() から Response だけを返す書き方も、まだ動きます。ただし非推奨になり、そのページで使った画像は生成されません。ビルドは通るのに画像だけが欠けるので気付きにくく、早めに直しておくのが無難です。

自前のエントリポイント向けのnotFound()

Astro 7.0の高度なルーティングでは、Astroのリクエスト処理を部品に分けて、自分で組み直せます。部品は astro/fetch と astro/hono から取り出します。既定の処理を丸ごと走らせる astro() のほかに、ミドルウェアを走らせる middleware()、ルートに一致したページを描画する pages() などの個別のハンドラー(処理を受け持つ関数)があります。Cloudflare Workersのworkerエントリポイントを自分で書くときによく使います。詳しくは7.3の記事で説明しました。

ここに穴がありました。astro() を使わずに pages() を直接使うと、どのルートにも一致しないリクエストに対して、空の404が返っていました。src/pages/404.astro を用意していても、プリレンダリング済みでも同じです。変更履歴によると、内部向けの X-Astro-Error ヘッダーだけが付いた、中身のないレスポンスです。

7.4.0で notFound() ハンドラーが入りました。pages() より前に置くと、一致するルートがないときに404ページを描画して返します。7.3までとの違いは次のとおりです。

pages()を直接使うエントリポイントで、7.3まではX-Astro-Errorヘッダーだけの空の404が返り、7.4からはnotFound()を先に通すことで404.astroが描画されることを対比した図

Honoで書いたCloudflare Workersのエントリポイントなら、次のようになります。HonoはWorkersなどで動く軽量なWebフレームワークです。

// src/worker.ts
import { Hono } from 'hono';
import { notFound, pages } from 'astro/hono';
import { cf } from '@astrojs/cloudflare/hono';

const app = new Hono();

app.use(cf());
app.use(notFound());
app.use(pages());

export default app;

リクエストがまずcf()で静的アセットか判定され、次にnotFound()でルートの有無を確かめ、ルートがなければ404.astroを、あればpages()でページを返す流れ

astro/fetch 版も同じ名前で用意されています。astro/fetch のリファレンスによると、ルートが一致したときは undefined を返すので、そのまま後続の処理へ進めます。

// src/fetch.ts
import { FetchState, middleware, notFound, pages } from 'astro/fetch';

export default {
  async fetch(request: Request): Promise<Response> {
    const state = new FetchState(request);
    const notFoundResponse = await notFound(state);
    if (notFoundResponse) return notFoundResponse;
    return middleware(state, (s) => pages(s));
  },
};

リファレンスには、過剰にエンコードされたパスへのリクエストに対しては、404ではなく空の400を返すとも書かれています。

astro() には最初からこの処理が入っています。notFound() が必要なのは、ハンドラーを自分で組み合わせている場合だけです。

フォント設定の警告

Astroのフォント機能(fonts 設定)では、Google Fontsなどの提供元をフォントプロバイダーとして指定し、使うweight(太さ)、style(斜体かどうか)、subset(文字の範囲)、ファイル形式を選びます。

ところが、そのフォントファミリーが持っていないweightやsubsetを指定しても、これまでは何も言われませんでした。見出しを 700 で指定したのに細いまま、といった見た目の違和感で初めて気付くことになります。

7.4.0から、フォントプロバイダーは getFontProperties() という任意のコールバックを実装できます。そのファミリーが実際に持っているweight、style、subset、形式を返す関数です。Astroは設定に書かれた値のうちプロバイダーが提供できないものについて、1つずつ警告を出します。

400と700しか持たないフォントに300も指定した場合で、前後を比べてみます。

400と700しか持たないフォントにweight 300も指定した例で、7.3までは何も言われず見た目で気付くのに対し、7.4からはgetFontProperties()と照合してビルド時に警告が出ることを対比した図

import type { FontProvider } from 'astro';

const provider: FontProvider = {
  name: 'my-provider',
  resolveFont: ({ familyName }) => {
    // ...
  },
  getFontProperties: ({ familyName }) => ({
    weights: ['400', '700'],
    styles: ['normal', 'italic'],
    subsets: ['latin'],
    formats: ['woff2'],
  }),
};

フォントプロバイダーのリファレンスによると、weightは "400" のような単独の値のほか、可変フォントなら "100 900" のような範囲でも書けます。知らないファミリーなら undefined を返せば、そのファミリーの確認は飛ばされます。

組み込みのフォントプロバイダーはすべて対応済みです。利用者は何もしなくても、上げるだけで警告が出るようになります。このブログもフォント機能でGoogle Fontsのフォントを複数のweightで読み込んでいるので、上げたらまずビルドログに警告が出ていないかを見るつもりです。

フォントプロバイダーを自作している人は、今のうちに getFontProperties() を実装しておくよう勧められています。Astro 7では任意ですが、Astro 8で必須になる予定です。

もう一つ、フォントプロバイダーの init() に渡されるコンテキストに fetch() 関数が加わりました。一時的な失敗を自動で再試行してくれる fetch() です。unifont(Astroのフォント機能が内部で使うライブラリ)のプロバイダーを包んで作る場合は、このコンテキストをそのまま渡せます。

Node.jsの最低バージョンが上がる

7.4.0で22.22.0、7.4.2で22.23.4

7.4.0で、Astroと公式インテグレーションが対応するNode.jsの最低バージョンが22.22.0になりました。ブラウザ上でNode.jsを動かすStackBlitzが対応している、Node.js 22系の最新版に合わせた変更です。

さらに7.4.2で、最低バージョンが22.23.4に上がりました。2026年10月21日に出たNode.jsのセキュリティリリースを取り込むためです。このリリースでは10件の脆弱性が修正され、そのうち7件が22系に影響します。深刻度が最も高いのは「High」で、DNSの名前解決や証明書の検証、Brotli(圧縮形式の一つ)の展開処理に関するものが含まれます。

Astro最低のNode.js理由
7.3系22.12.07.0からの値
7.4.0、7.4.122.22.0StackBlitzが対応する22系の最新版に合わせた
7.4.222.23.42026年10月のNode.jsセキュリティリリースを取り込んだ

パッチリリースで最低バージョンが上がるのは珍しく見えますが、ルールどおりの動きです。Astroのアップグレード方針では、Node.jsの脆弱性がAstroに影響する場合、メンテナンスLTS(長期サポート版のうち保守段階のもの)の最低バージョンをパッチリリースで上げてよい、と定めています。

古いNode.jsで起きること

package.json の engines 欄が変わるだけなら、パッケージマネージャーが警告を出す程度で済みます。ところが、Astroのコマンド本体にも同じ条件が書き込まれています。7.4.2の bin/astro.mjs を読むと、メジャーバージョンが23以下のNode.jsで >=22.23.4 を満たさない場合、何もせずにエラーで終了します。

つまり、Node.js 22.22.0を使っているCIでAstroを7.4.2に上げると、astro build がその場で止まります。逆に、Node.js 24以降はこの確認自体が飛ばされます。使っているバージョンごとの動きをまとめると次のとおりです。

使っているNode.jsのバージョンで分岐し、22系で22.23.4未満なら起動せずに終了、22系で22.23.4以上ならそのまま起動、24系以降は確認を飛ばして起動することを示した図

確認しておきたい場所は次のとおりです。

自分のプロジェクトの package.json の engines も合わせて直しておくと親切です。このブログも >=22.12.0 のままなので、上げるときに直す必要があります。

なお、Node.js 24系を使っている場合も、同じセキュリティリリースで24.22.1が出ています。Astroの起動条件とは関係ありませんが、こちらも上げておくのが無難です。

実験的機能:並列プリレンダリング

何が変わるのか

Astroは静的ページを並行して描画しますが、これまではすべて1つのスレッド(プログラムの実行の流れ)の上で動いていました。JavaScriptは1つのスレッドで1つの処理しか実行できません。fetch() の待ち時間のような「待ち」は重ねられても、計算の重いページは1ページずつしか処理できません。CPUのコアがいくつあっても、使えるのは1つだけでした。

7.4.0で入った experimental.parallelPrerender フラグを有効にすると、静的ページをNode.jsのワーカースレッドで描画します。ワーカースレッドは1つのプロセスの中で別々に動くJavaScriptの実行環境です。それぞれが別のCPUコアを使えます。レジが1台しかなかったスーパーに、レジを何台も増やすようなものです。

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

export default defineConfig({
  experimental: {
    parallelPrerender: true,
  },
});

既定では使えるCPUコアのうち1つを残して全部使います。ワーカーを増やすほどメモリの使用量も増えます。ビルドがメモリ不足で落ちる場合や、速くならない場合は、workers で数を減らします。

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

export default defineConfig({
  experimental: {
    parallelPrerender: {
      workers: 4,
    },
  },
});

各ワーカーはbuild.concurrency で指定した数のページを同時に描画します。build.concurrency の既定値は1なので、既定のままならワーカー数がそのまま同時に描画するページ数になります。

astro buildが変更のないページを前回の出力で済ませ、残りのページをメインスレッドから複数のワーカーへ振り分け、各ワーカーがbuild.concurrency件ずつ描画してdistへ書き出す流れ

7.2で入ったインクリメンタルビルドとも相性が良い、とリリース記事は書いています。インクリメンタルビルドが変更のないページを飛ばし、並列プリレンダリングが残りのページを複数のコアに振り分ける、という分担です。7.2の時点では、build.concurrency を1より大きくするとインクリメンタルビルドが機能しませんでしたが、7.3で修正されています。

制限と注意点

並列プリレンダリングのドキュメントには、次の制限が書かれています。

制限何が起きるか
モジュールの変数を共有しないページの描画中にモジュールのトップレベルの変数へ入れておいた値が、別のページの描画時には無いことがある
getStaticPaths() の props はシリアライズできる値に限る関数やクラスのインスタンスを含むページは並列に描画できない
アダプター独自のプリレンダリングとは併用できない公式アダプターでは @astrojs/cloudflare が該当し、フラグは無視されてビルド時に警告が出る

一つ目は気付かずに踏みやすい制限です。たとえば、APIの結果をモジュールのトップレベルの Map に入れて、ページ間で使い回しているとします。シングルスレッドなら最初のページで1回取得すれば済みますが、ワーカーごとにメモリが分かれるので、ワーカーの数だけ取得が走ります。速度が出ないだけならまだしも、APIの呼び出し回数制限に引っかかるかもしれません。ページ間で値を共有したいなら、コンテンツコレクションやファイルなど、スレッドの外に置く方法を考えてください。ワーカーが3つの場合を図にすると次のようになります。

APIの結果をトップレベルのMapに入れて使い回す例で、シングルスレッドではMapが1つで取得は1回、ワーカー3つの並列ではMapが3つでき取得も3回になることを対比した図

二つ目の「シリアライズ」は、値をスレッド間で受け渡せるデータの形に変換することです。数値、文字列、配列、ふつうのオブジェクトは問題ありません。関数やクラスのインスタンスは変換できません。

自分なら、まず --timings でページ生成がビルド時間の大半を占めていることを確かめてから試します。ページ数が少ないサイトや、時間の大半がネットワークの待ちで占められているサイトでは、ワーカーを起動する手間の方が大きくなることもあります。実験的機能なので、本番のビルドに入れる前に、生成されたHTMLが変わらないことも確かめておくと安心です。

実験的機能:カスタムリダイレクトページ

静的サイトのリダイレクトの仕組み

リダイレクトはあるURLへのアクセスを別のURLへ転送する仕組みです。サーバーがあれば、ステータスコード301や302と一緒に Location ヘッダーで転送先を返せます。

静的サイトには、ヘッダーを組み立てて返すサーバーがありません。そこでAstroはリダイレクトごとに <meta http-equiv="refresh"> タグ入りの小さなHTMLを生成します。このタグは指定した秒数の後に別のURLへ移動するようブラウザへ指示します。対象は次の3つです。

ブラウザが旧URLを要求すると静的ホスティングがmeta refresh入りの小さなHTMLを返し、ブラウザがdelay秒待ってから新URLを要求する流れ

このHTMLの中身、文言、待ち時間は固定されていて、デザインを合わせたり日本語に訳したりはできませんでした。引っ越しの貼り紙を、自分の言葉で書けなかったようなものです。

7.4での変更

experimental.redirectPage を true にすると、このページを自作できます。

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

export default defineConfig({
  experimental: {
    redirectPage: true,
  },
});

続いて src/pages/3xx.astro を作ります。Astroはリダイレクトごとにこのページを1回描画し、そのリダイレクトの情報をpropsとして渡します。日本語のサイトなら、たとえば次のように書けます。

---
// src/pages/3xx.astro
import type { RedirectPageProps as Props } from 'astro';

const { from, to, status, delay } = Astro.props;
---
<html lang="ja">
  <head>
    <meta http-equiv="refresh" content={`${delay};url=${to}`} />
    <meta name="robots" content="noindex" />
    <title>ページを移動しました</title>
  </head>
  <body>
    <p>このページは移動しました。<a href={to}>{to}</a> へ進んでください。</p>
  </body>
</html>

リダイレクトページのドキュメントによると、渡されるpropsは次の4つです。

prop型中身
fromstring転送元のパス
tostring転送先
statusnumber301 や 302 などのステータスコード
delaynumbermeta refreshの待ち秒数。302 なら2、それ以外は0

/old から /new への301リダイレクトを例に、設定がpropsを経てmeta refreshタグになるまでを追うと次のようになります。

redirectsで/oldを/newへ301で転送する設定が、3xx.astroのfrom、to、status、delayのpropsになり、toとdelayから0;url=/newのmeta refreshタグが書き出される流れを示した図

静的ビルドでは、<meta http-equiv="refresh"> タグそのものがリダイレクトの本体です。自作したページには to へ向かうこのタグを必ず入れてください。タグが無いか、転送先が to と違う場合は、ビルド時に警告が出ます。3xx.astro が無いか描画に失敗した場合は、組み込みのページに戻ります。

細かい挙動も書いておきます。

仕様についての意見はRFCで受け付けています。

そのほかの変更と7.4系の修正

インテグレーションによる出力先の制御

7.4.0では、Viteが環境ごとに解決した outDir(出力先)を、client、server、prerenderの出力先を決める基準として扱うようになりました。インテグレーションやViteのプラグインが出力先を動かせます。カスタムプリレンダラーは setPrerenderer() の新しい outputDirectories コンテキストから最終的な出力先を受け取れます。Viteのビルドマニフェスト(どのファイルがどこへ出力されたかの一覧)も、すべての buildApp フックが終わるまで残るようになり、デプロイ用のインテグレーションから読めるようになりました(#18313)。

ビルドエラーを隠していた不具合の修正

7.4.0の修正のうち、静的サイトでも気を付けたいのが次の1件です。

これが直ったので、今まで成功していたビルドが、7.4に上げた途端に失敗することがあります。このブログもremark/rehypeのプラグインを使っています。Markdownのプラグインを使っているサイトでビルドが落ちたら、まずエラーメッセージに出ているプラグインと記事を確認してください。Astroの不具合ではなく、元から起きていたエラーが見えるようになっただけの可能性が高いです。7.3までと7.4からの違いを並べておきます。

remark/rehypeプラグインが例外を投げたとき、7.3まではエラーが握りつぶされ終了コード0で成功したように見え、7.4からはビルドが失敗してエラーメッセージが表示されることを対比した図

同じく、@astrojs/cloudflare のように独自の実行環境でプリレンダリングするアダプターと src/fetch.ts を組み合わせた場合も、プリレンダリング中のエラーが握りつぶされていました。これまでは空のHTMLが書き出されてビルドが成功していましたが、7.4.0からはエラーでビルドが失敗します。

7.4.1と7.4.2の主な修正

変更履歴から、当てはまる人が多そうなものを選びました。

版修正内容
7.4.1開発サーバーの起動中にAstroの設定ファイルを編集するたびに、メモリ使用量が増えていく
7.4.1パスにアポストロフィ(')を含むプロジェクトで、セッション、ミドルウェア、キャッシュプロバイダー、src/fetch.ts を読み込めない
7.4.1アプリケーションのコードから astro/config をimportすると、Unterminated string でビルドが失敗する(共通モジュールで fontProviders を読み込む場合など)
7.4.1親ディレクトリに解決できない tsconfig.json があると、astro build が TSCONFIG_ERROR で失敗する
7.4.2astro sync、astro check、astro build を実行すると、同じプロジェクトで動いている開発サーバーの最適化済み依存関係を置き換えてしまい、アイランドが動かなくなる
7.4.2Linuxのスレッドに割り当てられたIDが残ったロックファイルを、動作中のAstroサーバーと誤認する。astro dev stop が無関係なプロセスを止める恐れがあった
7.4.2環境変数に $& などの置換パターンが含まれると、開発時に astro:env/server が正しく生成されない(Kittyターミナルが既定で設定する環境変数で起きていた)

7.4.2の1件目は、AIエージェントに開発サーバーをバックグラウンドで動かしてもらいながら、astro check や astro build も実行させるような使い方で踏みやすい不具合です。開発サーバーのReactなどのコンポーネントが急に動かなくなった経験があるなら、これが原因かもしれません。

アップグレード前の確認

上げる順番も含めて、確認項目を並べておきます。

  1. Node.jsが22.23.4以上か、Node.js 24系以降かを、手元、CI、ホスティングのそれぞれで確かめる。足りなければ先にNode.jsを上げる
  2. @astrojs/cloudflare と @astrojs/markdoc を使っているなら、Astro本体と同時に上げる。@astrojs/upgrade を使えばまとめて上がる
  3. インテグレーションやカスタムプリレンダラーを自作しているなら、config.image.service の読み方と、setPrerenderer() のメタデータの引き継ぎを確認する
  4. 上げた後にビルドが落ちたら、remark/rehypeプラグインのエラーが見えるようになっただけではないかを疑う
  5. 並列プリレンダリングを試す前に、astro build --timings で今のビルドの内訳を記録しておく

5つの手順を順番に並べると、次のようになります。

Node.jsの確認、関連パッケージの同時更新、自作の拡張の見直し、ビルド失敗の読み解き、--timingsでの記録の5ステップを縦に並べ、安全に上げて速くする場所を数字で決めるゴールへ至る図

要点

参考資料


Share this post:

Next Post
GPT-6 SolとLuna:Astraの改良を半値で。1タスクあたりの費用で読む発表の中身