Multi-Environment Cloudflare Deployment Plan¶
Current State¶
- Frontend: Next.js app in
apps/frontend - Deployment: Using
@opennextjs/cloudflarewith Wrangler - Config: Basic
wrangler.jsoncwith 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.comNEXT_PUBLIC_ENVIRONMENT=stageNEXT_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:
stagebranch only
Production Environment¶
- Name:
production - Environment Variables:
NEXT_PUBLIC_API_URL=https://api.piquetickets.comNEXT_PUBLIC_ENVIRONMENT=productionNEXT_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:
mainbranch 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:
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
- stage:
AorCNAMErecord: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¶
- ✅ Setup GitHub Environments - Create
stageandproductionenvironments with all variables and secrets - ✅ Update
wrangler.jsonc- Remove hardcoded vars, add stage routes - ✅ Update
next.config.ts- Remove hardcoded fallback URLs - ✅ Configure Cloudflare DNS - Add
stage.piquetickets.comDNS record - ✅ Create GitHub Actions workflow - Automated deployments with dynamic worker names
- ✅ 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:stagevsnpm 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_dispatchfor 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¶
- Navigate to Repository Settings → Environments
- Create stage environment:
- Add environment variables (NEXT_PUBLIC_API_URL, etc.)
- Add environment secrets (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, etc.)
- Configure deployment branch restrictions (optional)
- Create production environment:
- Add environment variables with production values
- Add environment secrets with production credentials
- Configure required reviewers (recommended)
Phase 2: Code Configuration¶
- Update
wrangler.jsonc- Remove hardcoded vars, add stage routes only - Update
next.config.ts- Remove hardcoded fallback URLs - No
package.jsonchanges needed - existing scripts work as-is
Phase 3: CI/CD Setup¶
- Create
.github/workflows/deploy-frontend.ymlworkflow file - Commit and push the workflow to your repository
- Verify workflow permissions in Settings → Actions → General
Phase 4: DNS & Cloudflare Configuration¶
- Add
stage.piquetickets.comDNS record in Cloudflare - Verify DNS propagation
- Ensure Cloudflare API token has Workers deployment permissions
Phase 5: Testing¶
- Create a test branch from
stageand push to trigger stage deployment - Verify stage deployment at
https://stage.piquetickets.com - Test stage environment functionality
- Merge to
mainbranch to trigger production deployment (when ready) - Verify production deployment at
https://piquetickets.com
Phase 6: Monitoring¶
- Monitor deployment logs in GitHub Actions
- Check Cloudflare Workers dashboard for deployment status
- 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¶
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)