Vercel Proプランで60以上のNext.jsプロジェクトを運用していましたが、組織向けアプリをCloudflare Pagesに移行することで、Hobbyプラン(無料)へのダウングレードを実現しました。
背景
Vercel Proの課題
Vercel Hobbyプランは非商用・個人利用のみです。組織のアプリをホストしている場合、Proプラン($20/月〜)を維持する必要があります。
しかし、60以上あるプロジェクトのうち組織向けアプリは2つだけでした。この2つをCloudflareに移せば、残りは個人プロジェクトとしてHobbyプランで運用できます。
移行対象
- 多言語対応(日本語/英語)の検索アプリ(Next.js + next-intl + Elasticsearch)
- IIIF画像ビューアアプリ
いずれもカスタムドメインで運用中。
Next.js 16の壁 — proxy.tsが未対応
最初にそのままCloudflare Pagesへの移行を試みましたが、ビルドの最終段階でエラーが発生しました。
Node.js middleware is not currently supported.
Consider switching to Edge Middleware.
Next.js 16では従来の middleware.ts が proxy.ts にリネームされ、Node.jsランタイム固定になりました。@opennextjs/cloudflare v1.18.0時点ではこの新しい proxy.ts に未対応です(opennextjs/opennextjs-cloudflare#962)。
解決策:Next.js 15にダウングレード
# package.json
"next": "^15.3.1", # 16.x → 15.x
"next-intl": "^3.26.5", # 4.x → 3.x(Next.js 15対応版)
"eslint-config-next": "^15.3.1",
同時に proxy.ts を middleware.ts にリネームし、エクスポート名も proxy → middleware に変更しました。
Cloudflare Pagesへのデプロイ手順
1. 依存パッケージのインストール
npm install --save-dev @opennextjs/cloudflare wrangler
2. 設定ファイルの作成
// open-next.config.ts
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
export default defineCloudflareConfig({});
// wrangler.jsonc
{
"name": "my-app",
"pages_build_output_dir": ".open-next/assets",
"compatibility_date": "2025-04-01",
"compatibility_flags": ["nodejs_compat"]
}
3. ビルド
npx @opennextjs/cloudflare build
4. Pagesへのデプロイ(ここが最大のポイント)
@opennextjs/cloudflare のビルド出力はWorkers向けですが、Pagesにデプロイするには以下の手順が必要です。
# Pagesプロジェクト作成
npx wrangler pages project create my-app --production-branch main
# _worker.jsをコピー(PagesのWorkerエントリーポイント)
cp .open-next/worker.js .open-next/_worker.js
# assetsの中身を.open-nextルートにコピー(静的ファイル配信用)
cp -r .open-next/assets/* .open-next/
# デプロイ(.open-nextディレクトリ全体)
npx wrangler pages deploy .open-next --project-name my-app --branch main
3つのポイント:
_worker.jsのコピー — Pagesは_worker.jsをWorkerエントリーポイントとして認識する- assetsの展開 —
assets/内のファイル(_next/static/等)を.open-next/ルートにコピーしないと、CSSやJSが404になる .open-nextディレクトリ全体をデプロイ —assets/だけではserver-functions/等のWorker依存ファイルが含まれない
5. _routes.json で静的ファイルをWorkerから除外
これがないと、CSSやJavaScript等の静的ファイルまでWorker(Next.jsサーバー)に渡されて404になります。
// .open-next/_routes.json
{
"version": 1,
"include": ["/*"],
"exclude": [
"/_next/static/*",
"/favicon.ico",
"/apple-icon.png",
"/icon.png",
"/manifest.json",
"/sitemap.xml",
"/robots.txt",
"/images/*",
"/*.webp",
"/*.png",
"/*.jpg",
"/*.css",
"/*.js"
]
}
excludeに指定したパスはPagesの静的ファイル配信で直接返され、それ以外は_worker.js(Next.jsサーバー)で処理されます。
6. 環境変数の設定
# サーバー側の機密情報はsecretsとして設定
npx wrangler pages secret put ES_URL --project-name my-app
npx wrangler pages secret put ES_USER --project-name my-app
npx wrangler pages secret put ES_PASSWORD --project-name my-app
NEXT_PUBLIC_* 変数はビルド時に埋め込まれるため、ビルド前に .env.local に設定しておきます。
git worktreeでの安全な実験
既存のVercelデプロイに影響を与えずにCloudflare移行を試すため、git worktreeを使いました。
# developブランチから実験用worktreeを作成
git worktree add ../my-app-cloudflare -b feature/cloudflare-migration develop
これにより、元のリポジトリはそのまま(Vercelでデプロイ中)で、別ディレクトリでCloudflare向けの変更を試せます。
Workers vs Pages — なぜPagesを選んだか
| 項目 | Workers | Pages |
|---|---|---|
| カスタムドメイン | Cloudflare DNSゾーン必須 | CNAMEだけでOK |
| GitHub連携 | 手動設定 | ダッシュボードから簡単 |
| プレビューデプロイ | 自分で設定 | PRごとに自動 |
| リクエスト数(無料) | 10万/日 | 無制限 |
最初はWorkersにデプロイして動作確認しましたが、カスタムドメインにCloudflare DNSゾーンが必要と判明。既存のDNS(さくら、Route 53等)をそのまま使いたかったため、Pagesに切り替えました。
Vercel(Hobby)で注意すべきこと
Hobbyプランへのダウングレード後の制限:
| 項目 | Pro | Hobby |
|---|---|---|
| 帯域幅 | 1TB/月 | 100GB/月 |
| サーバーレス関数実行時間 | 900秒 | 60秒 |
| SSR実行リージョン | 選択可能 | iad1(米国東部)のみ |
| チーム利用 | 可 | 個人のみ |
| 商用利用 | 可 | 非商用のみ |
特にSSRのリージョン制限は重要です。Hobbyプランでは日本からのSSRリクエストが太平洋を往復するため、レイテンシが増加します。一方、Cloudflare Pages(Workers)は東京を含む全世界のEdgeで実行されるため、SSRのレスポンスはCloudflareの方が高速です。
移行可否の判断基準
| 条件 | 移行可否 |
|---|---|
| Next.js 15以下 + API Routes のみ | ✅ 移行しやすい |
| Next.js 15以下 + Edge Middleware | ✅ 対応済み |
| Next.js 16 + proxy.ts | ❌ 未対応(15に下げれば可) |
| next-intl 等の国際化ライブラリ | ✅ middleware.ts経由で動作 |
| Elasticsearch等のTCPライブラリ | ⚠️ fetchへの書き換えが必要な場合あり |
| sharp等のネイティブモジュール | ❌ Workers環境では動作しない |
まとめ
- Vercel Hobbyプランは非商用・個人利用限定。組織アプリがあるならProが必要
- 組織アプリだけCloudflare Pagesに移せば、残りはHobbyで運用可能
- Next.js 16の
proxy.tsはopennextjs-cloudflareで未対応。15に下げれば移行できる - Pagesにデプロイする際は
_worker.jsのコピー、assetsの展開、_routes.jsonの配置が必要 - カスタムドメインを既存DNSで管理したいなら Workers ではなく Pages を使う
- git worktreeで安全に実験してから移行するのがおすすめ