リリースから 1 か月が経ったので、アクセス解析でも眺めようと思っただけの夜でした。ついでに叩いた /map が 308 を返してきて、そこから 1 か月放置されていた壊れ方が 2 つ出てきます。どちらも画面は動いていて、誰も困っていませんでした。そして直したあとに、サーバーからは手の出せない落とし穴がもう 1 つ残ります。
直したのは 3 つ。そのうえで「直したのに直っていない人がいる」が残りました。
Cloudflare Pages で SPA のフォールバックを書く定番は _redirects です。そこに /map /index.html 200 のような行を 8 本並べてありました。存在しない URL まで 200 で返す全部入りのワイルドカードは、ソフト 404 の原因になるので避けて、ルートを列挙する形にしていたものです。
実測したら、静的な 6 行はすべて 308 でトップへ、ワイルドカードの 2 行は 404 でした。8 行が、書き方 2 種類で別々に壊れていたことになります。
原因は左ではなく右側。rewrite 先の /index.html が / へ正規化され、3 列目の 200 が効かなくなっていました。
env.ASSETS.fetch("/index.html") も 308 を返すので、シェルを取るときは / を指定する必要がありました。同じ仕様に、外側と内側で 2 回ぶつかったことになります。
Pages Functions へ列挙を移して 200 を返すようにし、本番の検収も 7 項目すべて期待どおりでした。そのあと本番で自分で開いたら、トップページが出ました。
調べると、サーバーは 200 を返していました。ブラウザが聞きに来ていなかったのです。308 は恒久的なリダイレクトなので既定でキャッシュ可能で、一度踏んだブラウザはその記憶だけでトップへ飛ばします。仕様どおりの正しい動作です。
3 本目だけ通信そのものが発生しません。実測したリダイレクトは 2.3 ミリ秒でした。
気づけなかった理由も書いておきます。検収でタイル枚数を測るときに ?probe=1 というクエリを付けていました。リダイレクトのキャッシュは URL 単位なので、クエリを足した時点で古い記憶を外していたのです。curl もキャッシュを持たないので同じ理由で気づけません。
恒久リダイレクトを廃止する修正は、修正前の URL を、修正前から使っているブラウザで開くところまでが検収です。
同じリリースで通信量も減らしました。地図を開くと全国表示を描いてから通学圏へ寄せていたので、ほぼ見られないタイルを毎回 20 枚ほど取っていたのです。前回その端末で寄せたときの縮尺を覚えて、最初からその範囲で開くようにしました。
本番実測で 38 枚から 18 枚。地図に出る学校は 1 校も減っていません。
Qiita 版には、middleware の実装と「200 にしてはいけないもの」を押さえるテスト、_headers のセキュリティヘッダが env.ASSETS.fetch() の応答に引き継がれるかの実測、CSP 違反 0 件を対照実験つきで確かめる方法、通信量の回帰を CI で止めるバジェット検査を入れています。note 版は同じ出来事を、使う側から見た話として書きました。