Cloudflare Pages と Cloudflare Workers KV を組み合わせてウェブアプリを作ってみた
前回に引き続き、Cloudflare の無料枠で遊んでみる。今回は静的サイトホスティングをしてくれる Cloudflare Pages を使って、前回作成した Cloudflare Workers を呼び出すようなアプリを作ってみる。ついでに Cloudflare Web Analytics も試しているので、最後までお楽しみに (?)
目次
- Cloudflare Pages とは
- GitHub リポジトリを用意する
- Cloudflare Pages プロジェクトの作成・初回デプロイ
- Workers スクリプトを更新しつつ、Workers と連携する React アプリを作っていく
- React アプリの実装
- ついでに Cloudflare Web Analytics のコードも仕込んでおく
- Cloudflare Pages にデプロイする
- 以上
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 にデプロイすることにする。
- 参考 : Deploy a React application · Cloudflare Pages docs
- 参考 : Cloudflare Pages tutorial: Deploying a React app via GitHub - LogRocket Blog
- 参考 : Cloudflare PagesにReact-Webアプリをホストする – くじらぴーまん
# CRA で React プロジェクトを作り、GitHub に Push する
$ npx create-react-app practice-cloudflare-pages
Cloudflare Pages にデプロイするからといって、コード修正は一切必要なかった。
Cloudflare Pages プロジェクトの作成・初回デプロイ
ブラウザで Cloudflare Pages ダッシュボードに行き、次のように操作していく。
- 「プロジェクトを作成」ボタンを押す
- 「GitHub リポジトリからプロジェクトを作成する」画面で「GitHub アカウントを接続する」ボタンを押す
- 「リポジトリを選択する」欄で GitHub リポジトリを選択する
- 「セットアップの開始」ボタンを押す
- 「ビルドとデプロイをセットアップ」画面で設定を確認する
- 「フレームワークプリセット」 : 「Create React App」を選択する
- 「ビルドコマンド」 : 「
npm run build
」が設定されるはずなのでそのまま - 「ビルド出力ディレクトリ」 : 「
/build
」が設定されるはずなのでそのまま
- 「保存してデプロイする」ボタンを押す
- 「ビルドおよびデプロイを実行しています」と表示されるので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 相当) する処理を加えている。
- Neos21/practice-cloudflare-workers: Practice Cloudflare Workers
- https://practice.neos21.workers.dev/
また、Pages から呼び出すためには、レスポンスに CORS ヘッダが必要だったので、そのための対応をあちこちでしている。
- 参考 : Build an API for your frontend using Cloudflare Workers · Cloudflare Pages docs
-
Adding CORS headers
- ↑このセクションを参考に実装する
-
OPTIONS
メソッドの場合に、空 Response しつつ以下のヘッダを入れておくAccess-Control-Allow-Origin
:*
Access-Control-Allow-Methods
:GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers
:Content-Type
(最低限)
- その他の全ての
Response
時に、以下のヘッダが必要Access-Control-Allow-Origin
:*
この辺は漏れがちなので、new Response()
の処理を共通関数で作ってあげた方が良いだろう。
npm start
で起動したローカル React 環境とwrangler dev
で起動したローカル Workers との通信時- 本番稼動している Pages と Workers 間の通信
いずれも、この CORS ヘッダが必要だった。
実装が済んだら、wrangler publish
でデプロイしておこう。
React アプリの実装
Pages にデプロイした React アプリの実装は以下のとおり。
- Neos21/practice-cloudflare-pages: Practice Cloudflare Pages
- https://practice-cloudflare-pages.pages.dev/
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 を渡すようにした。
- ローカル開発時 =
npm start
でhttp://localhost:3000/
を起動した時.env
ファイルが参照されるhttp://127.0.0.1:8787/
へアクセスさせる (wrangler dev
で起動されるローカル Workers のエンドポイント URL)
- 本番でデプロイ時 =
npm run build
でビルドされた資材.env.production
ファイルが参照され、ビルドされた資材の中に環境変数の値がベタ書きされるhttps://practice.neos21.workers.dev/
へアクセスさせる (Cloudflare Workers の本番エンドポイント URL)
Cloudflare Pages プロジェクトを作成した時にデプロイ設定をしたとおり、npm run build
でビルドした資材が Pages に公開されるので、.env.production
を GitHub リポジトリに Push して参照させることにした。なので、Cloudflare Pages のプロジェクト設定での環境変数は一切指定していない。より機密性の高い情報は .env
ファイルに書いてコミットするワケにはいかないので、そういう時は Pages のプロジェクト設定から環境変数を定義・注入させよう。
- 参考 : Adding Custom Environment Variables | Create React App
-
What other
.env
files can be used?
-
ついでに Cloudflare Web Analytics のコードも仕込んでおく
ついでに、Cloudflare の無料枠で使える、Cloudflare Web Analytics という分析サービスのためのスクリプトコードを、index.html
に仕込んでおく。
Google Analytics なんかと似たような感じで、ページに script
要素を仕込むだけ。確認できる内容は Google Analytics よりは少なく、以下の項目ぐらい。
- アクセス数
- リファラ
- URL パス
- ブラウザ
- OS
- デバイス種別
- アクセス元の国
- ページロード時間
- Core Web Virals … Google が提唱する UX 指標
- LCP : Largest Contentful Paint … 読み込みパフォーマンス
- FID : First Input Delay … ユーザの応答性
- CLS : Cumulative Layout Shift … レイアウト崩れなど視覚的な安定性
解析できる項目 ≒ 処理が少ないためか、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 でも「実現出来ること」はそう変わらないが、
- CDN エッジで動作するというパフォーマンス面で抜群のアドバンテージ (Pages と Workers)
- データ永続化層 (NoSQL) も単一サービスで管理できる (Workers KV)
というメリットがかなり大きいので、細かな仕様差や制約事項が飲み込めれば、Cloudflare がかなりつよつよな無料サービスであろう。