← ishizakahiroshi.com
設計判断 2026-09-12

Cloudflare Pages の SPA フォールバックが 308 になる。直しても既存ブラウザには届かない

淡い青灰色の道に立つ道標。矢印が来た方の門へ向かって折れ曲がり、道が自分自身へ戻っているヘッダー画像

リリースから 1 か月が経ったので、アクセス解析でも眺めようと思っただけの夜でした。ついでに叩いた /map が 308 を返してきて、そこから 1 か月放置されていた壊れ方が 2 つ出てきます。どちらも画面は動いていて、誰も困っていませんでした。そして直したあとに、サーバーからは手の出せない落とし穴がもう 1 つ残ります。

Qiita で読む(技術版)→ note で読む(一般向け)→ manabi-map リポジトリ →
v0.9.3 で直した 3 つと、残った落とし穴をまとめた要約図

直したのは 3 つ。そのうえで「直したのに直っていない人がいる」が残りました。

設定に書いた 8 行が、1 行も効いていなかった

Cloudflare Pages で SPA のフォールバックを書く定番は _redirects です。そこに /map /index.html 200 のような行を 8 本並べてありました。存在しない URL まで 200 で返す全部入りのワイルドカードは、ソフト 404 の原因になるので避けて、ルートを列挙する形にしていたものです。

実測したら、静的な 6 行はすべて 308 でトップへ、ワイルドカードの 2 行は 404 でした。8 行が、書き方 2 種類で別々に壊れていたことになります。

_redirects に書いた意図と、実際に返っていたステータスの対比表

原因は左ではなく右側。rewrite 先の /index.html が / へ正規化され、3 列目の 200 が効かなくなっていました。

この正規化は Pages Functions の中でも起きます。env.ASSETS.fetch("/index.html") も 308 を返すので、シェルを取るときは / を指定する必要がありました。同じ仕様に、外側と内側で 2 回ぶつかったことになります。

直したのに、直っていない人がいる

Pages Functions へ列挙を移して 200 を返すようにし、本番の検収も 7 項目すべて期待どおりでした。そのあと本番で自分で開いたら、トップページが出ました。

調べると、サーバーは 200 を返していました。ブラウザが聞きに来ていなかったのです。308 は恒久的なリダイレクトなので既定でキャッシュ可能で、一度踏んだブラウザはその記憶だけでトップへ飛ばします。仕様どおりの正しい動作です。

修正前・修正後・キャッシュを持っているブラウザの 3 経路を比較した図

3 本目だけ通信そのものが発生しません。実測したリダイレクトは 2.3 ミリ秒でした。

気づけなかった理由も書いておきます。検収でタイル枚数を測るときに ?probe=1 というクエリを付けていました。リダイレクトのキャッシュは URL 単位なので、クエリを足した時点で古い記憶を外していたのです。curl もキャッシュを持たないので同じ理由で気づけません。

扉にたどり着けず、床の足跡が輪を描いている様子のイラスト

恒久リダイレクトを廃止する修正は、修正前の URL を、修正前から使っているブラウザで開くところまでが検収です。

ついでに減らした、一瞬しか見えない地図

同じリリースで通信量も減らしました。地図を開くと全国表示を描いてから通学圏へ寄せていたので、ほぼ見られないタイルを毎回 20 枚ほど取っていたのです。前回その端末で寄せたときの縮尺を覚えて、最初からその範囲で開くようにしました。

地図を開いたときに読み込むタイルの内訳を、変更前と変更後で比較した図

本番実測で 38 枚から 18 枚。地図に出る学校は 1 校も減っていません。

記事に書いたこと

Qiita 版には、middleware の実装と「200 にしてはいけないもの」を押さえるテスト、_headers のセキュリティヘッダが env.ASSETS.fetch() の応答に引き継がれるかの実測、CSP 違反 0 件を対照実験つきで確かめる方法、通信量の回帰を CI で止めるバジェット検査を入れています。note 版は同じ出来事を、使う側から見た話として書きました。