💡 Tips

Cloudflare R2のカスタムドメインで上書きしてもファイルが更新されない

Cloudflare R2 のカスタムドメインでファイルを上書きアップロードしたのに、ブラウザでは古いファイルが返ってくる——。R2 オリジンは新しいのに、だ。結論から言うと、cache-control: immutable を付けた配信は CDN(とブラウザ)が最大1年キャッシュを握り続けるため、同名上書きでは更新が反映されない。原因の切り分けとパージ手順、そして「そもそも踏まない設計」までまとめる。

何が起きているか

R2 のカスタムドメイン(例 dl.example.com)は Cloudflare CDN を経由する。レスポンスに

cache-control: public, max-age=31536000, immutable

を付けると、エッジに最大1年キャッシュされる。この状態で同名ファイルを上書きアップロードしても、CDN は HIT のまま古い実体を配り続ける。R2 オリジンだけが新しくなり、ユーザーには届かない。

まず切り分ける: オリジンは更新されているか

CDN を経由せず、R2 オリジンの実体を直接取って確認する。

wrangler r2 object get <bucket>/<key> --file /tmp/check --remote
md5 /tmp/check   # アップロードしたファイルと一致するか

オリジンが新しければ、問題は CDN キャッシュだと確定できる。

パージする

Cloudflare ダッシュボードの Caching → Configuration → Purge Cache → Custom Purge → By URL で対象URLを指定する。API なら zone:cache_purge 権限のトークンで:

curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"files":["https://dl.example.com/app.zip"]}'

検証は次のとおり。cf-cache-statusMISS になり、etag が入れ替わっていれば成功だ。

curl -sI https://dl.example.com/app.zip | grep -iE 'cf-cache-status|etag'

盲点: ブラウザキャッシュは CDN パージでは消えない

ここが一番ハマるところ。immutable は CDN だけでなくブラウザにも効く。そのため、一度ダウンロード済みのブラウザは、CDN をパージしてもローカルキャッシュから古い版を出し続ける

curl では正しい版が取れるのに、ブラウザのダウンロードだけ壊れた版が来る——という食い違いで顕在化する。curl 検証だけでは見抜けない。

応急処置として、URL にクエリを足す(app.zip?v=2)とキャッシュキーが変わってバイパスできる。

そもそも踏まない設計

王道は、バージョン付きファイル名にして同名上書きを避けることだ。

app-1.2.0.zip   # 中身は二度と変えない
app-1.2.1.zip   # 変更は新ファイルで

immutable を使うなら「中身は二度と変えない」が大前提。バージョン据え置きで差し替えると、CDN にもブラウザにもこの沼が残る。

まとめ

  • immutable キャッシュは同名上書きでは更新されない(CDN が古い実体を保持)
  • 切り分けは wrangler r2 object get --remote でオリジンを直接確認
  • パージは By URL か zone:cache_purge API。cf-cache-status: MISS で確認
  • ブラウザキャッシュは CDN パージで消えない。クエリ付与で回避
  • 本来はバージョン付きファイル名で同名上書きを避けるのが正解