QLITRE DIALY

Cloudflare PagesからWorkersへの移行

2025年05月04日

タイトルの通りCloudflare PagesにデプロイしていたサイトをCloudflare Workersへ移行する作業をしていた。

差し迫った事情は特にないのだが、yusukebeさんのポストで書かれている通り、特にPagesを使っている理由もなかったので移行をすることにした。

Cloudflare自体は外資系のサービスであるが、日本人であるyusukebeさんが定期的に情報発信をしてくれる。そのおかげで最新の動向のキャッチアップがしやすい。これがないと「知らないうちにサービス内容が変わって、サイトが動かなくなっていた」みたいな事態は普通に起こりうるので、かなり助かっている。

昨日から今日にかけて作業をして、とりあえずこの日記サイトとメインで使っている家計簿アプリの移行が完了。日記サイトはかなり簡単だったが、家計簿の方は泥沼化した。

かなりメモ的に移行作業を振り返る。

日記サイト

こちらはやることがあまりなかった。構成はhonoxとmicroCMS。

まずは今までの設定ファイルのwrangler.tomlwrangler.jsoncにおきかえる。

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "hono-qlitre-dialy",
  "main": "./dist/index.js",
  "compatibility_date": "2025-05-01",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": "./dist"
  },
  "vars": {
    "SERVICE_DOMAIN":"my-service-domain",
    "API_KEY":"supersecret"
  }
}

vite.config.tsで@hono/vite-build/cloudflare-workersを使うようにする。

import build from "@hono/vite-build/cloudflare-workers";
import adapter from "@hono/vite-dev-server/cloudflare";
import honox from "honox/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    honox({ devServer: { adapter }, client: { input: ["./public/static/css/style.css"] } }),
    build(),
  ],
  ssr: {
    external: [
      "microcms-js-sdk",
      "dayjs",
      "microcms-rich-editor-handler",
      "shiki",
    ],
  },
});

package.jsonのデプロイコマンドwrangler deployにする。


  "scripts": {
    "dev": "vite",
    "build": "vite build --mode client && vite build",
    "preview": "wrangler dev",
    "format":"npx prettier --write .",
    "deploy": "yarn build && wrangler deploy"
  },

あとはいつも通りデプロイ。カスタムドメインを使っているので、DNSレコードをworkersの方に向けて完了。

こっちは簡単だった。参考までにgitコミットを貼っておきます。

https://github.com/qlitre/hono-qlitre-dialy/commit/bc5952cbf4f611e66a2ebd92608209c57f9acbb3

家計簿

こっちが本題。日記サイトの方はデータはmicroCMSから取得しているので、基本的に動作環境の置き換えだけで済んだが、家計簿アプリはデータをCloudflare D1から取得している。WorkersはもちろんD1とバインドできる。

だが、Pagesで動かしていた時でのデータの取得方法の設計がバッドだった。

具体的にはhonoxのプロジェクト内に/apiルートを生やして、フロント側の実装でfetch関数を用いることで、データのやり取りをしていた。

もちろんサイト自体がD1とバインディングしているので、fetch関数を使わなくてもやり取りはできるのだが、なんとなく外部アプリと接続とか拡張をするときに便利かな?と思ってこういう構成にしていたのだった。

最小の構成で再現すると以下のような形になる。

以下のようなapiを設定して。。。

// routes/api/index.ts
import { createRoute } from "honox/factory";

export default createRoute(async (c) => {
  const result = await c.env.DB.prepare("SELECT * FROM expense LIMIT 5").all();
  return c.json(result);
});

フロント側の実装から呼び出す。

// routes/index.tsx
import { createRoute } from 'honox/factory'

export default createRoute(async (c) => {
  const origin = new URL(c.req.url).origin
  const response = await fetch(`${origin}/api`)
  const data = await response.json() as unknown as Record<string, unknown>

  return c.json(data)
})

するとcloudflare pagesや開発環境のlocalhostでは動いているのだが、workersにデプロイすると、謎のサーバーエラーが発生する。

原因がよくわからないが、ChatGPTなどで調査をしたところWorkersでは自己オリジンをfetchすると多重呼び出しが発生して、処理が落ちるという回答があった。

TL;DR

  • Pages Functions でも Workers でも “同じ URL へ fetch() すること自体は可能” です。
  • ただし Workers (Edge Runtime) では、その fetch()まったく新しい Worker インスタンスをもう 1 つ起動 してしまうため、 D1 へのコネクションやロックが二重 になりやすく、重いクエリではタイムアウト (Network connection lost) が起きやすい――これが「開発 OK / 本番 NG」の最大の理由です。
  • Pages Functions はローカル開発時に Node のミニプロキシ が挟まるのでオーバーヘッドが小さく、SQLite シミュレータも速い ⇒ 問題が表面化しにくいだけで、原理上は同じ “二段呼び出し” が起きています。

そのため、データのやり取りをしている記述を全て書き換える必要が発生。

最終的な更新は以下のような値に。個人的にかなり大規模な改変となった。Oh...

51 files changed +1109 -1626 lines changed

こちらも一応commit履歴を貼っておきます。

https://github.com/qlitre/honox-kakeibo/commit/b159c7773ff7c05f9a5de1ac0615a7af49a20e52

おわりに

頑張って移行したので、Workersならではの機能も組み込んでいきたい。ではでは。