やりたかったこと
- Cloudflare Pages Functionsで認証などの処理をかけたい
- 開発環境(プレビュー)には認証をかけたい
- 本番環境には認証をかけたくない
- さらに、本番環境ではそもそもPages Functionsを実行させたくない(重要)
- 無料枠に収めるには1日10万回のリクエスト制限内に抑える必要があるため
背景
Cloudflare Pages Functionsで functions/_middleware.ts を配置すると、すべてのリクエストに対してミドルウェアが動作します。環境変数で分岐して開発環境のみにBasic認証をかけること自体は可能ですが、
- 本番環境でも
_middleware.tsが存在する限り、全リクエストでFunctionsが実行される → つまり無料枠(1日10万回)を消費してしまう
という問題があります。
静的配信だけで済むはずの本番環境で、Functionsの実行回数を使い切りたくありません。
なお、認証だけが目的なら本来は Cloudflare Access を使うのが正攻法ですが、今回は諸事情(独自の認証ロジックを使いたい等)でPages Functionsを使う前提の話です。
解決方法
2通りの方法を紹介します。
方法1: _routes.json で Functions の実行範囲を制御する(推奨)
_routes.json は、Functionsをどのパスで実行するかを制御する設定ファイルです。include と exclude でルートを指定でき、exclude は include よりも優先されます。
つまり、exclude: ["/*"] と書けば全パスでFunctionsが非実行になります。
本番デプロイ時だけこの _routes.json をビルド成果物ディレクトリに配置することで、
- 開発:全パスでFunctionsが実行される(
_routes.jsonを配置しない) - 本番:全パスでFunctionsが実行されない(全exclude の
_routes.jsonを配置)
という切り分けができます。
例
# 本番のみ、全パスでFunctionsを実行させない _routes.json を配置
- name: Disable Functions for production
if: env.DEPLOY_ENV == 'production'
run: |
cat > out/_routes.json <<'EOF'
{
"version": 1,
"include": ["/*"],
"exclude": ["/*"]
}
EOF
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
command: pages deploy out --project-name=test_proj --branch=main
方法2: _middleware.ts をそもそもアップロードしない(力技)
こちらはちょっと力技で、本番デプロイ時に _middleware.ts ファイル自体を削除してからアップロードする方法です。
ファイルが存在しなければFunctionsも定義されないので、当然実行もされません。
例
# 本番デプロイ時のみ削除
- name: Remove _middleware.ts
if: env.DEPLOY_ENV == 'production'
run: rm -f functions/_middleware.ts
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
command: pages deploy out --project-name=test_proj --branch=main
注意
方法2が使えるのは「事前ビルド+アップロード型」のCI/CDだけです。
GitHub Actionsなどでビルド済み成果物をPagesにアップロードする構成であれば、どちらの方法も使えます。
一方、Pages側のビルドシステム(Cloudflareのダッシュボードで設定するビルドコマンド)でビルドを行う場合、方法2はうまくいきません。
Pages側ビルドで試してダメだったこと
次のような方法を試しました。
-
package.jsonのビルドスクリプトに_middleware.tsを削除するコマンドを仕込む - ビルドコマンドに
npm run build --extract-middlewareのようなオプションを用意して削除する
しかし、Pages側のデプロイでは functions/ ディレクトリがビルド成果物とは別にリポジトリのルートから直接読み込まれるようなので(推測)、ビルドスクリプトの中で _middleware.ts を削除しても反映されませんでした。
まとめ
- 今回の方法は、諸事情でPages Functionsを使う必要があり、かつ無料枠に収めたい場合の苦肉の策
- 本来、Cloudflare Pagesに認証をかけたい場合は Cloudflare Access を使ってOTPやIP制限を設けるのが正攻法
- ドメインをCloudflareで管理しているなら、Cloudflare Workers を使えばドメイン単位でEdge関数を実行できるので、そちらのほうが柔軟
同じような制約で悩んでいる人の参考になれば幸いです。