Vercel で適当なアプリをホスティングしていると、アクセスが増えだしたときに真っ先に心配になるのが Fast Data Transfer (転送量) の制限です。

無料の Hobby プランだと 100GB までで、容量大きいデータがあると意外とあっという間に使い切ってしまいます。100GB を超えると有料の Pro プラン(月額 $20〜)へのアップグレードが必要になりますが、個人開発としてはできればコストを抑えたいところ。
そこで今回は、アプリ本体は Vercel に置いたまま、静的コンテンツだけを転送量無料の Cloudflare R2 に逃がすハイブリッド構成を試します。
全体構成

- Vercel: Next.js との親和性が高く、NextAuth や Firebase を使ったサーバーサイドの処理がそのまま動く。
- Cloudflare R2: Amazon S3の互換サービスだが、データ転送量 (Egress) が完全に無料なので、ファイルの配信に最適。無料枠も大きい。
Cloudflare Pages にアプリごと移すと、Edge Runtime の制約で Node.js 依存のライブラリ(Firebase Admin など)を書き換える必要があるため、今回はハイブリッドな形を選びました。
実装のポイント
Cloudflare R2のバケットを作る
R2 Object Storageで新規バケットを作ったら、
パブリックアクセスを有効にして、

バケットに書き込むためのAPIトークンを作成します(Account API token)

データ取得 URL を環境変数で切り替える
ローカル開発時は手元のファイル、本番環境では R2 の URL を見に行くように、簡単なユーティリティを作成しました。
export const getDataUrl = (path: string) => { const baseUrl = process.env.NEXT_PUBLIC_DATA_URL || ''; const normalizedPath = path.startsWith('/') ? path : `/${path}`; return `${baseUrl}${normalizedPath}`; };
各コンポーネントでの fetch 部分をこれに置き換えます。
// 今まで fetch('/data/xxxxx.png') // これから fetch(getDataUrl('/data/xxxxx.png'))
Vercel の環境変数 NEXT_PUBLIC_DATA_URL に R2 バケットのパブリックURLを設定すれば、本番環境だけ R2 を参照するようになります。未設定ならローカルのファイルを見に行くので、開発環境を汚さないのもいい感じです。
GitHub Actions で R2 に自動同期
public/data/ 以下のファイルに変更があった時だけ、rclone コマンドで R2 へ差分同期(Sync)するように設定しています。GitHubのシークレットには先ほど作った R2 バケットのエンドポイントやAPIトークンを設定します。
on: push: branches: [main, release] paths: ['appdir/public/data/**'] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install rclone run: sudo apt-get install -y rclone - name: Configure & Sync run: | # rclone 設定後に sync 実行 rclone sync appdir/public/data r2:my-bucket/data --progress env: R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }}
これで、今まで通り git push するだけで、アプリ本体(Vercel)とデータ(R2)が同時に最新の状態に保たれます。

忘れがちな CORS 設定
R2 のデータをブラウザから直接 fetch するため、Cloudflare 側で CORS 設定を追加する必要があります。
[ { "AllowedOrigins": ["https://app-domain.com"], "AllowedMethods": ["GET"], "AllowedHeaders": ["*"] } ]
おわりに

この構成に切り替えた結果、Vercel 側の転送量は JS などの軽量なファイルのみになり、100GB 制限の不安はほぼなくなりました。アクセスが急増した時でも、転送量無料の R2 が盾になってくれるので、精神衛生上も非常によろしいです。
同じように Vercel の転送量でお悩みの方は、ぜひ検討してみてください。



