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

Astro 7.3の新機能:previewの並行起動と拡張ポイントへのロガー対応

Astro 7.3 が2026年9月3日に出ました。前の7.2がビルド時間に効く大きめの変更だったのに対し、7.3は、前のリリースで空いていた穴を埋めるリリースです。地味ですが、当てはまる人にはそれなりに効きます。

一つ目はPlaywrightでE2Eテストを回している人、二つ目は画像サービスやキャッシュプロバイダーを自作している人、三つ目はCloudflare Workersで src/worker.ts を自分で書いている人向けです。このブログのような、アダプターなしの静的サイトには正直どれも関係ありません。どれにも当てはまらないなら、後半の「7.3系で入った修正」だけ読めば十分です。7.2の記事で制限として挙げた並列ビルドの件が外れているので、あれを読んで見送った方は見直してもいいと思います。

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

3つの困りごとと7.3での答えを左右に対比し、下に同時に入った修正をまとめたマップ

Table of contents

Open Table of contents

リリースの中身

マイナーリリースなので、既存のコードを壊す変更はありません。7.3.0で追加された機能は次の3つです。

変更点触る場所ひとことで言うと
プレビューサーバーの並行起動astro preview --ignore-lockロックファイルを無視してもう1台起動する
画像サービスへのロガー各フックの末尾引数 loggerconsole.warn() の代わりに使う
キャッシュプロバイダーへのロガーonRequest()context.logger同上

同じ日に出た @astrojs/cloudflare 14.3.0 には finalize() ヘルパーが入りました。Astro本体の機能ではないですが、リリース記事が一緒に案内しているので、ここでも一緒に見ます。

なお、7.3.0の公開から3時間ほどで7.3.1が出ています。astro:assets を使うプロジェクトが起動もビルドもできなくなる不具合の修正なので、上げるなら最初から7.3.1以降にしてください。

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

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

パッケージマネージャーから直接更新する場合は astro@latest を指定します。Yarn 2以降では yarn upgrade --latest が動かないため、yarn add astro@latest を使ってください。

プレビューサーバーの並行起動

ロックファイルの経緯

Astro 7.0から、astro dev はAIコーディングエージェントから呼ばれたことを検知すると、自動的にバックグラウンドプロセスとして起動するようになりました。エージェントの端末を占有しないためです。このとき、同じプロジェクトでサーバーが二重に立ち上がるのを防ぐために、.astro/dev.json というロックファイルへURL、ポート、PID(プロセスID)を記録します。

このロックは便利な反面、同じプロジェクトで意図的に2台立てたい場面では邪魔になります。そこで7.1で astro dev --ignore-lock が入り、ロックファイルを読みも書きもせずに起動できるようになりました。

7.2では astro preview にも同じロックが入り、.astro/preview.json が作られるようになりました。ところが、逃げ道の --ignore-lockastro dev にしか付いていませんでした。それで、PlaywrightのE2E(End-to-End)テストで設定違いのビルドを複数のプレビューサーバーで同時に検証していた人が、7.2に上げた途端に次のエラーで止まったとIssueを上げています。

Another astro preview server is already running.

  URL:  http://localhost:4321
  PID:  3755

Run `astro preview stop` to stop it, or use `astro preview --force` to replace it.

このメッセージは --force で置き換えられると案内していますが、7.3.1のソースコードを読む限り、置き換え処理はバックグラウンド起動の経路にしかありません。普通の端末で astro preview --force と打っても、既存サーバーの検出で同じエラーになります。置き換えたいなら、astro preview stop で止めてから起動するか、astro preview --background --force を使ってください。AIエージェント検出時は自動でバックグラウンド経路に入るので、そのときだけ --force 単独でも置き換わります。

Issueを上げた人は --force--background も試していますが、Playwrightの webServer はどちらもプロセスの早期終了として扱うので、解決になりませんでした。

ここまでの経緯を、astro devastro preview の2行で並べると次のようになります。

Astro 7.0から7.3までを横軸に、astro devとastro previewの2行でロックファイルの導入と--ignore-lockの追加時期を並べた表

7.3での変更

7.3.0で astro preview にも --ignore-lock が付きました。

astro preview --ignore-lock --port 4322

挙動は astro dev --ignore-lock と同じです。CLIリファレンスに書いてあるのは次の3点。

astro preview実行時に、--ignore-lockの有無とロックファイルの状態で起動かエラーかが分かれる判定の流れ

リリース記事にもあるとおり、短時間だけ使う使い捨てのサーバー向けの機能です。長く動かして stopstatus で面倒を見たいサーバーは、これまでどおり --background で起動してください。

自分なら --port は毎回付けます。Astroの静的プレビューサーバーはViteのプレビューサーバーをそのまま使っていますが、アダプターが提供するプレビューサーバーではポートが埋まっていたときの挙動が変わり得ます。公式ドキュメントの例も --port 4322 を付けています。

AIエージェント環境での注意

落とし穴が一つ。AIエージェントから呼ばれたと判定されると、astro preview は自動的にバックグラウンドモードになります。ソースコードを読む限り、この自動判定も --background を付けたのと同じ扱いなので、--ignore-lock と衝突してエラーになります。エラーメッセージにも、自動検出されたAIエージェント環境とは併用できない旨が出ます。

Claude CodeのようなエージェントからPlaywrightを回す場合は、環境変数でバックグラウンド化を止めてから --ignore-lock を付けます。

ASTRO_PREVIEW_BACKGROUND=0 astro preview --ignore-lock --port 4322

環境変数の書式は、AIツールと組み合わせて開発するガイドにあります。

Playwrightの webServer へ書くなら、次のような形になります。プロジェクトごとに作業ディレクトリとポートを分け、どちらも --ignore-lock を付けます。

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  webServer: [
    {
      command: 'astro preview --ignore-lock --port 4321',
      cwd: './fixtures/site-a',
      url: 'http://localhost:4321',
    },
    {
      command: 'astro preview --ignore-lock --port 4322',
      cwd: './fixtures/site-b',
      url: 'http://localhost:4322',
    },
  ],
});

画像サービスとキャッシュプロバイダーへのロガー

Astroのロガーとは

Astro 7.0で入ったLogger APIは、Astroが出すログの行き先としきい値を設定で決められる仕組みです。astro.config.mjslogger オプションに、組み込みの logHandlers.json()logHandlers.console()logHandlers.node()(既定)、複数をまとめる logHandlers.compose() のいずれか、または自作のロガーを指定します。

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

export default defineConfig({
  logger: logHandlers.json({ level: 'warn' }),
});

ログレベルは errorwarninfo の3段階で、設定したレベル以上のメッセージだけが出ます。さらに、debug まで出す --verbose、何も出さない --silent というCLIフラグもあります。ページのレンダリング中に自分のログを混ぜたいときは、Astro.loggercontext.logger から info()warn()error() を呼びます。この3つのメソッドを持つオブジェクトの型が AstroRuntimeLogger です。

7.1ではエントリポイントを URL で指定できるようになり、7.2ではプロジェクト相対パスも受け付けるようになりました。ロガー周りは、Astroのチームが毎リリース少しずつ手を入れている場所です。

何が困っていたか

ところが、Astroを拡張する側のコードには、このロガーが届いていませんでした。具体的には次の2つです。

対応していない画像形式を素通しするとき、Cookieを設定するレスポンスのキャッシュを飛ばすとき、これらが警告を出す手段は console.warn() しかありませんでした。console.warn() は設定したレベルも出力先も --silent も無視して、端末へ直接書き出します。JSONログを集約基盤へ送っている環境では、そこだけ素のテキストが混ざることになります。

7.2まではconsole.warn()が端末へ直行していたのに対し、7.3からはlogger.warn()がレベル判定を経て設定した出力先へ届く流れの対比

画像サービス側の変更

7.3.0から、画像サービスの全フックが末尾の引数として logger を受け取ります。対象は getURL()parseURL()transform()getHTMLAttributes()getSrcSet()validateOptions()getRemoteSize() です。

import type { LocalImageService } from 'astro';

const service: LocalImageService = {
  // ...
  async transform(inputBuffer, transform, imageConfig, logger) {
    logger.warn(`Could not optimize "${transform.src}". Passing it through unchanged.`);
    return { data: inputBuffer, format: 'png' };
  },
};

既存のサービスは引数が1つ増えただけなので、そのまま動きます。TypeScriptでも、受け取る引数が少ない関数は互換として扱われるため、型エラーにはなりません。

注意が要るのは、フックを自分で呼んでいるコードです。たとえば独自の画像エンドポイントを作り、getConfiguredImageService() で取ったサービスの parseURL()transform() を直接呼んでいる場合は、エンドポイントの context.logger を末尾に渡してください。組み込みのSharpサービスは警告を出すときだけ logger に触るので、渡し忘れても普段は動いてしまい、問題のある画像に当たったときに初めて undefined の呼び出しで落ちます。公式ドキュメントのカスタムエンドポイントの例も、logger を渡す形に直っています。

Sharpサービスが logger 経由で出すようになった警告は、ソースコードを読む限り次の2つです。

キャッシュプロバイダー側の変更

キャッシュプロバイダーは、onRequest() に渡される context オブジェクトへ logger が追加されました。requesturlwaitUntil() と並ぶ形です。

import type { CacheProvider } from 'astro';

const provider: CacheProvider = {
  name: 'my-cache',
  async onRequest({ request, url, logger }, next) {
    logger.warn(`Skipping cache for ${url.pathname} because the response sets a cookie.`);
    return next();
  },
  // ...
};

組み込みの memoryCache() も同じ経路を使うようになりました。ソースコードでは、次の3つの場面で logger.warn() を呼んでいます。

キャッシュが効かなかった理由が、設定したログの出力先へ流れてくるようになります。これまでは見えていませんでした。キャッシュヒット率が想定より低いときの調査が楽になるはずです。

同じPRで、作者はAstro内部に残っていた console 直書きの警告やエラーも、できる範囲で設定済みロガー経由へ置き換えています。

両方の変更点を1枚にまとめておきます。

左に画像サービス、右にキャッシュプロバイダーを置き、loggerを受け取る引数、対象のフックやcontextのプロパティ、組み込みのSharpとmemoryCache()が出す警告を並べた図

Cloudflareのfinalize()ヘルパー

カスタムworkerエントリポイントとは

Astro 7.0で入った高度なルーティングは、Astroのリクエスト処理パイプラインを自分で組み直す仕組みです。既定では、末尾スラッシュの正規化、リダイレクト、セッション、Actions、ミドルウェア、ページのレンダリング、i18n、キャッシュという順で処理されます。src/fetch.tsfetch() メソッドを持つオブジェクトをdefault exportすると、この順番へ自分の処理を差し込んだり、一部を省いたりできます。部品は astro/fetch モジュールから取り出します。リクエストごとの状態を持つ FetchState を先頭で作り、既定のパイプラインを丸ごと走らせる astro() か、middleware()pages() のような個別ハンドラーへ渡します。

Cloudflare Workersでは、これをworkerのエントリポイントそのものとして書けます。wrangler.jsoncmain./src/worker.ts を指定し、Cloudflareの env(バインディング)と ExecutionContext を受け取る fetch() を自分で用意する形です。Durable Objectsやキューと同居させたいときに使います。書き方は2通りあります。

書き方使う関数特徴
丸ごと委譲@astrojs/cloudflare/handlerhandle()既定のエントリポイントと同じ処理を関数1つで呼ぶ
自分で組む@astrojs/cloudflare/fetchcf()astro/fetch静的アセットの判定だけ cf() に任せ、残りは自由に組む

cf()@astrojs/cloudflare 13.6.0で入りました。FetchStateenvExecutionContext を受け取り、静的アセットに当たれば Response を、Astroへ回すべきなら undefined を返す関数です。

何が抜けていたか

問題は、cf() で組んだエントリポイントと handle() の間で、挙動に差があったことです。2026年8月にIssueを立てた人が、同じプロジェクトで main だけを切り替えて比較し、4つの差を報告しています。

項目handle()cf() で組んだ場合
ページが設定したCookie送られる送られない。セッションも毎回新規になる
キャッシュプロバイダー有効時の既定ヘッダーno-store が付く付かない
ルートに一致しないパス静的アセットへフォールバック404ページ
prerender = true のページを含むビルド成功失敗

どれも警告なしに静かに起きるうえ、型チェックも通ります。Cookieが落ちる件は、セッションの保存先であるKVバインディングを cf() が配線していたため、保存はされるのにCookieだけ返らないという、気づきにくい壊れ方でした。no-store の件も地味に危険で、アダプターのソースコードにあるコメントによれば、CloudflareのWorkerキャッシュは既定でGETレスポンスを最長2時間キャッシュします。キャッシュの意図を書いていないルートが、知らないうちに共有キャッシュへ乗ることになります。Issueを立てた人の分析では、cf() が13.6.0で書かれた後に handle() 側へ入った改修が、cf() 側には入っていなかったのが原因だそうです。

14.3.0での変更

@astrojs/cloudflare 14.3.0で、これらがまとめて直っています。表の上2つ、CookieとCDNキャッシュの既定値は、新しい finalize() ヘルパーで付けます。Astroのパイプラインが返した Response と、同じ FetchState を渡します。

// src/worker.ts
import { astro, FetchState } from 'astro/fetch';
import { cf, finalize } from '@astrojs/cloudflare/fetch';

export default {
  async fetch(request: Request, env: Env, context: ExecutionContext) {
    const state = new FetchState(request);
    const asset = await cf(state, env, context);
    if (asset) return asset;

    return finalize(state, await astro(state));
  },
};

ブラウザからのリクエストをworkerが受け、cf()で静的アセットを判定し、Astroのパイプラインで描画した結果にfinalize()でCookieとキャッシュヘッダーを付けて返すまでのシーケンス

表の下2つ、静的アセットへのフォールバックとworkerdでのプリレンダリングは、アダプター側の内部変更で直っていて、finalize() を呼ばなくても効きます。アダプターのCHANGELOGには、ルートに一致しないときは静的アセットへフォールバックし、workerd経由のプリレンダリングでは既定のサーバーエントリポイントを使う、とあります。

どの抜けがどちらで直ったかは、この対応表のとおりです。

cf()で組んだ自前エントリポイントに抜けていたCookie、CDNキャッシュの既定値、静的アセットへのフォールバック、プリレンダリングの4つを縦に並べ、右側にfinalize()で直すかアダプター内部で直るかを示した図

Honoを使っている場合は、@astrojs/cloudflare/honocf() ミドルウェアが、後続のハンドラーの実行後に自動で同じ仕上げをします。finalize() を呼ぶ必要はありません。

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

const app = new Hono<{ Bindings: Env }>();

app.use(cf());
app.use(actions());
app.use(middleware());
app.use(pages());
app.use(i18n());

export default app;

cf() で組んだエントリポイントを使っていて、finalize() を足さずに14.3.0へ上げた場合、CookieとCDNの既定ヘッダーは付かないままです。上げたら忘れずに1行足してください。逆に、handle() に委譲しているプロジェクトや、そもそもカスタムエントリポイントを使っていないプロジェクトには、関係のない変更です。

7.3系で入った修正

CHANGELOGから、実用上影響のある修正を拾っておきます。

インクリメンタルビルドと並列ビルドの両立

7.2の記事で、実験的機能のインクリメンタルスタティックビルドは build.concurrency が1より大きいと無効になる、と書きました。7.3.0でこの制限が外れています。並列レンダリング中も、ページごとに独立したストアでコンテンツエントリと画像変換の依存を記録するようになったためです。キャッシュを効かせるために build.concurrency: 1 へ落としていたプロジェクトは、その回避策を消せます。@astrojs/cloudflare のworkerd経由プリレンダリングでも同じ仕組みが入り、大きなプリレンダリング済みページのシリアライズ負荷も減っています。

並列ビルド時の挙動を7.2までと比べると、こうなります。

左に7.2まで、右に7.3からを置き、build.concurrencyを1より大きくしたときに、7.2では4ページすべてを再生成していたのに対し、7.3では変わった1ページだけを再生成する違いを示した図

なお、2026年9月5日時点の公式ドキュメントの制限一覧には、まだ build.concurrency の制限が書かれています。PRの説明に、この制限はビルド時の警告でしか表に出ていなかったのでドキュメント更新は不要、とあるので、自分は古い記述が残っているだけだと見ています。ただし実際に試すときは、自分のビルドログで警告が消えたことを確かめてください。

memoryCache() は、レスポンスの Vary ヘッダーに挙がっているリクエストヘッダーの値ごとに、別のキャッシュエントリを作ります。ドキュメントによると、これまで Cookie はユーザーごとに違いすぎて実質キャッシュできないという理由で、キーを作るときに無視されていました。しかしそれでは、Cookieの内容で中身が変わるレスポンスが、Cookieを無視したキーで他のユーザーへ配られかねません。7.3.0からは Vary: CookieVary: * を含むレスポンスは保存自体を飛ばし、前述のとおり logger.warn() で理由を出します。

そのほかの修正

アップグレード前に確認したいこと

  1. まずは npx @astrojs/upgrade で7.3.1以降へ上げ、既存のビルドが通ることを確認する。新機能はどれもオプトインなので、ここまでは何も変わらない
  2. Playwrightなどで複数のプレビューサーバーを立てているなら、webServercommand--ignore-lock と明示的な --port を付ける。AIエージェントから実行する場合は ASTRO_PREVIEW_BACKGROUND=0 も添える
  3. 画像サービスやキャッシュプロバイダーを自作しているなら、console.warn()logger.warn() へ置き換える。画像サービスのフックを直接呼んでいるコードがあれば、logger を末尾へ渡す
  4. Cloudflareで src/worker.tscf() で組んでいるなら、@astrojs/cloudflare を14.3.0以降へ上げ、astro(state) の戻り値を finalize(state, ...) で包む。Honoなら cf() ミドルウェアが入っているかを確認するだけでよい
  5. インクリメンタルビルドのために build.concurrency: 1 へ落としていたなら、元の値へ戻して2回続けてビルドし、2回目でキャッシュが効いていることをログで確認する

この5つを上から順に見ていけば、自分に関係のあるものだけ拾えます。

本体を上げる、複数previewの並行起動、拡張をloggerへ切り替え、Cloudflareの自前workerの仕上げ、並列ビルドの回避策を外す、の5ステップを上から順に並べ、各ステップが誰に関係するかをチップで示した図

要点

参考資料


Share this post:

Previous Post
GPT-6 Astra:初めてCriticalに達したモデルの能力、料金、監視しにくくなった推論
Next Post
Gemini 3.8 Flashと3.8 Flash Cyber:3週間ごとの更新と、Googleも始めた選別配布