自社サイトを Vercel から Cloudflare Workers へフル移行した記録

カテゴリ
News

こんにちは!システムデザインワークスの山岸です。

現在ご覧いただいている弊社のコーポレートサイト(Next.js + Payload CMS 構成)のホスティングを、Vercel から Cloudflare Workers へ全面的に移行しました。データベース接続・画像配信・メール送信・定期実行まで含めた「フル移行」です。狙いは、AI 時代に急速に高まるセキュリティリスクへの備えと、コストの最適化でした。

結論から言うと、サイトはいま Cloudflare のエッジ(世界中に分散したサーバー網)上で安定稼働しています。ただ、移行は一筋縄ではいきませんでした。Workers は一般的なサーバーとは仕組みが異なる実行環境のため、「これまで普通に動いていた画像処理やメール送信が、移行した途端に動かなくなる」という壁に何度もぶつかったのです。本記事では、採用した構成と、特にハマった 3 つのポイントを共有します。

背景・課題:AI 時代のセキュリティリスクに、先回りして備えたい

移行前の構成は、ホスティングが Vercel、メディアストレージが Vercel Blob、データベースが Supabase(PostgreSQL)でした。Vercel の開発体験は素晴らしく、不満があったわけではありません。特に、GitHub にプッシュするだけで確認用のプレビュー URL が自動発行される体験は秀逸で、レビューや動作確認のスピードを大きく支えてくれていました。

それでも移行を決めた一番の理由は、セキュリティです。

生成 AI の登場により、攻撃の様相は急速に変わっています。脆弱性の探索や攻撃コードの作成が自動化・高速化され、情報漏洩や脆弱性を突いたハッキングのリスクは、企業規模を問わず急激に高まっています。「うちは小さい会社だから狙われない」が通用しない時代になりました。

Cloudflare は世界中のインターネットトラフィックの相当な割合を中継する、世界最大級のセキュリティ企業です。DDoS 攻撃(大量アクセスでサービスを停止させる攻撃)の防御や WAF(Web アプリケーションファイアウォール)をエッジで標準的に提供しており、アプリケーションが動く場所そのものを Cloudflare 上に置けば、この防御網の内側でサイトを運用できます。

もう一つの理由はコストです。Workers の有料プラン(Workers Paid)は月 5 ドルからと安価で、R2(Cloudflare のオブジェクトストレージ)は転送量課金がありません。セキュリティを高めながら固定費も抑えられる、という点が決め手になりました。

ただし条件を一つ決めていました。Vercel で気に入っていた「プッシュすればプレビュー URL が出る」開発体験は、移行後も手放さないこと。セキュリティとコストのために開発スピードを犠牲にしては本末転倒だからです。

加えて、「まず自分たちで試して、効果と落とし穴を把握してからお客様に提案する」が弊社のスタイルです。今回の移行はその実践でもありました。

移行後の構成:すべて Cloudflare のマネージドサービスに寄せる

Next.js を Cloudflare Workers で動かすために、公式が推奨する OpenNext@opennextjs/cloudflare)を採用しました。Next.js のビルド成果物を Workers のランタイムである workerd 向けに変換してくれるアダプタです。

役割ごとの構成は次のとおりです。

  • ホスティング: Cloudflare Workers(OpenNext 経由)

  • データベース接続: Supabase PostgreSQL へ Hyperdrive(Cloudflare のコネクションプーリングサービス)経由で接続

  • メディアストレージ: Vercel Blob から R2 へ全件移行

  • 画像リサイズ: sharp(画像処理ライブラリ)の代わりに Cloudflare Image Transformations へ委譲

  • SSG/ISR キャッシュ: ページキャッシュは R2、タグ管理と再生成キューは Durable Objects(Workers 用の永続ステートフルオブジェクト)

  • メール送信: SMTP から Cloudflare Email Service へ切替

  • 定期実行: Vercel Cron から Workers Cron Triggers へ移植

  • デプロイ: GitHub Actions から自動デプロイ(ブランチごとのプレビュー URL 付き)

ポイントは「Workers で動かないものを無理に動かす」のではなく、動かない部分を Cloudflare のマネージドサービスに置き換える方針で統一したことです。

実装・検証の要点:workerd で本当にハマった 3 つ

移行作業の大半は順調でしたが、workerd は Node.js と似て非なるランタイムです。特に苦労した 3 つを紹介します。

1. 間欠的に発生する謎のハング(Error 1101)

移行直後、管理画面など DB アクセスの多いページで、リクエストが応答しないまま失敗する現象(Cloudflare の Error 1101)が間欠的に発生しました。再現条件が安定せず、ローカルでは再現しない厄介なバグです。

原因は 2 つ重なっていました。

  1. Hyperdrive の接続先が IPv6 専用だった: Supabase の direct 接続は IPv6 専用で、これがハングの一因に。IPv4 で到達できる Session pooler(pooler.supabase.com:5432)へ変更して解消しました。なお Transaction pooler(6543 番ポート)は prepared statement(SQL の事前コンパイル機能)に非対応のため使えません

  2. コネクションプールが workerd の I/O 分離に違反していた: Node.js の感覚でモジュールスコープに pg のコネクションプールを置くと、DB 接続(ソケット)がリクエストをまたいで再利用されます。workerd はリクエスト間の I/O を分離する設計のため、これが後続リクエストの無限ハングを引き起こしていました。Workers 上では接続を使い捨て(maxUses: 1)にし、実際のプーリングは Hyperdrive に委ねる構成で解決しました

1// Workers 実行時のみ: 接続は使い捨て、プーリングは Hyperdrive に任せる
2const pool = new Pool({
3 connectionString,
4 max: 10,
5 maxUses: 1, // リクエストをまたいだソケット再利用を防ぐ
6})

修正後は管理画面への同時 20 リクエストがすべて正常応答し、Error 1101 はゼロになりました。

2. sharp が動かない、そして SVG が全滅した

Payload CMS や next/image が使う画像処理ライブラリ sharp はネイティブバイナリを含むため、workerd では起動時にクラッシュします。そこで画像のリサイズ・再エンコードは Cloudflare Image Transformations(/cdn-cgi/image/ の URL に変換を委譲する仕組み)へ切り替えました。

ところが切り替え後、サイト中のロゴや技術アイコンが一斉に表示されなくなりました。原因は、Image Transformations がベクター画像(SVG)の変換に対応しておらず 404 を返すこと。カスタムローダー側で SVG と GIF は変換を通さず原本を直接配信するよう除外して解決しました。

3. 問い合わせメールが「静かに」届かなくなっていた

移行後しばらくして、お問い合わせフォームの通知メールが届いていないことに気づきました。workerd では DNS/TCP の制約により nodemailer の SMTP 送信が機能しません。しかも送信エラーはフォームライブラリ側で握りつぶされており、表面上は正常に見えていました。

対応として、Cloudflare Email Service(send_email バインディング)を使う Payload 用のメールアダプタを実装し、Workers 上では env.EMAIL.send() で送信する構成に切り替えました。ローカル開発では従来どおり SMTP を使い分けています。

エラーが画面に出ない「サイレント障害」は、移行プロジェクトで最も怖い類のものです。移行後は主要動線(問い合わせ・ログイン・記事更新)を実際に通すスモークテストが欠かせないと再認識しました。

やめたこと・採らなかった選択肢

  • OpenNext 生成物への直接パッチ: 定期実行(cron)を生成された Worker に差し込む案は、OpenNext の更新ごとに壊れるため不採用。cron 専用の小さな Worker を別に立て、service binding(Worker 間の内部呼び出し)で本体 API を叩く構成にしました

  • CDN キャッシュの積極パージ: 設定不備時に黙って失敗する(silent failure)リスクがあるため、初期構成では意図的に無効化。安定稼働を確認してから段階導入する方針です

結果:セキュリティもコストも狙いどおり、デプロイ体験まで向上

  • サイト全体が Cloudflare の防御網(DDoS 対策・WAF)の内側で動くようになり、当初の狙いだったセキュリティ面の土台が整った

  • ホスティングの固定費は Workers Paid(月 5 ドル〜) が基本となり、メディア配信も R2 直配信で転送量課金なし。コストの見通しが立てやすくなった

  • 管理画面・公開ページとも安定稼働し、間欠ハング(Error 1101)は解消

  • Payload で記事を更新すると該当ページだけが再生成される ISR(ページの差分再生成)も Workers 上で機能

  • 移行の条件にしていた「プッシュすればプレビュー URL が出る」体験も再現。GitHub Actions と Workers のプレビュー機能(preview alias)を組み合わせ、main へのプッシュは本番デプロイ、それ以外のブランチはブランチ名入りのプレビュー URL が自動発行される開発フローを構築できた

なお、バンドルサイズが Workers 無料プランの上限(gzip 3MiB)を超えるため Workers Paid プランが前提になりますが、それでも月 5 ドルからという価格は移行の後押しになりました。

最後に、移行を終えて振り返った両者の比較を表にまとめます。どちらが優れているという話ではなく、Vercel の手軽さを取るか、Cloudflare のセキュリティとコストを取るかという選択だと感じています。

比較項目

Vercel

Cloudflare Workers

月額基本料金

Pro プラン: $20/人

Workers Paid: $5〜

メディア配信の転送量

転送量に応じて課金

R2 は転送量課金なし

セキュリティ

WAF はプラン・設定しだい

DDoS 防御・WAF を標準提供

実行環境

Node.js がそのまま動く

workerd(Node.js 互換は部分的。sharp などのネイティブモジュールは不可)

画像リサイズ

組み込みの画像最適化

Image Transformations へ委譲(SVG / GIF は素通しが必要)

メール送信(SMTP)

利用可

SMTP 不可(Email Service で代替)

プレビュー URL

プッシュするだけで自動発行

CI(GitHub Actions)で構築して再現可能

構築・移行の手間

ほぼ設定不要

OpenNext の導入など個別対応が必要

※ 2026 年 6 月時点。Next.js + Payload CMS 構成での弊社の移行経験に基づきます。

まとめ:移行から得た 3 つの学び

  1. workerd は「ほぼ Node.js」ではない: ネイティブモジュール(sharp)、SMTP、リクエストをまたぐソケット再利用——Node.js の常識が通用しない箇所は事前にリストアップしておく

  2. 動かないものはマネージドサービスに置き換える: Hyperdrive・R2・Durable Objects・Email Service と、足りないピースは Cloudflare 側に揃っている。無理に互換レイヤーを自作しない

  3. 切り戻せる形で段階移行する: メディア移行は「コピーのみ」で旧環境を残し、画像変換には無効化スイッチを用意。安心して前に進むには退路の設計が効く

ご相談お待ちしております。

Web サイト・Web システムのセキュリティ強化や、Vercel・AWS からの移行、Cloudflare Workers での新規構築をご検討中の方、栃木県でシステム開発・アプリ開発の会社をお探しの方は、ぜひ一度ご相談ください。

システムデザインワークスでは、今回のような自社での実践に基づき、インフラ選定から移行・運用まで一貫してサポートしております。お気軽にお問い合わせフォームよりご連絡ください。