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、Toolchain の管理、production deploy の扱い、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 に変更する
    • Toolchain version と共通タスクを mise.toml に寄せる
    • production deploy は GitHub Actions に集約する
  • Cloudflare 側を Workers に切り替える
    • gkzz.dev の配信先を Pages から Workers に切り替える
  • 移行後に旧 Pages プロジェクトを削除する

また、移行後の整理として、Node.js、pnpm、Hugo、Wrangler の version と共通タスクを mise.toml に寄せました。Toolchain 整理の詳細は Workers 移行そのものとは少し話が分かれるため、ここではデプロイ経路に関係する部分だけ触れます。

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

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 を cloudflare/wrangler-action に変更する

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

Hugo のビルド自体は Pages のころと同じく hugo --minify ですが、現在は mise.tomlsite:build task に寄せています。Wrangler の version も mise.toml"npm:wrangler" を参照します。

- name: Setup mise
  uses: jdx/mise-action@<version>

- name: Build
  run: mise run site:build

- 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.wrangler_version.outputs.version }}
    command: deploy

実際の workflow では、mise.toml から Wrangler の version を読み取り、wranglerVersion に渡しています。

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

production deploy を GitHub Actions に集約する

gkzz.dev は production の Custom Domain なので、ローカルから wrangler deploy できる経路は塞いでいます。

production への反映は main branch への merge、または GitHub Actions の手動実行に限定しています。

ローカルの make deploy-worker は残していますが、通常は即終了する非常用のメモにしてあります。production へ反映する経路を GitHub Actions に寄せることで、手元の環境や権限の状態に依存した deploy を避けるためです。

Pull Request や branch の表示確認には wrangler versions upload --preview-alias による Version Preview URL を使い、production route の gkzz.dev とは分けています。

② 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 に置き換えることを心がけました。あわせて、Toolchain version と共通タスクは mise.toml に集約し、ローカルと GitHub Actions で同じ入口を使いやすくしました。

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

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