メインコンテンツへスキップ
kt-tech.blog
【環境構築】Next.js × Cloudflare Workers の本番環境を一から構築する
設定・環境構築
(更新: 2026/3/20)· 約13分で読めます

【環境構築】Next.js × Cloudflare Workers の本番環境を一から構築する

Share
💡
dev環境が整っている Next.js + Cloudflare Workers プロジェクトに、本番(prod)環境を新規構築する手順を体系的にまとめました。Terraform、Prisma Postgres、GitHub Actions 環境別デプロイなど、実践的な構成を解説します。

はじめに

開発環境(dev)で動いている Next.js アプリケーションを本番環境にデプロイするには、単にコードを push するだけでは済みません。データベース、ストレージ、認証、決済など、各サービスのリソースを本番用に新規作成し、適切に分離する必要があります。

この記事では、Cloudflare Workers 上で動く Next.js アプリの本番環境を一から構築した際の手順と設計判断をまとめます。

この記事でわかること

  • dev/prod 環境のリソース分離設計の考え方

  • Cloudflare API トークンの種類と使い分け

  • Terraform で本番 R2 バケットを作成する手順

  • Prisma Postgres の環境別 DB 構成

  • GitHub Secrets を使った環境別シークレット管理

  • 本番デプロイまでの正しい作業順序

対象読者

  • Cloudflare Workers + Next.js でアプリを運用している方

  • dev 環境はあるが prod 環境をこれから作る方

  • インフラを Terraform で管理したい方

前提条件

  • Next.js (App Router) + Cloudflare Workers (OpenNext) の構成

  • Terraform CLI がインストール済み

  • GitHub Actions で CI/CD が構築済み

  • dev 環境が稼働中

技術スタック

サービス 用途
Cloudflare Workers サーバーレスランタイム (Next.js)
Cloudflare R2 ファイルストレージ (S3互換)
Prisma Postgres データベース (Accelerate経由)
Stripe 決済・サブスクリプション
Resend トランザクションメール
Google OAuth ソーシャルログイン
GitHub Actions CI/CD
Terraform インフラ管理

1. 環境分離の設計方針

リソース分離の原則

本番環境では全リソースを完全分離する方針を取りました。

リソース dev prod
Cloudflare アカウント 共通 共通
Worker 名 app-dev app
R2 バケット app-dev app-prod
データベース 開発用 DB 本番用 DB
Stripe テストモード ライブモード
API キー各種 dev 用 prod 用
💡
Cloudflare アカウント自体は共通で OK。アカウントを分けるメリットはほぼなく、リソース名(Worker 名、バケット名)で分離すれば十分です。

API キーも分離する理由

  • レート制限を環境ごとに独立させる

  • 本番の障害が dev 環境に波及しない

  • セキュリティ侵害時の影響範囲を限定

2. Cloudflare API トークンの設計

Cloudflare では用途別に3種類のトークンを使い分けます。

トークン構成

用途 トークン名例 権限 設定先
Terraform (IaC) prod-infra Workers R2 Storage: 編集 terraform.tfvars
wrangler deploy (CI/CD) ci-deploy Workers 全般 (Edit Cloudflare Workers テンプレート) GitHub Secrets
R2 S3互換 (アプリ) prod-r2 Object Read & Write GitHub Secrets

R2 トークンの種類に注意

R2 の API トークン管理画面には2種類あります。

  • アカウント API トークン — 組織に紐づく。組織を離れても有効。本番推奨

  • ユーザー API トークン — 個人に紐づく。組織を離れると無効化

⚠️
本番環境では必ずアカウント API トークンで作成しましょう。ユーザー API トークンは個人に紐づくため、チーム変更時にサービスが停止するリスクがあります。

3. Terraform で R2 バケットを作成

ディレクトリ構成

terraform/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf
│       ├── variables.tf
│       └── terraform.tfvars
└── modules/
    ├── r2/          # R2バケット + CORS設定
    └── worker/      # Worker Cronトリガー

prod/main.tf の構成

module "r2" {
  source = "../../modules/r2"

  account_id  = var.cloudflare_account_id
  bucket_name = "app-prod"
  allowed_origins = [
    "https://example.com",  # 本番ドメインのみ
  ]
}

module "worker" {
  source = "../../modules/worker"

  account_id  = var.cloudflare_account_id
  worker_name = "app"
  cron_schedules = ["0 15 * * *"]  # JST 0:00
}

R2 だけ先行作成

Worker Cron トリガーは既存の Worker が必要なため、初回は R2 だけ先に作成します。

cd terraform/environments/prod
terraform init
terraform plan -target=module.r2
terraform apply -target=module.r2
💡
Worker Cron トリガーは、wrangler deploy で Worker を作成した後に terraform apply(target 指定なし)で追加します。

4. Prisma Postgres の環境別構成

同一プロジェクト内で DB を分離

Prisma Console では、1つのプロジェクト内に複数のデータベースを作成できます。新しいプロジェクトを作る必要はありません。

プロジェクト: my-app
├── Development  ← dev環境用(既存)
└── Production   ← prod環境用(新規作成)

作成手順

  1. Prisma Console でプロジェクトを開く

  2. 左サイドバーの DATABASES の + ボタンをクリック

  3. 名前: Production、リージョン: Asia Pacific (Tokyo)

  4. 2つの接続 URL を取得

URL 種類 形式 用途
DATABASE_URL prisma+postgres://accelerate.prisma-data.net/?api_key=… アプリからの接続(Accelerate 経由)
DIRECT_URL postgresql://…@db.prisma.io:5432/postgres マイグレーション実行用(直接接続)

5. GitHub Secrets の環境別管理

GitHub Environments の活用

GitHub Actions の environment 機能を使い、環境別にシークレットを分離します。

# deploy.yml
jobs:
  deploy:
    environment: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}

これにより:

  • main ブランチ → prod 環境のシークレットを使用

  • develop ブランチ → dev 環境のシークレットを使用

CLI で一括登録

# 環境別にシークレットを登録
gh secret set DATABASE_URL --env prod --body "[DATABASE_URL]"
gh secret set AUTH_SECRET --env prod --body "$(openssl rand -base64 32)"
gh secret set CRON_SECRET --env prod --body "$(openssl rand -hex 32)"
# ... 他のシークレットも同様

必要なシークレット一覧

カテゴリ シークレット名 取得元
Database DATABASE_URL Prisma Console
Database DIRECT_URL Prisma Console
Auth AUTH_SECRET openssl で生成
Auth AUTH_GOOGLE_ID Google Cloud Console
Auth AUTH_GOOGLE_SECRET Google Cloud Console
Auth NEXTAUTH_URL 固定値 (本番URL)
AI/LLM OPENAI_API_KEY OpenAI Dashboard
AI/LLM GEMINI_API_KEY Google AI Studio
AI/TTS AIVIS_API_KEY Aivis Cloud
Email RESEND_API_KEY Resend Dashboard
Cloudflare CLOUDFLARE_API_TOKEN Cloudflare API Tokens
Cloudflare CLOUDFLARE_ACCOUNT_ID Cloudflare Dashboard
Storage R2_ENDPOINT 固定値
Storage R2_ACCESS_KEY_ID R2 API Tokens
Storage R2_SECRET_ACCESS_KEY R2 API Tokens
Storage R2_BUCKET_NAME 固定値
Payment STRIPE_SECRET_KEY Stripe Dashboard
Payment STRIPE_PUBLISHABLE_KEY Stripe Dashboard
Payment STRIPE_WEBHOOK_SECRET Stripe Webhooks
Payment STRIPE_PRICE_ID Stripe Products
Cron CRON_SECRET openssl で生成

6. Stripe 本番モードの注意点

Stripe Live モードの有効化にはビジネスオンボーディングが必要です。

必要な情報

  • 事業形態・個人情報

  • ビジネスのウェブサイト(表示可能で公開されていること

  • 銀行口座情報

  • 2段階認証設定

⚠️
ウェブサイトが実際にアクセス可能である必要があるため、先にアプリをデプロイしてから Stripe を設定する必要があります。Stripe 以外のシークレットで先にデプロイし、決済機能は後から有効化する順序になります。

7. 正しい作業順序

各サービス間の依存関係を考慮すると、以下の順序が最適です。

Phase 1: リソース作成

1. Cloudflare API トークン作成 (Terraform用 + wrangler用 + R2用)
2. Terraform apply → R2 バケット作成
3. Prisma Console → 本番 DB 作成
4. Google Cloud ConsoleOAuth クライアント作成
5. 各サービス → API キー作成 (OpenAI, Gemini, Aivis, Resend)

Phase 2: デプロイ準備

6. AUTH_SECRET, CRON_SECRET を生成
7. GitHub Secrets に全シークレットを登録 (Stripe除く)
8. Worker 名の環境別切り替え対応

Phase 3: デプロイ & 後続設定

9.  main ブランチに push → prod デプロイ
10. Cloudflare → カスタムドメイン設定
11. Stripe → ビジネスオンボーディング完了
12. StripeProduct/Price/Webhook 設定
13. GitHub SecretsStripe シークレット追加
14. 再デプロイ
15. Terraform apply → Worker Cron トリガー追加

Tips

1️⃣
terraform.tfvars には機密情報が含まれるため、.gitignore に追加して絶対にコミットしないこと。
2️⃣
openssl rand -base64 32 で生成するシークレットは環境ごとに別の値を使うこと。同じ値を使うとセッションハイジャックのリスクがある。
3️⃣
Stripe 本番設定を後回しにする場合、アプリ側で Stripe 関連の環境変数が未設定でもクラッシュしないようにガード処理を入れておくと安心。
4️⃣
GitHub Secrets の値は CLI でも確認できないため、パスワードマネージャーなどに別途バックアップしておくと良い。

8. デプロイ時のトラブルシューティング

初回の本番デプロイでは、開発環境では発生しなかった問題がいくつか発生しました。

pgvector 拡張の未インストール

prisma db pushERROR: type "vector" does not exist エラーが発生。新規 DB に pgvector 拡張がインストールされていないため。

psql '[DIRECT_URL]' -c 'CREATE EXTENSION IF NOT EXISTS vector;'

prisma db push の --accept-data-loss

新規 DB へのスキーマ push 時に「データ消失の可能性」警告で停止。初回のみローカルから手動実行し、CI のコマンドはそのまま維持。

DIRECT_URL='[接続文字列]' bunx prisma db push --accept-data-loss
⚠️
--accept-data-loss を CI に常設するのは危険。運用中のデータが消える可能性があるため、初回セットアップ時のみ手動で実行すること。

Stripe 環境変数未設定によるビルドエラー

Stripe の初期化コードが環境変数を必須としているため、未設定だとビルドが失敗。ダミー値を設定してビルドを通し、本番 Stripe 設定完了後に差し替える。

9. カスタムドメインの設定

Cloudflare Workers のカスタムドメインは API で設定可能。ダッシュボードに行く必要はありません。

curl -X PUT \
  "https://api.cloudflare.com/client/v4/accounts/[ACCOUNT_ID]/workers/domains" \
  -H "Authorization: Bearer [API_TOKEN]" \
  -H "Content-Type: application/json" \
  -d '{"hostname":"example.com","service":"worker-name","environment":"production"}'
  • SSL 証明書は自動発行される

  • DNS が Cloudflare で管理されていれば即時反映

まとめ

  • リソースは完全分離が原則。Cloudflare アカウントだけ共通、それ以外は全て環境別

  • API トークンは用途別に作成。Terraform 用、CI/CD 用、アプリ用で分ける

  • Terraform の部分適用 (-target) で依存関係のないリソースから作成できる

  • GitHub Environments で環境別シークレット管理。ブランチ名で自動切替

  • Stripe はデプロイ後に設定。ウェブサイトの公開が前提条件

  • 作業順序が重要。依存関係を把握して効率的に進める

  • 初回デプロイは手動ステップが必要。pgvector 拡張のインストール、prisma db push --accept-data-loss

  • Stripe は段階的に有効化。ダミー値でビルドを通し、オンボーディング後に差し替え

  • カスタムドメインは API 設定可能。Cloudflare Dashboard 不要

この記事が役に立ったら共有しよう

Share
Koki

Koki

フルスタックエンジニア / React, Next.js, TypeScript