Cloudflare Setup
Automate Cloudflare workflows: DNS setup, Clerk integration, Vercel deployment, email routing, and R2 storage.
Prerequisites
Authentication (Choose One)
Option 1: API Token (Recommended)
# Add to .env.local CLOUDFLARE_API_TOKEN="your-api-token" CLOUDFLARE_ACCOUNT_ID="your-account-id"
Create token at: https://dash.cloudflare.com/profile/api-tokens Required permissions:
- Zone:DNS:Edit
- Zone:Zone:Read
- Email Routing Addresses:Edit
- Email Routing Rules:Edit
- Account:R2:Edit (for R2 storage)
Option 2: Wrangler CLI
# Install wrangler bun add -g wrangler # Login (opens browser) wrangler login # Verify wrangler whoami
Other Tools
# Vercel CLI (required) bun add -g vercel vercel login
Workflow
When setting up a new domain, follow these steps:
Step 1: Gather Information
Ask the user for:
- Domain name (e.g.,
example.com) - Clerk DNS records (paste from Clerk dashboard)
- Vercel project name (e.g.,
my-app) - Email addresses to create (e.g.,
contact,support) - Redirect target email (e.g.,
[email protected])
Step 2: Get Zone ID
# If using API token curl -X GET "https://api.cloudflare.com/client/v4/zones?name=DOMAIN" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" | jq '.result[0].id' # If using wrangler wrangler pages project list # Shows associated zones
Step 3: Create DNS Records for Clerk
Clerk provides specific DNS records for each project. Common patterns:
# Example: CNAME record curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "CNAME", "name": "clerk", "content": "frontend-api.clerk.dev", "ttl": 1, "proxied": false }' # Example: TXT record for verification curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "TXT", "name": "@", "content": "clerk-verification=xxxxx", "ttl": 1 }'
Step 4: Add Domain to Vercel
# Add domain to Vercel project vercel domains add DOMAIN --scope=TEAM_SLUG # Or link to specific project vercel domains add DOMAIN PROJECT_NAME
Then create Vercel DNS records:
# A record for root domain curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "A", "name": "@", "content": "76.76.21.21", "ttl": 1, "proxied": false }' # CNAME for www subdomain curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "CNAME", "name": "www", "content": "cname.vercel-dns.com", "ttl": 1, "proxied": false }'
Step 5: Setup Email Routing
First, enable email routing for the zone (do this in Cloudflare dashboard first time).
Then create routing rules:
# Create destination address (must be verified first) curl -X POST "https://api.cloudflare.com/client/v4/accounts/ACCOUNT_ID/email/routing/addresses" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "email": "[email protected]" }' # Create routing rule for [email protected] curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/email/routing/rules" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "name": "Forward contact", "enabled": true, "matchers": [{"type": "literal", "field": "to", "value": "contact@DOMAIN"}], "actions": [{"type": "forward", "value": ["[email protected]"]}] }'
Required MX records for email routing:
# MX records for Cloudflare Email Routing curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "MX", "name": "@", "content": "route1.mx.cloudflare.net", "priority": 69, "ttl": 1 }' curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "MX", "name": "@", "content": "route2.mx.cloudflare.net", "priority": 46, "ttl": 1 }' curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "MX", "name": "@", "content": "route3.mx.cloudflare.net", "priority": 89, "ttl": 1 }' # TXT record for SPF curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "TXT", "name": "@", "content": "v=spf1 include:_spf.mx.cloudflare.net ~all", "ttl": 1 }'
Step 6: Verification Checklist
After setup, verify:
# List all DNS records curl -X GET "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result[] | {type, name, content}' # Check Vercel domain status vercel domains inspect DOMAIN # Test email routing (send test email to contact@DOMAIN)
Interactive Prompts Template
When running /cloudflare, ask:
What domain are you setting up?
> example.com
Paste the Clerk DNS records from your Clerk dashboard:
> [user pastes records]
What's the Vercel project name?
> my-saas-app
What email addresses should I create? (comma-separated)
> contact, support, hello
What email should these redirect to?
> [email protected]
Common DNS Record Types
| Type | Use Case | Proxied |
|---|---|---|
| A | Root domain to IP | No (for Vercel) |
| CNAME | Subdomain to hostname | No (for Clerk/Vercel) |
| TXT | Verification, SPF | N/A |
| MX | Email routing | N/A |
Troubleshooting
| Issue | Solution |
|---|---|
| Zone not found | Domain must be added to Cloudflare first |
| DNS propagation slow | Wait 5-10 minutes, check with dig |
| Email not forwarding | Verify destination email first |
| Vercel 404 | Check DNS proxied=false for Vercel records |
| Clerk verification failed | Ensure TXT record is on root (@) |
Useful Commands
# Check DNS propagation dig DOMAIN +short dig DOMAIN MX +short dig DOMAIN TXT +short # List zones in account curl -X GET "https://api.cloudflare.com/client/v4/zones" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result[] | {name, id}' # Delete a DNS record curl -X DELETE "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records/RECORD_ID" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
R2 Storage Setup
Setup R2 buckets for file storage: user uploads, static assets, backups.
R2 Workflow
Step 1: Determine Use Case
Ask the user:
What do you want to do with R2?
1. Create new bucket (full setup)
2. Configure existing bucket (CORS, public access)
3. Setup custom domain for bucket
Step 2: Gather Bucket Info
Bucket name?
> my-app-uploads
What will this bucket store?
1. User uploads (images, files) - needs CORS + presigned URLs
2. Static assets (public CDN) - needs public access
3. Backups (private) - no public access
> 1
Custom domain? (optional, press enter to skip)
> uploads.myapp.com
Step 3: Create Bucket
# Create bucket via wrangler wrangler r2 bucket create my-app-uploads # Or via API curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{account_id}/r2/buckets" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{"name": "my-app-uploads", "locationHint": "wnam"}'
Step 4: Configure CORS (for user uploads)
Create cors.json:
{ "corsRules": [ { "allowedOrigins": ["https://myapp.com", "http://localhost:3000"], "allowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"], "allowedHeaders": ["*"], "exposeHeaders": ["ETag", "Content-Length"], "maxAgeSeconds": 3600 } ] }
Apply CORS:
wrangler r2 bucket cors put my-app-uploads --file=cors.json
Step 5: Setup Public Access (for static assets)
Option A: Enable R2.dev subdomain (via dashboard)
- Go to R2 > Bucket > Settings > Public access
Option B: Custom domain:
# Add CNAME record curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "type": "CNAME", "name": "uploads", "content": "{account_id}.r2.cloudflarestorage.com", "ttl": 1, "proxied": true }'
Then enable custom domain in R2 bucket settings.
Step 6: Generate S3 API Credentials (for SDK access)
- Go to R2 > Manage R2 API Tokens
- Create token with Object Read & Write
- Add to
.env.local:
R2_ACCESS_KEY_ID="your-access-key" R2_SECRET_ACCESS_KEY="your-secret-key" R2_ENDPOINT="https://{account_id}.r2.cloudflarestorage.com" R2_BUCKET_NAME="my-app-uploads"
R2 Quick Commands
# List buckets wrangler r2 bucket list # Create bucket wrangler r2 bucket create BUCKET_NAME # Delete bucket wrangler r2 bucket delete BUCKET_NAME # List objects wrangler r2 object list BUCKET_NAME # Upload file wrangler r2 object put BUCKET_NAME/path/file.png --file=./local.png # View CORS config wrangler r2 bucket cors get BUCKET_NAME
R2 Use Case Presets
| Use Case | CORS | Public | Custom Domain |
|---|---|---|---|
| User uploads | Yes | No | Optional |
| Static assets/CDN | No | Yes | Recommended |
| Backups | No | No | No |
| Public downloads | No | Yes | Optional |
R2 Troubleshooting
| Issue | Solution |
|---|---|
| CORS error in browser | Add domain to allowedOrigins |
| 403 Forbidden | Check API token has R2:Edit permission |
| Custom domain not working | Ensure CNAME is proxied (orange cloud) |
| Upload fails | Verify Content-Type header matches file |