Astro の routeRules から外してもページがキャッシュされる
エッジキャッシュを入れるとき(Cloudflare は既定では HTML をキャッシュしないので、Astro 側で指定しています。Cloudflare で HTML がキャッシュされないのは既定の仕様)、問い合わせフォームのページだけは外したいと考えました。routeRules に列挙しなければキャッシュの対象にならないと理解していたので、そのページを書かずにデプロイしました。
本番で確認したら、HIT が返ってきました。
まず実測
routeRules にはトップと固定ページ 3 枚だけを書き、/contact は意図的に書いていません。
$ curl -sI https://example.com/contact
cf-cache-status: HIT
age: 66age: 66 は、66 秒前に保存されたレスポンスをそのまま返しているという意味です。ルールに書いていないページが、書いたページと同じようにキャッシュされていました。
「有効かどうか」は 2 つの条件のどちらかで決まる
Astro 7 のキャッシュは、リクエストごとに「このレスポンスはキャッシュ対象か」を判定します。判定しているのは astro/dist/core/cache/runtime/cache.js で、条件はこうなっています。
maxAgeが設定されている- または、タグが 1 つでも積まれている
routeRules は 1 つ目の maxAge を設定する仕組みで、ルールに書かなければ maxAge は設定されません。しかし 2 つ目のタグの条件はルールと関係なく残ります。
CMS からコンテンツを引くページには、たいてい次の 1 行が入っています。
const { entry: page, cacheHint } = await getEmDashEntry("pages", "contact");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);この cacheHint の中身を見ると、こうなっていました。
{ "tags": ["01KRTTRS7F1ATKX9P60SBQS7MB"], "lastModified": "2026-08-07T12:34:56.000Z" }中身はタグと更新日時だけで、`maxAge` は入っていません。 タグはコンテンツを更新したときに該当ページのキャッシュを消すためのもので、保持時間は指定しません。
しかし判定は「maxAge または タグ」なので、タグだけでもキャッシュは有効になり、アダプターは次のヘッダーを付けます。
Cloudflare-CDN-Cache-Control: publicmax-age が付いていないので、保持時間を指定しないまま「キャッシュしてよい」とだけ宣言した状態です。
ルールに書くより悪い
routeRules に書いていれば、ヘッダーはこうなります。
Cloudflare-CDN-Cache-Control: public, max-age=300, stale-while-revalidate=864005 分で期限が切れ、次のアクセスで作り直されます。
一方、タグだけで有効になった場合は max-age が無いので、保持時間は CDN の既定に委ねられます。外したつもりのページが明示的にキャッシュしたページより長く残ることになります。
同じ構成でも外れているページがある
紛らわしいのは、同じ仕組みの別サイトでは検索ページがきちんと外れていたことです。
$ curl -sI https://example.net/search?q=test
cf-cache-status: BYPASSBYPASS は「キャッシュの対象にしない」という状態です。こちらも routeRules には書いていません。
違いはページの中身で、検索ページは CMS のコンテンツを 1 件ずつ引くのではなく検索 API を叩いているので、`Astro.cache.set()` をそもそも呼んでいません。呼んでいなければタグは積まれず、判定は「対象外」になり、アダプターが no-store を付けます。
つまり外れていた理由は「ルールを書かなかったから」ではなく「そのページが cacheHint を拾っていなかったから」でした。私はこの 1 例を見て「ルールを書かなければ外れる」と一般化し、間違えました。外れているように見えるページがあっても、外れている理由はルール以外にあるかもしれません。
外すならページ側で宣言する
routeRules の型には「キャッシュしない」を表す値がありません。指定できるのは maxAge と swr とタグだけです。
確実に外すには、ページ側でこう書きます。
if (Astro.cache?.enabled) Astro.cache.set(false);set(false) はキャッシュを無効にしてキャッシュ用のヘッダーを付けないようにするので、アダプターが既定の Cloudflare-CDN-Cache-Control: no-store を付けます。
デプロイして確認しました。
$ curl -sI https://example.com/contact
cf-cache-status: BYPASS他のページのキャッシュはそのままです。
maxAge: 0 は「保存しない」ではない
外す方法として maxAge: 0 を試した記録も残しておきます。これは保存はするが毎回オリジンに再検証するという意味です。実測ではこうなります。
1回目 cf-cache-status: MISS
2回目 cf-cache-status: EXPIRED
3回目 cf-cache-status: EXPIRED古い内容が返ることはないので、内容を新しく保つ目的なら足ります。ただし毎回サーバーまで届くので、サーバーの実行を減らす効果はありません。CPU の消費を減らしたくて外すなら、maxAge: 0 では足りません(CPU の上限については Cloudflare Workers 無料プランで 503 が出る原因は CPU 上限)。
直した後にも少し残る
修正をデプロイしても、修正前に保存されたレスポンスはしばらく残ります。私の場合、6 回試して 1 回だけ HIT / age: 424 が返りました。地域ごとに保存場所が分かれているためです。
保持時間を指定しないまま保存されたものなので、CDN の既定の保持時間が過ぎて消えるのを待つことになります。すぐ消したいなら、管理画面からそのページを公開し直すのが確実です。コンテンツを更新すると CMS がタグ経由で該当ページのキャッシュを消すので、公開し直すことでこのページのキャッシュも消せます(purge はキャッシュを消すだけで、作り直すのは次のアクセス時です。Cloudflare で purge しても age が伸び続けるのは壊れていない)。
修正後は新しく保存されることはないので、影響は「しばらくの間、一部の地域で古い問い合わせページが返り得る」ことだけです。フォームの構造自体は変えていないので、送信は問題なく動きます。
ページ側で外し、ヘッダーで確かめる
- `routeRules` はキャッシュを有効にする設定で、キャッシュから外す設定には使えない
- 外すのはページ側の `Astro.cache.set(false)`
- CMS のキャッシュヒントは保持時間を持たないことがある。タグだけでもキャッシュは有効になる
- ページごとに
cf-cache-statusを実際に確認する。ルールの書き方だけを見て判断しない
私はデプロイ後に 1 パスずつヘッダーを確認していて、この設定と実際のキャッシュの食い違いを見つけました。設定ファイルを読み直すだけでは絶対に見つからない種類の食い違いです。