Cloudflare Pages と Cloudflare Workers KV を組み合わせてウェブアプリを作ってみた

前回に引き続き、Cloudflare の無料枠で遊んでみる。今回は静的サイトホスティングをしてくれる Cloudflare Pages を使って、前回作成した Cloudflare Workers を呼び出すようなアプリを作ってみる。ついでに Cloudflare Web Analytics も試しているので、最後までお楽しみに (?)

目次

Cloudflare Pages とは

Cloudflare Pages は、GitHub Pages や Netlify と同じく、GitHub リポジトリへの Push をトリガーに、自動的にプロジェクトをビルド・デプロイしてくれる静的サイトホスティングサービス。

出来ること自体は GitHub Pages や Netlify と変わらないが、Cloudflare が持つ CDN エッジ上にサイトがデプロイされるため、他のホスティングサービスよりもリクエストが高速に行われるようだ。

以下の公式サイトに記載があるとおり、無料枠ではビルドの同時実行数・1ヶ月あたりのビルド回数の上限があるが、サイト数やリクエスト数、帯域幅などは有料版と同じく無制限。頻繁な更新がなければ無料プランでも十分使えそうだ。

裏側は Cloudflare Workers を隠蔽して静的サイトホスティングに特化させているようで、サービスとしても今後 Workers と統合する予定だとか聞いた。

Cloudflare Pages と Cloudflare Workers を組み合わせることで、Netlify + Netlify Functions ないしは Vercel + Vercel Serverless Functions 的な感じで、サーバレス環境が手に入るが、Netlify と Vercel にはデータ永続化層がない。MongoDB Atlas という無料枠のある NoSQL サービスを別途登録すれば、無料でデータ永続化層を用意して接続することはできるが、複数サービスを併用することになる。その点、Cloudflare の場合は NoSQL である Cloudflare Workers KV もセットなので、単一サービスで全てが完結し、Functions (Workers) から NoSQL (Workers KV) への通信も Cloudflare 内に閉じていて高速だ。

GitHub リポジトリを用意する

Cloudflare Pages のプロジェクトを作るには、GitHub リポジトリとの連携が前提になるので、先に GitHub リポジトリを作っておく。

今回は Create-React-App (CRA) で作った React の SPA を Cloudflare Pages にデプロイすることにする。

# CRA で React プロジェクトを作り、GitHub に Push する
$ npx create-react-app practice-cloudflare-pages

Cloudflare Pages にデプロイするからといって、コード修正は一切必要なかった。

Cloudflare Pages プロジェクトの作成・初回デプロイ

ブラウザで Cloudflare Pages ダッシュボードに行き、次のように操作していく。

  1. 「プロジェクトを作成」ボタンを押す
  2. 「GitHub リポジトリからプロジェクトを作成する」画面で「GitHub アカウントを接続する」ボタンを押す
  3. 「リポジトリを選択する」欄で GitHub リポジトリを選択する
  4. 「セットアップの開始」ボタンを押す
  5. 「ビルドとデプロイをセットアップ」画面で設定を確認する
    • 「フレームワークプリセット」 : 「Create React App」を選択する
    • 「ビルドコマンド」 : 「npm run build」が設定されるはずなのでそのまま
    • 「ビルド出力ディレクトリ」 : 「/build」が設定されるはずなのでそのまま
  6. 「保存してデプロイする」ボタンを押す
  7. 「ビルドおよびデプロイを実行しています」と表示されるので3分ほど待つ

→ コレで、Cloudflare Pages プロジェクトが作成でき、初回デプロイができた。現状は、React のロゴが見えているだけの、ボイラープレートファイルそのままの状態だ。

Workers スクリプトを更新しつつ、Workers と連携する React アプリを作っていく

サンプルとして、Workers KV を読み取り・書き込みする簡単なウェブアプリを作成する。今回は滅茶苦茶雑に、全ユーザで共有される一つのノートアプリというモノを作ってみる。

普通だったらユーザごとにノートブックのデータを分けて保持するだろうが、今回はユーザ登録や認証を考えたくなかったので、誰がアクセスしてきても同じノートを参照・編集することになる、という不思議なアプリにしてみた。ユーザを区別しなくて良いので、KV としては単一の Key にテキストを保存することになる。Workers KV は NoSQL、つまり結果整合性に重きをおき、強整合性は保証されないので、様々なユーザから保存と同時に参照が行われた時は、更新前・更新後どちらのデータが返されるか分からないタイミングがあったりする。

また、今回はアクセス元が Cloudflare Pages からかどうかもチェックしていない。極端な話、curl コマンドで Workers の URL を叩いてもノートの内容が参照・更新できる雑仕様なので、そのつもりで。

Workers の実装

Cloudflare Workers の実装、および URL エンドポイントは以下のとおり。前回の記事では kv.get() で KV から値を取得するだけだったが、今回は PUT メソッドでのアクセス時に kv.put() メソッドでデータを書き込み (Insert もしくは Update 相当) する処理を加えている。

また、Pages から呼び出すためには、レスポンスに CORS ヘッダが必要だったので、そのための対応をあちこちでしている。

この辺は漏れがちなので、new Response() の処理を共通関数で作ってあげた方が良いだろう。

いずれも、この CORS ヘッダが必要だった。

実装が済んだら、wrangler publish でデプロイしておこう。

React アプリの実装

Pages にデプロイした React アプリの実装は以下のとおり。

Workers との通信は window.fetch() を使っている。今はもう Axios とか入れなくても Fetch API で事足りますな…。

Workers KV から取得したテキストを表示・編集可能にするためのテキストエリアを配置。form 要素内にない textarea 要素に対して value をバインディングする際は、onChange イベントを必ず定義して、その中で setState() を呼び出して確実にバインディングした value 値を更新してあげないといけない。onChange だが、onInput 相当のタイミングでも都度発火する。

Workers のエンドポイント URL は環境変数 REACT_APP_API_URL を定義して注入した。CRA が内蔵している .env ファイルの読み取り機能を使って、ローカル開発時と本番デプロイ時でそれぞれ別々の URL を渡すようにした。

Cloudflare Pages プロジェクトを作成した時にデプロイ設定をしたとおり、npm run build でビルドした資材が Pages に公開されるので、.env.production を GitHub リポジトリに Push して参照させることにした。なので、Cloudflare Pages のプロジェクト設定での環境変数は一切指定していない。より機密性の高い情報は .env ファイルに書いてコミットするワケにはいかないので、そういう時は Pages のプロジェクト設定から環境変数を定義・注入させよう。

ついでに Cloudflare Web Analytics のコードも仕込んでおく

ついでに、Cloudflare の無料枠で使える、Cloudflare Web Analytics という分析サービスのためのスクリプトコードを、index.html に仕込んでおく。

Google Analytics なんかと似たような感じで、ページに script 要素を仕込むだけ。確認できる内容は Google Analytics よりは少なく、以下の項目ぐらい。

解析できる項目 ≒ 処理が少ないためか、CDN エッジで全てが完結しているためか、PageSpeed Insights ではモバイル99点、パソコン100点である。Google Analytics のコード入れると途端に点数が下がるのと比べると、かなりパフォーマンスが良い。

ずっと Google Analytics 使ってきたけど、Cloudflare Web Analytics に移行しようかな…。w

Cloudflare Pages にデプロイする

Web Analytics はおまけ程度に仕込むとして。

アプリを実装したら、GitHub にコミット・Push する。すると Cloudflare Pages 側が自動的に Push を検知し、資材のビルドと公開処理が始まる。Pages のデプロイは毎回3分程度かかる。

こうして公開したのが以下のサイト。

画面いっぱいに表示されたテキストエリアに自由にテキストを入力していただき、「Save」ボタンを押してもらうと、その内容が Workers を通じて Workers KV に保存される。

違うブラウザや違うデバイスでアクセスしても、同じ KV から同じデータを取得しているので、タイミングによってはせっかく自分が書いて更新した内容が上書き削除されてしまう可能性もあるという、謎仕様のジョーク・ノートアプリの出来上がり。

以上

Cloudflare Workers 単体でも、リクエストパスに応じて処理を振り分ければ、HTML や CSS を返すパス、Workers KV と接続する API のパス、などを作れるが、いささかコードが煩雑になる。

そこで今回のように Cloudflare Pages を併用すれば、フロントエンドとバックエンドを綺麗に切り分けられる。今回は分かりやすくするため GitHub リポジトリを Workers 用と Pages 用とで2つに分けたが、設定ファイルの置き方次第で単一リポジトリでも運用は可能そうだ。Workers へのデプロイも、GitHub Actions を使うことで自動化でき、wrangler publish をローカルから叩かなくても良くなる。

Netlify や Vercel でも「実現出来ること」はそう変わらないが、

というメリットがかなり大きいので、細かな仕様差や制約事項が飲み込めれば、Cloudflare がかなりつよつよな無料サービスであろう。