🎂

静的ファイル配信を対応したCloudflare WorkersにRemixを移行してログ出力を容易にする方法

2024/09/27に公開

昨日のCloudflareからWorkersのアップデートの中にこういうものが入っていました。

https://blog.cloudflare.com/builder-day-2024-announcements/#static-asset-hosting

https://developers.cloudflare.com/workers/frameworks/

Run front-end websites — static or dynamic — directly on Cloudflare’s global network.

超意訳)静的もしくは動的なサイトをCloudflare Workersで配信できるようにします。

は?って感じですよね。静的ファイルの配信といったら、今まではCloudflare PagesかR2などを使用して配信するってのが普通でしたが、Cloudflare Workersから配信する?つまり、プログラム(JS,TS,WASM)以外の静的ファイルをCloudflare Workersのデプロイに含めることが出来るようになったということらしいのです。なのでそのアップデートを受けてRemixをCloudflare WorkersでデプロイしてCloudflare Workersが使える全機能の活用方法を考えてみたっていうのが本記事の本題です。

Remixで見る今までとの違い

まずRemixでの使用方法はCloudflareのドキュメントで記載されています。

https://developers.cloudflare.com/workers/frameworks/framework-guides/remix/

このコマンド通りに実行してみて、Remixの公式テンプレートとの違いを見ていきます。
ちなみにCloudflareのドキュメント通りのコマンドを実行するとRemixのCloudflare Pagesのテンプレートに近かったので、そちらと比較します(RemixはそもそもRemixからCloudflare Workersで動作するテンプレートも存在しますが、一旦それは無視です。既にややこしい。)

まず、ファイルの違いは以下です。

.assetsignore なんという見たこともないファイルが存在します。後で調べるとして、大事なのは package.json の違いなので、そこを見ていきます。

wrangler のバージョン違いは新しくないと対応してないということでそうなんでしょう。何よりこの違いで大事そうなのは builddeploy といったスクリプト部分です。(Cloudflare公式のコマンドで落としたRemixはちょっと古いのは見なかったことにしましょう)

build の違い

まずはビルドです。RemixのCloudflare Pagesのビルドは純粋にviteでビルドしているだけでしたが、Cloudflare Workersの静的ファイル対応版では更に wrangler pages functions build でビルドしています。

このコマンドは以前から存在していたのですが、何が違うかというと説明するより見た方が早いので、結果を見せます。

Remixは通常は build/clientbuild/server しか出力しませんが、今回は build/workers が増えています。これは何かというとCloudflare Pages Functionsで動作するためのWorkersのバンドルファイルなのです。本来は wrangler pages deploy をすると裏で wrangler pages functions build が動いて functions/[[path]].ts をビルドして build/workers/index.js のようなファイルを出力してそれをデプロイしていたのですが、これがただビルドされて出力されただけになります。要はCloudflare Pages FunctionsもCloudflare Workersなのでこのファイルをデプロイしようってことになると思っています。

deploy の違い

今まではCloudflare PagesにデプロイしていたのものをCloudflare Workersにデプロイするのでそのコマンドも変わっています。

このデプロイを実行すると何がデプロイされるのか見ていきます。

このように build/client のファイルが一緒にデプロイされたようです。では build/client のファイルや build/workers がデプロイされるというのはどこに記載されているかというと wrangler.toml にその設定が存在します。

Cloudflare Workersとなるファイルを main に指定していました。また今まで wrangler.toml にはなかった assets という設定も存在し、静的ファイルも指定できるようになっています。どうやらこの設定でCloudflare Workersへ静的ファイルのアップロードも指定できるようになっており一緒にデプロイできるようです。

ここで少し前に出てきた .assetsignore の出番のようです。 wrangler.tomlassets にはディレクトリを指定いますので、そのディレクトリ内のファイルをデプロイするようです。しかし、中にはデプロイする必要もないファイルがある場合は .assetsignore で除外するようになっているようです。試しにRemixのロゴのファイルを除外してデプロイする設定を書いてみます。

この状態でデプロイしたページを開いてみると

このようにロゴの画像はデプロイ対象から除外したので表示されていません。もし不必要なファイルがある場合は .assetsignore に指定するとよいでしょう。そもそも _headers というファイルはCloudflare Pages専用の設定ファイルであり、Cloudflare Workersでは動作しないので消す必要があるものをこのように設定しているのはニュアンスを読み取ってほしいのでしょうか。

Cloudflare Workersはどうなってる

では実際にデプロイできたということで今度はCloudflare Workers側ではどうなっているか見ていきます。

静的ファイルの配信でWorkersは動作しない(動作カウントに入らない)

まず気になるのは静的ファイルの配信でWorkersは起動するのか?ということです。Remixの初期テンプレートの状態でデプロイされたCloudflare WorkersのURLにアクセスしてみます。

こんな感じでレスポンスが返ってきました。もちろん静的ファイルも返ってきています。このアクセスをした状態でCloudflareのコンソールでログを見てみます。

Remixへのhttpリクエストのみがログとして出力されました。静的ファイルのログは出力されていないよなのでCloudflare Workersの動作としては対象外として扱われているようです。
公式のドキュメントにも記載されていますが、静的ファイルへのアクセスはWorkersの動作範囲外で無料で配信してくれるそうです。本当に太っ腹ですね。

https://developers.cloudflare.com/workers/static-assets/#pricing

Cloudflare PagesじゃなくてCloudflare Workersで動作すると何が嬉しいの?

たぶん、1番気になるのはここかなと思っています。公式からWorkersとPagesの違いがドキュメントに纏められています。

https://developers.cloudflare.com/workers/static-assets/compatibility-matrix/

Pagesにあって、Workersに無いのは以下です。

  • middleware
  • redirect

逆にWorkersにあって、Pagesに無いのは相当数が存在します。
正直今まではPagesに無いものが多いのはわかってはいたのですが、そこまで気にはしてなかったです。それはなぜかというと静的ファイルの配信がCloudflare Pagesの方が楽だったからです。逆にそのアドバンテージが無くなってしまうとPages専用のmiddlewareなどをどうにかすればPagesを使う理由が無くなってきます。

ログの出力はCloudflare Workersに軍配

このCloudflare Workersの静的ファイルの配信と同時に以下の発表がされました。

https://blog.cloudflare.com/builder-day-2024-announcements/#logs-for-every-worker

そうです。やっとCloudflare Workersのログがコンソール上で表示できるようになったのです。しかしこのログの対応はどうやらCloudflare Workersだけであり、Cloudflare Pagesは未対応のようです。なのでこれを利用するにはCloudflare Workersで動作させる必要があります。

今回はサッと wrangler.toml に以下の設定だけ入れてデプロイしてみてログが出るのか確かめてみました。

wrangler.toml
#:schema node_modules/wrangler/config-schema.json
name = "my-remix-app"
compatibility_date = "2024-09-25"
main = "./build/worker/index.js"
assets = { directory = "./build/client" }

# Add
[observability]
enabled = true

するとこんな感じに出力されました。

長年要望してたのでちょっと感動しました。Baselimeを買収した成果がすぐ出て嬉しいですね。(レスポンスタイムが出てたら満点だった)
Cloudflare WorkersおよびPagesの最大の弱点がこのログだったので、これが利用できるという点でCloudflare Workers側を利用するというアドバンテージはかなりのものがあると思います。ただし、ドキュメントを見る限り有料版のWorkersでもログの保存期間が7日間しかないのでそこはもう少し頑張ってほしいかなと思っています。

middlewareの構築方法

Cloudflare Pagesで使用できていたmiddleware(functions/_middleware.ts)はCloudflare Workers版でどうするか移行方法が大きく2つあります。

そのまま使用する

Cloudflare Pages Functionsをbuildしてバンドルファイルを出力したものをCloudflare Workersにデプロイするというのがデプロイの流れで説明したと思います。
なのでCloudflare Pages Functionsをビルドするコマンド(wrangler pages functions build)はCloudflare Pagesで使用する _middleware.ts もバンドルされてビルドされます。なので実質Cloudflare WorkersでもCloudflare Pagesと同様にmiddlewareは使えるということになるのです。

middlewareをService Bindingで構築する

Pagesはmiddlewareが使えるのは非常に便利だったのですが、Cloudflare Workersも同等のことをやろうと思えば出来ます。それがService Bindingsです。仮にこのRemixの前にCloudflare Workersを1つ用意して、そのCloudflare WorkersからRemixのCloudflare Workersを呼べるようにすれば前段のCloudflare Workersはmiddlewareになりませんか?仮実装するとして

src/index.ts
export default {
  async fetch(request, env): Promise<Response> {
    const now = performance.now();
    const response = await env.REMIX_APP.fetch(request, request);
    console.log(`status: ${response.status} in ${performance.now() - now}ms`);

    return response;
  },
} satisfies ExportedHandler<Env>;
wrangler.toml
#:schema node_modules/wrangler/config-schema.json
name = "my-remix-app-middleware"
main = "src/index.ts"
compatibility_date = "2024-09-25"
compatibility_flags = ["nodejs_compat"]

# Add
services = [
  { binding = "REMIX_APP", service = "my-remix-app" }
]

このようにオリジンとなるRemixのレスポンスタイムを測るログを仕込むとします。
Workersをmiddlewareとしてデプロイするには何通りかの方法が考えられます。

  1. routesを設定してmiddlewareをRemixのCloudflare Workersに処理に割り込ませる
  2. このmiddlewareをアクセスを受けるメインのドメインに設定して、RemixのCloudflare Workersは露出しない

文字で書いてわかりにくいので図にするとこんな感じです。

前者はRemixがアクセスされるメインのエンドポイント(ドメイン)に設定されますが、routesの設定により特定のルーティングのみmiddlewareが動くという状態です。
逆に後者はmiddlewareがアクセスされるメインのエンドポイント(ドメイン)になり、RemixはService Bindingsで繋いで裏に隠している状態です。

ちなみにどちらかがいいかというとオススメは前者です

後者には結構辛い欠点がいくつかあります。

  • middlewareとなるCloudflare Workersがすべて処理を受けているのでmiddlewareが不必要なパスも動作してしまう
  • 静的ファイルの配信をmiddlewareが受けないのようにするための設定が若干面倒であり、今回のCloudflare Workersから静的ファイルの配信を行えるメリットが失われる

詳しく説明はしませんが、特に静的ファイルの配信をmiddlewareから除外するのが非常に面倒です。なのでこれがあるのでroutesによるmiddlewareの設定をオススメします。

ではmiddlewareとして使用する設定を以下に説明していきます。

Remixにカスタムドメインを割り当てる

Remixアプリケーションが動作するために .workers.dev ドメインで動作させるのではなく、ちゃんと自分のドメインで動作させるためのドメインを割り当てます。

wrangler.toml
#:schema node_modules/wrangler/config-schema.json
name = "my-remix-app"
compatibility_date = "2024-09-25"
main = "./build/worker/index.js"
assets = { directory = "./build/client" }
route = { pattern = "my-remix-app.chimame.dev", custom_domain = true } # <- Add

[observability]
enabled = true

この例では私が持っているドメインにサブドメインに割り当てました。

middlewareとなるCloudflare Workersにroutesを設定する
wrangler.toml
#:schema node_modules/wrangler/config-schema.json
name = "my-remix-app-middleware"
main = "src/index.ts"
compatibility_date = "2024-09-25"
compatibility_flags = ["nodejs_compat"]

services = [
  { binding = "REMIX_APP", service = "my-remix-app" }
]

# ***Add***
routes = [
  { pattern = "my-remix-app.chimame.dev/*", zone_name = "chimame.dev" }
]

[observability]
enabled = true

routesの設定によりこのmiddlewareはすべてのパスで動作するという設定になりました。もし仮に特定のパスだけしか動作しなくていいという場合は pattern の指定を変えるとよいです。しかしすべてパスで動作すると静的ファイルの配信もこのmiddlewareとなるWorkersが動作してしまいます。
なので静的ファイルのパスだけは除外する必要があります。
除外するにはCloudflareのコンソールから「トップページ」→「アカウント選択」→「ドメイン選択」→「Workersルート」と開いていくと設定できる画面があります。そこで以下のように先程のmiddlewareで設定したroutes以外にworkersを動作しないパスを設定します

Remixは基本的に /assets というパスから静的ファイルの配信を行います。また public ディレクトリに配置したファイルも静的ファイルとして配信されます。なのでその2つを除外する設定を入れています。Remixのテンプレートのままなので本来は logo-light.pnglogo-dark.png などの public ディレクトリに入れて配信するのは少ないと思いますが、このように除外する必要があるのを忘れないでください。

こうすることで私が設定したCloudflare Workersのドメインにアクセスすると

ちゃんと表示されるし、middlewareやRemixのログをCloudflareのコンソール上で確認してみても正常に出力されています。(Remix側のログが2重に出力されてるのはよくわからないです)

middleware側

Remix側

ここまでくれば後はRemixとmiddlewareをどう処理を配置するかの問題になってくると思います。

さいごに

Cloudflareの Birthday Weekの発表でログに関する発表があることはDiscordやコンソールなどを見ているとわかっていたのですが、Cloudflare Workersが静的ファイルの配信をサポートしてくることは予想外でした。
Cloudflare PagesとCloudflare Workersで被る部分が多くなってきており、実際にどっちを使用すればいいかわからないという声も多くなってくるかもしれませんが、Remixだけでなく色々なフレームワークでCloudflare Workersで動作する設定が既にCloudflareのドキュメントにあがっているのでこれからはCloudflare Workersで動作させる方向に舵を切った方がいいかもしれません。

Discussion