以下の内容はhttps://tech.hello.ai/entry/vercel-cloudflare-migrationより取得しました。


Next.jsをVercelからCloudflareへ移行し、90%のコスト削減を実現した話

酒井です。ハローでは、プロダクトのローンチ前からAutoReserveの開発に関わっています。

この記事では、Next.jsアプリケーションであるautoreserve.comVercelからCloudflareに移行し、月額コストを約90%削減した背景と実装の詳細を共有します。

Next.jsは比較的セルフホスティングが難しいフレームワークとして知られており、Vercelへのベンダーロックインが懸念されることがあります。Next.js 16でBuild Adapters APIが導入されるなど、セルフホスティングのハードルは徐々に下がっていますが、実運用では課題が多いのが現状です。

VercelからOpenNext + Cloudflare Workersの構成に本番環境を移行したため、現場でのNext.jsのセルフホスティングの実際について紹介できればと思います。

背景

AutoReserveは、世界中のレストラン予約を扱うグローバルサービスです。iOS / Android / Webの3 プラットフォームで展開しており、Web版は100ページ規模のNext.jsアプリケーションです。

  • モバイル: React Native(Expo)
  • Web: Next.js

Web版は一時期SPA構成でしたが、SEOとパフォーマンスの課題から2024年ごろにNext.js + Vercelに段階的に移行しています。

SSRの効果は明確で、SPAからNext.js + Vercelへ移行で以下の成果が得られました:

  • Core Web Vitals(LCP)が平均1.7倍改善
  • キャッシュ無効状態のクローラ向けページロード時間が約2.8倍高速化

一方でSSR化した結果、サービスの成長とグローバル展開に伴い、Vercelのコストが急増する問題に直面しました。 Vercel上では合計で月あたり約200万円の請求が継続的に発生しており、サービスの成長に伴いさらに増加する見込みでした。

Vercelで直面したコスト問題

主なコスト増加の要因は次の通りです。

1.キャッシュが効きづらい構造

  • 多量のページが存在する
    • (世界のレストラン数 + 検索結果ページ) * 言語数のページが存在する
  • トラフィックがロングテール
  • キャッシュ困難なSSRページが多く、Compute課金が増大

SSRコスト削減のため、VercelのFluid Computeも検証しましたが、約40%のCompute削減効果は確認できたものの:

  • レイテンシーが大幅に悪化
    • CPUをStandardのままにするとCPU Throttlingによりレイテンシーが増加する
    • 一方でCPUをPerformanceレベルに上げるとCompute削減効果が相殺される
  • マネージドインフラのため、原因究明・解消が難しい

という理由から、本番導入は見送りました。

請求額の1/3がCompute課金によるものである一方、削減余地が限られており、根本的な解決には至りませんでした。

2. 帯域幅課金の構造的問題

Vercelではデータ転送(帯域幅)に課金が発生します:

  • VercelのCDNとVercel Compute間の通信
  • VercelのCDNとエンドユーザー間の通信

AutoReserve のようにSSRが多いサービスでは、

  • HTML配信
  • バックエンドAPI(GCP)との通信

が積み上がりやすく、トラフィック増加に比例してコストが増加します。

具体的には、課金額の1/2程度が帯域幅に起因していました。

移行先の検討

ホスティング先を変更するにあたり、重視したポイントは以下です。

  • Next.jsを継続利用できること
  • 帯域幅課金がない、または極めて安価であること
  • SSRのComputeコストが安いこと
  • SEO/パフォーマンスを維持できること

この条件を満たす選択肢として、OpenNext + Cloudflare Workersの組み合わせを検討しました。

Cloudflareを選んだ理由

  • Cloudflare Workersからのデータ転送に課金が発生しない
  • エッジ実行によるパフォーマンスの良さ
  • Compute 課金が CPU Time ベース
    • バックエンド API待ち時間が課金対象外
  • Vercelと比較してCompute価格が大幅に安い
    • CPU-hrあたりの価格でおよそ約5倍差
    • Cloudflare Workers: $0.072/CPU-hr($0.02/100万 CPU-ms)
    • Vercel: $0.36/CPU-hr ($0.18/GB-hr, Standard = 1vCPU / 2GBでの計算)
      • Legacy Pricingでの計算
      • 実際にはワークロードがメモリ/CPUヘビーかなど特性により変動する

OpenNextを選んだ理由

Next.jsをセルフホスティング上で、デプロイ方法は大きく3つあります。

  • Node.jsのサーバーとして動かす方法
  • Dockerを使用しデプロイする方法
  • OpenNextを使用しプラットフォームにデプロイする方法

下記の理由でOpenNextを採用しました:

  • CloudflareのアダプターはCloudflare公式が管理しており長期的なメンテナンスが期待できる
  • エッジで動作し、コールドスタートもほぼなくパフォーマンス的に有利である
  • Cloudflare Workersはオートスケールし管理が容易である

以上から、OpenNextを利用してCloudflareへ移行することを決定しました。

Cloudflareへの移行

技術調査と詰まりどころ

Cloudflare Workersの制約対応

Cloudflare WorkersにはFree・Paid問わず複数の技術的制約が存在します。 AutoReserveではWorkers Paidプランを利用していますが、以下の制約への対応が必要でした。

Node.jsの互換性対応

Cloudflare WorkersはNode.jsとの互換性が完全ではありませんnodejs_compatを利用してNode.js互換レイヤーを有効化しても動作しないパッケージが存在します。 一方、対応ライブラリに移行する・不要なパッケージを除去する等の対応が行えるため、Node.jsとの互換性問題は比較的回避しやすいです。

対応内容:

  • axiosの削除
    • 実際にはアプリケーションではofetchを使用しておりaxiosは未使用だったため、依存関係からimportを除去

バンドルサイズ制限

Cloudflare Workersにはバンドルサイズの制限があります。バンドルサイズの制限を超えるとデプロイが失敗するため、特に注意が必要です。 今後の機能開発にも十分対応できるよう、修正対応後、バンドルサイズが余裕を持って制限内に収まることを確認しました。

Workers Paidプランでの制約:

  • 全体: 64MB(圧縮前)
  • Worker 単体: 10MB

対応内容:

  • @sentry/nextjsが5MBあるWASMファイルを読み込んでいるため、除外
    • Webpack設定で不要なバンドルを防止

メモリ制限(128MB)

同様に、Cloudflare Workersにはメモリ使用量の制限があります。

対応内容:

  • Vercelでは超過しているケースがあったが、デプロイし確かめたところCloudflare Workersでも安定して動作
    • 追加対応なし

これらを事前に対応した上でOpenNextを導入しました。

デプロイ戦略

Cloudflare Workers Buildを使わなかった理由

Cloudflare Workers Buildは設定が簡単であるメリットがあり、ダッシュボードからの操作も可能で使いやすいものの、以下のデメリットがあり採用を見送りました。

  • ビルド時間が20分制限を超えるとデプロイに失敗する
    • まれに失敗するケースがあり、安定性に懸念があった
  • デプロイ完了をトリガーにE2Eテストを走らせづらい
    • デプロイ完了の通知を容易に受け取る手段がない

結果として、GitHub Actionsからのデプロイを採用しました。

環境変数管理

Next.jsにはビルド時とランタイムでの環境変数が存在し、ビルド・ランタイムの両方の環境で環境変数にアクセスできる必要があります。

対応内容:

  • GitHub Environments(staging / production)を使用して環境変数を管理
    • Cloudflare Workersのランタイム側の環境変数はwrangler secret bulkでGitHub Environmentsの環境変数をデプロイ時に同期

Sentry 対応

OpenNext on Cloudflare環境では、移行実施時点で@sentry/nextjsがCloudflare Workersに非対応でした。

対応内容:

  • @sentry/cloudflare+Sentry CLIに移行
  • ビルド時にSource Mapsをアップロード

OpenNextのミドルウェアの不具合修正

OpenNextのmiddleware実装に不具合があり、外部URLに対してのrewriteが正しく動作しない問題がありました。これが原因でアプリケーションのE2Eテストが落ちていたため、筆者が@opennextjs/awsにコントリビュートし修正しました。

パッケージ名がわかりづらいですが、@opennextjs/awsはCloudflare環境下でも使用されているコアライブラリとなります。

段階的な移行

フロントエンドインフラ全体の移行はリスクが高いため、ロールバックが簡単にできるよう段階的に移行を進める必要がありました。 よって、Vercel側の既存のデプロイは維持しつつ、Cloudflare Workers側へも並行してデプロイする形を取りました。

対応内容:

  • Vercel / Cloudflareを並行してデプロイ
    • 両方に対してE2Eテストを別々に実行
    • 問題発生時に即切り戻しできるように

Vercel側のトラフィックも、元々Cloudflareを通る構成としていたため、切り替え時もDNSを触らず、Cloudflare Workers Routesの切り替えだけで数クリックで瞬時にロールバックできる構成にしました。

開発体験の維持・ブランチデプロイの実現

Vercelでは機能開発時に、PRへのコミットごとにプレビューURLを発行し、E2EテストやQAを実施できるようになっています。 Cloudflare Workersでも同様のワークフローを実現するため、以下の対応を行いました。

  • コミットごとにopennextjs-cloudflare buildでビルド & アップロード
    • コミットごとに独立したURLが発行されるようにし、このURLに対してE2Eテストを走らせる
    • QAに渡す時、PRに対して最新のデプロイが参照されるURLも欲しいため、--preview-alias <alias>オプションを使用し、PRごとに固定のエイリアスURLも発行
  • GitHub ActionsでPRにコメントを投稿し、ビルド進行状況とプレビューURLを通知

PRにつくプレビューコメントの例

なお、Cloudflare Workersでは、Durable Objectsを使用しているとPreview URLを生成できない制約があり、OpenNextのキャッシュ機構でDurable Objectsを使用するとこの制約に引っかかってしまうため、ブランチデプロイ時はMemory Queueを使用するよう分岐して対応しています。

移行後の効果

コスト

月次請求ベースでフロントエンドのインフラコストを約90%削減できました。

※ 他プロジェクトで引き続き使用しているVercelの席課金などの固定費を除いた請求額

内訳:

  • 帯域幅コスト: $0
  • Compute: 大幅削減
    • 時間単位の単価が1/4程度
    • CPU Time 課金により、バックエンドAPI待ち時間が課金対象外に

パフォーマンス / SEO

Next.js + Vercelと同等の水準を維持できています。

  • Web Vitals指標に悪影響なし
    • 本番運用開始後も大きな問題は発生していません

まとめ

Next.js + OpenNext + Cloudflare Workersへの移行により、AutoReserveのインフラコストを大幅に削減できました。

Vercelの利便性は高いものの、SSRが多くキャッシュが効きづらい大規模なNext.jsアプリケーションでは、課金構造上コストが嵩みやすいという課題があります。

今回の移行により、トラフィックの増大に対応できる持続可能なコスト構造を実現できたと考えています。

ハローでは、プロダクトの急成長を一緒に支えてくれるエンジニアを募集しています。

少しでも気になる方は気軽に酒井にDMでお声がけください!

https://hello.ai/recruit/engineer




以上の内容はhttps://tech.hello.ai/entry/vercel-cloudflare-migrationより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14