Deploy Featul to Cloudflare Workers for global edge distribution with low latency. This guide covers deployment using Wrangler CLI.
Cloudflare deployment is experimental. Vercel is the recommended production platform. See Deploy to Vercel .
Prerequisites
Before deploying to Cloudflare:
Cloudflare Account
Sign up at cloudflare.com and note your Account ID from the dashboard.
Install Wrangler
npm install -g wrangler
# or
bun add -g wrangler
Wrangler is included as a dev dependency in apps/app/package.json: {
"devDependencies" : {
"wrangler" : "^4.53.0"
}
}
Authenticate Wrangler
This opens a browser to authenticate with your Cloudflare account.
Prepare Database
Provision a PostgreSQL database accessible from Cloudflare Workers. See Database Setup . Recommended: Neon (serverless PostgreSQL over HTTP)
Cloudflare Workers Setup
Next.js on Cloudflare Workers requires specific configuration and has some limitations compared to Node.js runtime.
Create wrangler.toml
Create wrangler.toml in apps/app/:
name = "featul-app"
main = "./.worker-next/index.mjs"
compatibility_date = "2024-01-01"
compatibility_flags = [ "nodejs_compat" ]
[ build ]
command = "next build"
[ env . production ]
name = "featul-app-production"
route = { pattern = "app.yourdomain.com/*" , zone_name = "yourdomain.com" }
[ env . production . vars ]
NEXT_PUBLIC_APP_URL = "https://app.yourdomain.com"
AUTH_COOKIE_DOMAIN = "yourdomain.com"
PASSKEY_RP_ID = "yourdomain.com"
PASSKEY_RP_NAME = "Featul"
PASSKEY_ORIGIN = "https://app.yourdomain.com"
[ env . staging ]
name = "featul-app-staging"
route = { pattern = "staging.yourdomain.com/*" , zone_name = "yourdomain.com" }
Do not include secrets in wrangler.toml. Use wrangler secret to set sensitive values.
Update apps/app/next.config.ts to support Cloudflare:
const nextConfig = {
// Existing config...
// Enable standalone output for Cloudflare
output: 'standalone' ,
// Experimental Cloudflare support
experimental: {
runtime: 'experimental-edge' ,
},
}
Cloudflare Workers has a 1MB script size limit. Large dependencies may cause deployment failures.
Environment Variables
Set environment variables using Wrangler secrets:
Required Secrets
# Database
wrangler secret put DATABASE_URL
# Paste: postgresql://user:password@host/db?sslmode=require
# Authentication
wrangler secret put BETTER_AUTH_SECRET
# Paste: your-secret-min-32-chars (generate with: openssl rand -base64 32)
# Trusted origins
wrangler secret put AUTH_TRUSTED_ORIGINS
# Paste: https://yourdomain.com,https://app.yourdomain.com,https://*.yourdomain.com
Optional Secrets
# Redis
wrangler secret put UPSTASH_REDIS_REST_URL
wrangler secret put UPSTASH_REDIS_REST_TOKEN
# OAuth Providers
wrangler secret put GOOGLE_CLIENT_ID
wrangler secret put GOOGLE_CLIENT_SECRET
wrangler secret put GITHUB_CLIENT_ID
wrangler secret put GITHUB_CLIENT_SECRET
# AI Features
wrangler secret put OPENROUTER_API_KEY
# Payments
wrangler secret put POLAR_ACCESS_TOKEN
wrangler secret put POLAR_WEBHOOK_SECRET
# Monitoring
wrangler secret put SENTRY_AUTH_TOKEN
# Encryption
wrangler secret put NOTRA_CREDENTIALS_ENCRYPTION_KEY
Bulk Secret Upload
Create .env.production (do not commit):
DATABASE_URL = postgresql://...
BETTER_AUTH_SECRET = ...
AUTH_TRUSTED_ORIGINS = ...
# ... all other secrets
Upload all at once:
wrangler secret bulk .env.production
Database Migrations
Run Migrations Locally
Cloudflare Workers cannot run migrations during build. Run migrations before deployment: # Set production DATABASE_URL
export DATABASE_URL = "postgresql://user:password@host/db?sslmode=require"
# Run migrations
bun run db:migrate
Verify Schema
Check all tables are created.
Build and Deploy
Production
Staging
Development
Build Application
cd apps/app
bun run build
Deploy to Production
wrangler deploy --env production
This:
Uploads worker script to Cloudflare
Deploys to production route
Activates new version
Verify Deployment
curl https://app.yourdomain.com/api/health
Deploy to Staging
wrangler deploy --env staging
Test Staging
curl https://staging.yourdomain.com/api/health
Local Development with Wrangler
This starts a local Cloudflare Workers runtime.
Test Locally
Access at http://localhost:8787
Domain Configuration
Wildcard routing for multi-tenant subdomains requires Cloudflare Workers for Platforms (paid feature).
Add Custom Domain
Add Domain to Cloudflare
Go to Cloudflare dashboard
Click Add Site
Enter your domain: yourdomain.com
Follow DNS migration steps
Configure DNS Records
Add DNS records in Cloudflare: Type Name Content Proxy
A app 192.0.2.1 Proxied (orange cloud)
CNAME * app.yourdomain.com Proxied
The wildcard * CNAME enables workspace subdomains.
Add Routes in wrangler.toml
[ env . production ]
routes = [
{ pattern = "app.yourdomain.com/*" , zone_name = "yourdomain.com" },
{ pattern = "*.yourdomain.com/*" , zone_name = "yourdomain.com" }
]
Deploy with Routes
wrangler deploy --env production
SSL Configuration
Cloudflare automatically provides:
Universal SSL certificate (Free)
Automatic HTTPS rewrites
TLS 1.3 support
For wildcard SSL:
Go to SSL/TLS > Edge Certificates
Enable Universal SSL
Wait for certificate issuance (up to 24 hours)
Cloudflare-Specific Features
D1 Database (Alternative to PostgreSQL)
Cloudflare D1 is SQLite-based. Featul uses PostgreSQL. Migration to D1 requires schema changes.
To use D1 instead of PostgreSQL:
Create D1 database:
wrangler d1 create featul-db
Add to wrangler.toml:
[[ d1_databases ]]
binding = "DB"
database_name = "featul-db"
database_id = "your-database-id"
Migrate Drizzle schema to D1 (requires custom work)
KV Storage (Alternative to Redis)
Use Cloudflare KV for caching:
wrangler kv:namespace create "FEATUL_CACHE"
Add to wrangler.toml:
[[ kv_namespaces ]]
binding = "CACHE"
id = "your-namespace-id"
R2 Storage (File Uploads)
For file uploads, use Cloudflare R2:
wrangler r2 bucket create featul-uploads
Add to wrangler.toml:
[[ r2_buckets ]]
binding = "UPLOADS"
bucket_name = "featul-uploads"
Monitoring and Logs
View Logs
# Tail production logs
wrangler tail --env production
# Filter by status code
wrangler tail --env production --status 500
Analytics
Cloudflare Workers provides built-in analytics:
Go to Workers & Pages > featul-app
Click Analytics tab
View:
Requests per second
Success rate
CPU time
Duration
Error Tracking
Integrate Sentry for error tracking:
// apps/app/instrumentation.ts
import * as Sentry from '@sentry/nextjs'
export function register () {
if ( process . env . NEXT_RUNTIME === 'edge' ) {
Sentry . init ({
dsn: process . env . SENTRY_DSN ,
environment: process . env . VERCEL_ENV || 'development' ,
})
}
}
Limitations
Cloudflare Workers has some limitations compared to Node.js:
Be aware of these limitations when deploying to Cloudflare.
CPU Time : 50ms per request (Free), 50s (Paid)
Memory : 128MB per request
Script Size : 1MB compressed (Free), 5MB (Paid)
Subrequests : 50 per request (Free), 1000 (Paid)
Node.js APIs : Limited compatibility (enable nodejs_compat flag)
WebSockets : Not supported in all plans
For Featul specifically:
Drizzle ORM works with Neon HTTP driver
File uploads require R2 integration
Session storage requires KV or external Redis
Some Node.js dependencies may not work
CI/CD with GitHub Actions
Automate deployments with GitHub Actions:
# .github/workflows/deploy-cloudflare.yml
name : Deploy to Cloudflare
on :
push :
branches : [ main ]
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- uses : oven-sh/setup-bun@v1
with :
bun-version : 1.2.21
- name : Install dependencies
run : bun install
- name : Build
run : bun run build --filter=app
- name : Deploy to Cloudflare
uses : cloudflare/wrangler-action@v3
with :
apiToken : ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId : ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command : deploy --env production
workingDirectory : apps/app
Add secrets to GitHub:
CLOUDFLARE_API_TOKEN: From Cloudflare dashboard > API Tokens
CLOUDFLARE_ACCOUNT_ID: From Cloudflare dashboard
Troubleshooting
Deployment Fails: “Script too large”
Error: Script size exceeds 1MB limit
Solution :
Upgrade to Workers Paid plan (5MB limit)
Reduce bundle size:
# Analyze bundle
ANALYZE = true bun run build
Remove unused dependencies
Enable tree-shaking in next.config.ts
Database Connection Timeout
Error: Connection timeout
Solution :
Use Neon serverless with HTTP driver (not WebSocket)
Ensure DATABASE_URL uses pooled connection
Check Cloudflare outbound connections are allowed
Node.js API Not Supported
Error: Cannot find module 'fs'
Solution :
Add nodejs_compat to wrangler.toml:
compatibility_flags = [ "nodejs_compat" ]
Or replace with edge-compatible alternatives
Wildcard Subdomain Not Working
Solution :
Verify wildcard DNS: CNAME * app.yourdomain.com
Check route pattern in wrangler.toml: *.yourdomain.com/*
For production: Consider Workers for Platforms
Next Steps
Vercel Deployment Deploy to Vercel (recommended platform)
Environment Variables Complete environment variable reference
Database Setup Configure PostgreSQL and migrations