Skip to content

Multi-Environment Cloudflare Deployment Plan

Current State

  • Frontend: Next.js app in apps/frontend
  • Deployment: Using @opennextjs/cloudflare with Wrangler
  • Config: Basic wrangler.jsonc with single environment
  • API endpoints: Already have stage (api-stage.piquetickets.com) and production (api.piquetickets.com) backends

Implementation Plan

1. Wrangler Configuration - Multi-Environment Setup

Update wrangler.jsonc with routes only (no hardcoded variables):

{
  "main": ".open-next/worker.js",
  "compatibility_date": "2025-03-25",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": ".open-next/assets",
    "binding": "ASSETS"
  },
  "routes": [
    { "pattern": "piquetickets.com/*", "zone_name": "piquetickets.com" },
    { "pattern": "www.piquetickets.com/*", "zone_name": "piquetickets.com" }
  ],
  "env": {
    "stage": {
      "routes": [
        { "pattern": "stage.piquetickets.com/*", "zone_name": "piquetickets.com" }
      ]
    }
  }
}

Key Points: - ✅ No hardcoded name - Will be set dynamically via --name flag in GitHub Actions - ✅ No hardcoded vars - All environment variables injected during build from GitHub Environments - ✅ Only routes are configured per environment - ✅ Clean separation between stage and production routes


2. GitHub Environments Setup

Configure GitHub Environments in Repository Settings:

Navigate to: Settings → Environments and create two environments:

stage Environment

  • Name: stage
  • Environment Variables:
  • NEXT_PUBLIC_API_URL = https://api-stage.piquetickets.com
  • NEXT_PUBLIC_ENVIRONMENT = stage
  • NEXT_PUBLIC_RECAPTCHA_SITE_KEY = <stage-recaptcha-key>
  • NEXT_PUBLIC_GOOGLE_ANALYTICS_ID = <stage-ga-id>
  • NEXT_PUBLIC_FACEBOOK_PIXEL_ID = <stage-fb-pixel-id>

  • Environment Secrets:

  • CLOUDFLARE_API_TOKEN = <stage-cloudflare-api-token>
  • CLOUDFLARE_ACCOUNT_ID = <cloudflare-account-id>

  • Protection Rules (optional):

  • Required reviewers: None (or add for approval gate)
  • Deployment branches: stage branch only

Production Environment

  • Name: production
  • Environment Variables:
  • NEXT_PUBLIC_API_URL = https://api.piquetickets.com
  • NEXT_PUBLIC_ENVIRONMENT = production
  • NEXT_PUBLIC_RECAPTCHA_SITE_KEY = <production-recaptcha-key>
  • NEXT_PUBLIC_GOOGLE_ANALYTICS_ID = <production-ga-id>
  • NEXT_PUBLIC_FACEBOOK_PIXEL_ID = <production-fb-pixel-id>

  • Environment Secrets:

  • CLOUDFLARE_API_TOKEN = <production-cloudflare-api-token>
  • CLOUDFLARE_ACCOUNT_ID = <cloudflare-account-id>

  • Protection Rules (recommended):

  • Required reviewers: Add team members for approval
  • Deployment branches: main branch only

Local Development (Optional):

For local testing, create .env.local file (gitignored):

NEXT_PUBLIC_API_URL=http://localhost:8000
NEXT_PUBLIC_ENVIRONMENT=development
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=<dev-key>

Add to .gitignore:

.env.production
.env.stage
.env.local


3. Package.json Scripts

No changes needed! The existing scripts work with the environment-aware approach:

{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "build:worker": "OPENNEXT_BUILD=true npx opennextjs-cloudflare build",
    "preview:worker": "npx opennextjs-cloudflare preview",
    "preview": "npm run build:worker && npm run preview:worker",
    "deploy": "npm run build:worker && npx opennextjs-cloudflare deploy"
  }
}

Why no changes? - Environment variables are injected by GitHub Actions during the workflow - The build:worker script picks up whatever env vars are set at build time - Wrangler deployment commands are run directly in the GitHub Actions workflow - For local development, you can set env vars before running commands:

# Local stage deployment
NEXT_PUBLIC_API_URL=https://api-stage.piquetickets.com npm run build:worker
npx wrangler deploy --env stage --name piquetickets-frontend-stage

# Local production deployment
NEXT_PUBLIC_API_URL=https://api.piquetickets.com npm run build:worker
npx wrangler deploy --name piquetickets-frontend-production


4. Update next.config.ts

Remove hardcoded fallbacks - rely on environment variables:

// Headers for performance
async headers() {
  // Get API URL from environment variable - no hardcoded fallbacks
  const apiUrl = process.env.NEXT_PUBLIC_API_URL;
  const apiDomain = apiUrl ? new URL(apiUrl).origin : 'https://api.piquetickets.com';

  return [
    {
      source: '/(.*)',
      headers: [
        // ... existing headers ...
        {
          key: 'Content-Security-Policy',
          value: [
            "default-src 'self'",
            "script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://www.google.com https://www.gstatic.com https://connect.facebook.net",
            "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
            "img-src 'self' data: https: http:",
            "font-src 'self' data: https://fonts.gstatic.com",
            `connect-src 'self' ${apiDomain} https://www.google-analytics.com https://api.piquetickets.com https://api-stage.piquetickets.com https://www.facebook.com`,
            // ... rest of CSP ...
          ].join('; ')
        }
      ]
    }
  ];
}

Key Changes: - ✅ No if statements or environment detection logic - ✅ No hardcoded fallback URLs - ✅ Relies entirely on NEXT_PUBLIC_API_URL from GitHub Environments - ✅ CSP headers dynamically use the correct API domain


5. Cloudflare Dashboard Configuration

DNS Records needed: 1. Production: - A or CNAME record: piquetickets.com → Cloudflare Workers - A or CNAME record: www.piquetickets.com → Cloudflare Workers

  1. stage:
  2. A or CNAME record: stage.piquetickets.com → Cloudflare Workers

Workers Routes (configured via wrangler.jsonc or dashboard): - Production worker handles piquetickets.com/* and www.piquetickets.com/* - stage worker handles stage.piquetickets.com/*


6. CI/CD Pipeline with GitHub Environments

Create .github/workflows/deploy-frontend.yml:

name: Deploy Frontend

on:
  push:
    branches:
      - main        # Deploy to production
      - stage       # Deploy to stage
  workflow_dispatch:  # Allow manual deployments
    inputs:
      environment:
        description: 'Environment to deploy to'
        required: true
        type: choice
        options:
          - stage
          - production

jobs:
  deploy-stage:
    name: Deploy to stage
    if: github.ref == 'refs/heads/stage' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'stage')
    runs-on: ubuntu-latest
    environment:
      name: stage
      url: https://stage.piquetickets.com

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: apps/frontend/package-lock.json

      - name: Install dependencies
        working-directory: apps/frontend
        run: npm ci

      - name: Build for Cloudflare Workers
        working-directory: apps/frontend
        run: npm run build:worker:stage
        env:
          NEXT_PUBLIC_API_URL: ${{ vars.NEXT_PUBLIC_API_URL }}
          NEXT_PUBLIC_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_ENVIRONMENT }}
          NEXT_PUBLIC_RECAPTCHA_SITE_KEY: ${{ vars.NEXT_PUBLIC_RECAPTCHA_SITE_KEY }}
          NEXT_PUBLIC_GOOGLE_ANALYTICS_ID: ${{ vars.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID }}
          NEXT_PUBLIC_FACEBOOK_PIXEL_ID: ${{ vars.NEXT_PUBLIC_FACEBOOK_PIXEL_ID }}

      - name: Deploy to Cloudflare Workers (stage)
        working-directory: apps/frontend
        run: npx wrangler deploy --env stage --name piquetickets-frontend-stage
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

  deploy-production:
    name: Deploy to Production
    if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production')
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://piquetickets.com

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: apps/frontend/package-lock.json

      - name: Install dependencies
        working-directory: apps/frontend
        run: npm ci

      - name: Build for Cloudflare Workers
        working-directory: apps/frontend
        run: npm run build:worker:production
        env:
          NEXT_PUBLIC_API_URL: ${{ vars.NEXT_PUBLIC_API_URL }}
          NEXT_PUBLIC_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_ENVIRONMENT }}
          NEXT_PUBLIC_RECAPTCHA_SITE_KEY: ${{ vars.NEXT_PUBLIC_RECAPTCHA_SITE_KEY }}
          NEXT_PUBLIC_GOOGLE_ANALYTICS_ID: ${{ vars.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID }}
          NEXT_PUBLIC_FACEBOOK_PIXEL_ID: ${{ vars.NEXT_PUBLIC_FACEBOOK_PIXEL_ID }}

      - name: Deploy to Cloudflare Workers (Production)
        working-directory: apps/frontend
        run: npx wrangler deploy --name piquetickets-frontend-production
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

Key Features: - ✅ Separate jobs for stage and production - ✅ GitHub Environments for all variable/secret management (no hardcoded values) - ✅ Dynamic worker names set via --name flag (piquetickets-frontend-stage / piquetickets-frontend-production) - ✅ Environment URLs for deployment tracking in GitHub - ✅ Manual deployment trigger via workflow_dispatch - ✅ Build-time environment variable injection - All NEXT_PUBLIC_* vars from GitHub Environments - ✅ Node.js dependency caching for faster builds - ✅ Zero hardcoded configurations - Everything comes from GitHub Environments


7. Testing & Validation

Manual deployment testing:

# Test stage deployment
cd apps/frontend
npm run deploy:stage

# Verify stage site
curl https://stage.piquetickets.com

# Test production deployment (use caution)
npm run deploy:production

# Verify production site
curl https://piquetickets.com


Summary of Changes Needed

  1. Setup GitHub Environments - Create stage and production environments with all variables and secrets
  2. Update wrangler.jsonc - Remove hardcoded vars, add stage routes
  3. Update next.config.ts - Remove hardcoded fallback URLs
  4. Configure Cloudflare DNS - Add stage.piquetickets.com DNS record
  5. Create GitHub Actions workflow - Automated deployments with dynamic worker names
  6. Test deployments - Verify both environments work correctly

What's NOT needed: - ❌ No changes to package.json scripts - ❌ No .env.production or .env.stage files in the repo - ❌ No hardcoded environment-specific values in code


Benefits of This Approach

  • Clear separation between stage and production environments
  • Centralized secret management - All secrets stored securely in GitHub
  • Environment-specific API endpoints and configurations
  • Safe testing on stage before production releases
  • Simple commands - npm run deploy:stage vs npm run deploy:production
  • Automated CI/CD - Branch-based deployments with environment protection
  • Proper secrets management - No sensitive data in config files or codebase
  • Deployment tracking - GitHub shows deployment history per environment
  • Rollback capability - Easy to redeploy previous commits
  • Manual deployment option - Use workflow_dispatch for on-demand deployments
  • Approval gates - Require reviews before production deployments (optional)
  • Audit trail - Track who deployed what and when

Implementation Steps

Phase 1: GitHub Repository Setup

  1. Navigate to Repository Settings → Environments
  2. Create stage environment:
  3. Add environment variables (NEXT_PUBLIC_API_URL, etc.)
  4. Add environment secrets (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, etc.)
  5. Configure deployment branch restrictions (optional)
  6. Create production environment:
  7. Add environment variables with production values
  8. Add environment secrets with production credentials
  9. Configure required reviewers (recommended)

Phase 2: Code Configuration

  1. Update wrangler.jsonc - Remove hardcoded vars, add stage routes only
  2. Update next.config.ts - Remove hardcoded fallback URLs
  3. No package.json changes needed - existing scripts work as-is

Phase 3: CI/CD Setup

  1. Create .github/workflows/deploy-frontend.yml workflow file
  2. Commit and push the workflow to your repository
  3. Verify workflow permissions in Settings → Actions → General

Phase 4: DNS & Cloudflare Configuration

  1. Add stage.piquetickets.com DNS record in Cloudflare
  2. Verify DNS propagation
  3. Ensure Cloudflare API token has Workers deployment permissions

Phase 5: Testing

  1. Create a test branch from stage and push to trigger stage deployment
  2. Verify stage deployment at https://stage.piquetickets.com
  3. Test stage environment functionality
  4. Merge to main branch to trigger production deployment (when ready)
  5. Verify production deployment at https://piquetickets.com

Phase 6: Monitoring

  1. Monitor deployment logs in GitHub Actions
  2. Check Cloudflare Workers dashboard for deployment status
  3. Set up alerting for failed deployments (optional)

Troubleshooting

Deployment Fails with "Unauthorized" Error

Issue: Cloudflare API token is invalid or has insufficient permissions

Solution: - Verify CLOUDFLARE_API_TOKEN is set correctly in GitHub environment secrets - Ensure token has "Workers Scripts:Edit" permission - Regenerate token if needed in Cloudflare Dashboard → My Profile → API Tokens

Environment Variables Not Available During Build

Issue: NEXT_PUBLIC_* variables are undefined in the deployed app

Solution: - Ensure variables are set in GitHub Environment Variables (not Secrets) - Variables must be prefixed with NEXT_PUBLIC_ to be exposed to browser - Verify they're passed in the workflow's env: section during build step - Check build logs to confirm variables are being injected

Wrong Environment Variables Used

Issue: stage deployment uses production API or vice versa

Solution: - Verify the workflow job is using the correct environment: name - Check that ${{ vars.NEXT_PUBLIC_API_URL }} references the right environment - Ensure wrangler deploy command includes --env stage for stage

DNS Not Resolving

Issue: stage.piquetickets.com returns DNS error

Solution: - Verify DNS record exists in Cloudflare dashboard - Wait for DNS propagation (can take up to 24 hours, usually minutes) - Use nslookup stage.piquetickets.com to check DNS resolution - Ensure zone name in wrangler.jsonc matches your Cloudflare zone

Worker Routes Not Working

Issue: Domain loads but shows 404 or default Cloudflare page

Solution: - Check worker routes are configured in wrangler.jsonc - Verify zone_name matches exactly in Cloudflare - Routes may take a few minutes to activate after deployment - Check Cloudflare Workers dashboard → Routes to verify configuration

Manual Deployment Not Working

Issue: workflow_dispatch doesn't trigger from GitHub UI

Solution: - Ensure workflow file is on the branch you're trying to deploy from - Check Actions tab → Select workflow → "Run workflow" button - Verify GitHub Actions is enabled for the repository - Check workflow permissions in Settings → Actions → General

Build Runs Out of Memory

Issue: Build fails with heap out of memory error

Solution: - Already configured in next.config.ts with webpackMemoryOptimizations: true - Verify OPENNEXT_BUILD=true is set (disables heavy chunk splitting) - Consider increasing GitHub Actions runner memory (paid feature) - Review bundle size and optimize imports


Quick Reference

Deploy Commands

# Local deployments (requires local Cloudflare auth)
npm run deploy:stage      # Deploy to stage
npm run deploy:production   # Deploy to production

# View deployment status
wrangler deployments list --env stage
wrangler deployments list

View Logs

# stage logs
wrangler tail --env stage

# Production logs
wrangler tail

Environment URLs

  • stage: https://stage.piquetickets.com
  • Production: https://piquetickets.com
  • Production (www): https://www.piquetickets.com

GitHub Actions

  • Workflow: .github/workflows/deploy-frontend.yml
  • Manual Deploy: Actions tab → Deploy Frontend → Run workflow
  • Deployment History: Deployments tab (per environment)