1. はじめに

このブログは Hugo で生成した静的ファイルを Cloudflare Pages から配信していました。今回、これを Cloudflare Workers の Static Assets から配信する構成へ移行しました。

前から気になっていたのが、Cloudflare Workers で Static Assets を配信できるようになったことです。長らく臭い物に蓋をしていたのはここだけの略

2024年9月、当社は静的アセットをホスト、保存、配信するためのベータサポートをCloudflare Workersで無料で利用できるようにしました。以前はCloudflare Pagesでのみ利用可能だったものです。

フロントエンド、バックエンド、データベースが1つのCloudflare Workerに | Cloudflare ブログ

加えて、GitHub Actions で使っていた cloudflare/pages-action が deprecated になったことも、移行のきっかけでした。

Pages を使い続けることもできますが、この機会に Workers の Static Assets へ移し、GitHub Actions からのデプロイには cloudflare/wrangler-action を使うことにしました。

2. 移行にあたって

Cloudflare には Pages から Workers への公式の移行ガイドがあります。

Migrate from Pages to Workers · Cloudflare Workers docs

今回はこの公式ガイドをベースに、コーディングエージェントにも助けてもらいながら移行を進めました。

実際にやってみると、Workers の設定だけではなく、GitHub Actions やローカルからのデプロイ方法、Custom Domain の切り替え、旧 Pages プロジェクトの削除など、いくつか手を入れるところがありました。

この記事では公式ガイドをなぞるのではなく、実際に変更したところや途中で引っかかったところを作業ログとして残します。

この手の移行はあとから同じ環境でもう一度試すのが難しいため、細かな正確性までは保証しきれません。実際に移行する際は公式ドキュメントも確認しつつ、進め方の一例として読んでもらえればと思います。

3. 移行前後の構成

今回の移行をざっくり diff にすると、下記のとおりです。

 Hugo build
   └─ public/
-      └─ cloudflare/pages-action
-           └─ Cloudflare Pages
+      └─ cloudflare/wrangler-action
+           └─ wrangler deploy
+                └─ Cloudflare Workers Static Assets

Hugo のビルド方法や Dev Container などの開発環境はなるべく変えず、public/ 以降のデプロイ経路を Pages から Workers に置き換えました。

具体的には、次の作業をしています。

  • リポジトリ側を Workers に対応させる
    • Workers 用の wrangler.jsonc を追加する
    • GitHub Actions を cloudflare/pages-action から cloudflare/wrangler-action に変更する
    • ローカルからも Wrangler でデプロイできるようにする
  • Cloudflare 側を Workers に切り替える
    • gkzz.dev の配信先を Pages から Workers に切り替える
  • 移行後に旧 Pages プロジェクトを削除する

また、移行にあたっては既存の Dev Container と .tool-versions をそのまま活かし、ローカルと CI で同じバージョンの Wrangler を使えるようにしました。

以降では、この順番で実際の作業を見ていきます。

4. 移行作業

① リポジトリ側を Workers に対応させる

wrangler.jsonc を追加する

Workers の設定は wrangler.jsonc にまとめました。

Cloudflare のドキュメントでも、Wrangler の設定ファイルを Worker の設定における source of truth として扱うことが推奨されています。

It is best practice to treat Wrangler’s configuration file as the source of truth for configuring a Worker.

Configuration - Wrangler · Cloudflare Workers docs

今回追加した設定は次のとおりです。

{
  "name": "<Worker 名>",
  "compatibility_date": "<compatibility date>",
  "assets": {
    "directory": "./public",
    "not_found_handling": "404-page"
  },
  "workers_dev": false,
  "preview_urls": true,
  "routes": [
    {
      "pattern": "gkzz.dev",
      "custom_domain": true
    }
  ]
}

主な設定は次のとおりです。

  • Hugo のビルド結果である ./public を Static Assets として配信
  • 存在しないパスでは Hugo の 404.html を返す
  • gkzz.dev を Custom Domain に設定
  • workers.dev は無効、Preview URL は有効

ローカル、GitHub Actions ともに、この設定を使って wrangler deploy しています。

GitHub Actions を cloudflare/wrangler-action に変更する

GitHub Actions では、public/ を作ったあとのデプロイ処理を cloudflare/pages-action から cloudflare/wrangler-action に置き換えました。

Hugo のビルド方法は Pages のころと同様、hugo --minify のままです。

- name: Build
  run: hugo --minify

- name: Deploy production Worker
  id: publish_cloudflare_workers
  uses: cloudflare/wrangler-action@<version>
  with:
    apiToken: ${{ secrets.YOUR_CLOUDFLARE_API_TOKEN }}
    accountId: ${{ secrets.YOUR_CLOUDFLARE_ACCOUNT_ID }}
    packageManager: pnpm
    wranglerVersion: ${{ steps.tool_versions.outputs.wrangler }}
    command: deploy

ここでは Wrangler のバージョンを .tool-versions から取得して wranglerVersion に渡しています。

Cloudflare Workers には Workers Builds もありますが、今回は既存の GitHub Actions を活かし、デプロイ部分だけを Wrangler に置き換えました。

ローカルからも Wrangler でデプロイする

ローカルからのデプロイも Wrangler に寄せました。

Dev Container の中で、.tool-versions から取得した Wrangler のバージョンを指定して pnpm dlx から実行しています。

devcontainer exec \
  --workspace-folder "." \
  env \
  CLOUDFLARE_ACCOUNT_ID="${CLOUDFLARE_ACCOUNT_ID}" \
  CLOUDFLARE_API_TOKEN="${CLOUDFLARE_API_TOKEN}" \
  WRANGLER_VERSION="${wrangler_version}" \
  bash -lc 'pnpm dlx "wrangler@${WRANGLER_VERSION}" deploy'

ローカルと GitHub Actions で実行方法は異なりますが、Wrangler のバージョンは .tool-versions に寄せています。

.tool-versions
      ├─ local → pnpm dlx wrangler@<version>
      └─ CI    → wranglerVersion

Wrangler を package.json の依存関係として追加する方法もありますが、今回は既存の .tool-versions をそのまま活かすことにしました。

② Cloudflare 側を Workers に切り替える

リポジトリ側の準備ができたら、gkzz.dev の配信先を Pages から Workers に切り替えます。

  • wrangler versions upload --preview-alias で Version Preview URL を発行し、表示を確認
  • Pages 側の Custom Domain から gkzz.dev を外す
  • wrangler.jsoncroutes を反映して Workers 側へ切り替え
  • gkzz.dev から表示を確認

Version Preview URL では、トップページ、個別記事、CSS、画像が問題なく配信されていることを確認しました。

gkzz.dev の Custom Domain は、先ほどの wrangler.jsoncroutes で設定しています。

このあたりは Cloudflare Dashboard が少し分かりづらく、Pages と Workers のどちらが gkzz.dev を配信しているのか確認しながら進めました。

最後に gkzz.dev からも同様に表示を確認して、切り替え完了です。

③ 旧 Pages プロジェクトを削除する

Workers への切り替えが終わったので、旧 Pages プロジェクトを削除します。

ここはすぐ終わると思っていたのですが、100 件を超える deployments が残っており、Cloudflare Dashboard から削除できませんでした。

調べてみると、Cloudflare の Known issues にも記載がありました。

You may not be able to delete your Pages project if it has a high number (over 100) of deployments. The Cloudflare team is tracking this issue.

Delete a project with a high number of deployments · Cloudflare Pages docs

Cloudflare の Known issues には deployments を削除するスクリプトも掲載されています。

今回は削除対象を事前に確認したかったため、dry run と実削除前の確認を追加したスクリプトを用意しました。

  • デフォルトは dry run
  • --apply を付けた場合のみ削除
  • 実削除の前に確認を挟む
  • production deployment は残す

実装のうち、削除まわりを抜粋すると次のようになります。

# デフォルトは dry run
if ! $apply; then
  echo "Dry run only."
  printf '%s\n' "$preview_ids" | sed 's/^/Would delete: /'
  continue
fi

# 実削除の前に project name の入力を要求
printf 'Type DELETE %s to confirm destructive deletion: ' "$project_name"
read -r confirmation

if [ "$confirmation" != "DELETE ${project_name}" ]; then
  echo "Aborted."
  exit 1
fi

# preview deployments を削除
while IFS= read -r id; do
  [ -z "$id" ] && continue

  curl -sS -X DELETE \
    "${auth[@]}" \
    "${api}/${id}?force=true" |
    jq -e '.success == true' >/dev/null

  echo "Deleted ${id}"
done <<< "$preview_ids"

preview deployments を削除したあとは、Cloudflare Dashboard から旧 Pages プロジェクトを削除できました。

移行そのものより、最後の Pages の片付けのほうが意外と手間がかかりました。長く運用している Pages プロジェクトでは、deployments の数も事前に確認しておいたほうがよさそうです。

5. まとめ

Hugo 製のこのブログを Cloudflare Pages から Workers の Static Assets へ移行しました。

Hugo のビルド方法や既存の開発環境にはなるべく手を入れず、public/ 以降のデプロイ経路を Pages から Wrangler + Workers に置き換えることを心がけました。

一方で、Custom Domain の切り替えや Pages deployments の削除など、Cloudflare 側の作業には少し手間取りました。

Pages から Workers へ移行する場合は、Workers へのデプロイだけでなく、Custom Domain の切り替えと旧 Pages 環境の片付けまで含めて考えておくとよいかもしれません。