Cloudflareで静的アセットをS3 Originで配布したい
JSとかCSS、画像などの静的ファイルはS3に向けて、それ以外はアプリケーションサーバーにルーティングしたいというよくあるケース。CloudFrontとかFastlyではよくやるパターンなのでCloudflareでもサクっとできるだろう思ってたけど意外と難しかったのでメモっておく。
まず前提として、Cloudflareではパスベースのルーティングがエンタープライズプランでしか使えない。
なので、example.com/_next/*
はS3に、それ以外のパスはアプリケーションサーバーにルーティングするみたいなことがエンタープライズプラン以外では素直には設定できない。せめてProプランくらいでできるようになると嬉しいんだけどまあできないものは仕方ない。
追記: Load Balancingの機能を使うと実現できそうというのをCloudflareの人に教えてもらった。試してないけど、月$5からなので現実的な選択肢になるかも。
いくつか方法はありそうだけど、
- サブドメインを分けてS3に向ける
- Cloudflare Workersを使う
のどちらかがよさそう。
サブドメインを分けてS3に向ける
パスベースでなく、assets.example.com
みたいなサブドメインをS3にCNAMEで向ける方法。これであればフリープランからいける。CloudflareのCNAMEはCNAMEといいつつ、CloudflareのCDNを通してターゲットにProxyするというものなので、エッジでのキャッシュもちゃんと効く。
注意しないといけないのは、CNAMEでS3を参照する場合、バケット名をドメイン名と合わせる必要があるということ。
この場合バケット名をassets.example.com
にしてStatic Website Hostingを有効にしておく。
なお、先述したようにCloudflareのCNAMEは実質Proxyのようなものなので、CloudflareのPage RuleでHostヘッダを${buekct}.s3.${region}.amazonaws.com
のような値に書き換えてやればバケット名はなんでもよくなるけど、CloudflareでHostヘッダを上書きするのはこれまたエンタープライズプライのみなので諦めた。
Cloudflare Workersを使う
基本的には上記のサブドメインの方法でいいと思うけど、すでにあるバケットを使いたくて名前をドメイン名に合わせたものに変えたくないとか、サブドメインじゃなくて絶対にパスベースでルーティングしたいみたいなケースではCloudflare Workersを使うとよさそう。
WorkerのコードはただProxyするだけのシンプルなもの。
const endpoint =
"https://${bucket}.s3.ap-northeast-1.amazonaws.com";
export default {
async fetch(request: Request): Promise<Response> {
const { url } = request;
const { pathname } = new URL(url);
const newUrl = `${endpoint}${pathname}`;
const newRequest = new Request(newUrl, {
body: request.body,
headers: request.headers,
method: request.method,
redirect: request.redirect,
});
return fetch(newRequest);
},
};
そしてexample.com/static/*
のようなパスをこのWorkerに向ける。これでキャッシュもしてくれるので便利。
$ curl -s -I https://s3-assets-test.hokaccha.dev/static/app.js | grep cache-status
cf-cache-status: HIT
単にProxyするだけでなく細かい制御もできるのでそういうのがやりたいケースでもWorkerはよさそう。ただしWorkerはフリープランだと10万リクエスト/dayというかなり多めとはいえ上限があるのでリクエスト数によっては課金が必要そう。
Workersを使うならそもそもS3じゃなくてCloudflare R2を使うというのもありそうだけど、それはまた別途検証してみようと思う。
Discussion