16 KiB
Wrangler Deployment Skill
Expert knowledge for deploying static sites to Cloudflare Pages using Wrangler CLI.
Overview
This skill provides comprehensive patterns, best practices, and troubleshooting guidance for deploying web applications to Cloudflare Pages using the official Wrangler CLI tool.
Prerequisites
Required Tools
-
Wrangler CLI (>= 3.0.0)
npm install -g wrangler -
Node.js (>= 18.0.0)
node --version # Should be >= v18.0.0 -
Cloudflare Account
- Free account sufficient for most use cases
- Pages included in all plans
- Sign up: https://dash.cloudflare.com/sign-up
Authentication Setup
# Method 1: OAuth (recommended for local development)
wrangler login
# Opens browser for authentication
# Method 2: API Token (recommended for CI/CD)
export CLOUDFLARE_API_TOKEN=your-token-here
wrangler whoami # Verify authentication
# Verify authentication
wrangler whoami
# Output should show: "You are logged in as user@example.com"
Core Concepts
Cloudflare Pages Architecture
┌─────────────────────────────────────────────┐
│ Your Source Code │
│ (React, Vue, Svelte, Next.js, etc.) │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Build Process (Local) │
│ npm run build → generates static files │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Wrangler CLI Upload │
│ wrangler pages deploy ./dist │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Cloudflare Global CDN │
│ 300+ Edge locations worldwide │
│ Automatic HTTPS, DDoS protection │
└─────────────────────────────────────────────┘
Deployment Types
1. Production Deployment
- Triggered from main/master branch
- Gets production URL:
https://project-name.pages.dev - Visible to all users
- Should be stable and tested
2. Preview Deployment
- Triggered from any other branch
- Gets unique preview URL:
https://abc123.project-name.pages.dev - Used for testing before production
- Automatically cleaned up after branch deletion
Deployment Patterns
Pattern 1: Basic Deployment
# Step 1: Build your project
npm run build
# Step 2: Deploy to Cloudflare Pages
wrangler pages deploy ./dist --project-name=my-website
When to use:
- First deployment
- Simple static sites
- Quick prototyping
- Manual deployments
Pattern 2: Branch-Based Deployment
# Production deployment (main branch)
git checkout main
npm run build
wrangler pages deploy ./dist \
--project-name=my-website \
--branch=main
# Preview deployment (feature branch)
git checkout feature/new-design
npm run build
wrangler pages deploy ./dist \
--project-name=my-website \
--branch=feature/new-design
When to use:
- Git-based workflows
- Multiple environments
- Preview before production
- Team collaboration
Pattern 3: CI/CD Deployment
# GitHub Actions example
name: Deploy to Cloudflare Pages
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: pages deploy ./dist --project-name=my-website --branch=${{ github.ref_name }}
When to use:
- Automated deployments
- Continuous deployment
- Team workflows
- Production systems
Pattern 4: Monorepo Deployment
# Deploy specific workspace from monorepo
cd packages/website
npm run build
wrangler pages deploy ./dist \
--project-name=my-website-frontend \
--branch=main
cd ../admin
npm run build
wrangler pages deploy ./dist \
--project-name=my-website-admin \
--branch=main
When to use:
- Multiple related projects
- Shared dependencies
- Coordinated deployments
- Large applications
Build Configuration
Common Framework Patterns
React (Create React App)
{
"scripts": {
"build": "react-scripts build"
}
}
Build output: build/
npm run build
wrangler pages deploy ./build --project-name=my-react-app
Vue.js
{
"scripts": {
"build": "vue-cli-service build"
}
}
Build output: dist/
npm run build
wrangler pages deploy ./dist --project-name=my-vue-app
Next.js (Static Export)
{
"scripts": {
"build": "next build && next export"
}
}
Build output: out/
npm run build
wrangler pages deploy ./out --project-name=my-nextjs-app
Svelte
{
"scripts": {
"build": "vite build"
}
}
Build output: dist/
npm run build
wrangler pages deploy ./dist --project-name=my-svelte-app
Astro
{
"scripts": {
"build": "astro build"
}
}
Build output: dist/
npm run build
wrangler pages deploy ./dist --project-name=my-astro-app
Hugo
# Hugo generates static files directly
hugo --minify
# Deploy
wrangler pages deploy ./public --project-name=my-hugo-blog
Build output: public/
Advanced Deployment Options
Include Git Metadata
wrangler pages deploy ./dist \
--project-name=my-website \
--branch=$(git branch --show-current) \
--commit-hash=$(git rev-parse HEAD) \
--commit-message="$(git log -1 --pretty=%B)"
Benefits:
- Trace deployments to source code
- Better deployment history
- Easier debugging
- Audit trail
Specify Compatibility Date
wrangler pages deploy ./dist \
--project-name=my-website \
--compatibility-date=2025-01-15
Use when:
- Using Cloudflare-specific features
- Ensuring consistent behavior
- Testing new platform features
Custom Headers
Create _headers file in your build output:
# _headers file
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
/api/*
Access-Control-Allow-Origin: *
/assets/*
Cache-Control: public, max-age=31536000, immutable
Then deploy normally - Cloudflare Pages automatically applies headers.
Redirects & Rewrites
Create _redirects file in your build output:
# _redirects file
# Redirect old blog to new location
/blog/* https://blog.example.com/:splat 301
# SPA fallback
/* /index.html 200
# Temporary redirect
/old-page /new-page 302
# Domain redirect
https://www.example.com/* https://example.com/:splat 301
Environment Variables & Secrets
Setting Secrets
# Interactive prompt (recommended for sensitive data)
wrangler pages secret put API_KEY --project-name=my-website
# From file
cat api-key.txt | wrangler pages secret put API_KEY --project-name=my-website
# From environment variable
echo "$MY_SECRET" | wrangler pages secret put API_KEY --project-name=my-website
Listing Secrets
# List all secrets (values are hidden)
wrangler pages secret list --project-name=my-website
Output:
Secret Name Uploaded At
API_KEY 2025-01-15T10:30:00Z
DATABASE_URL 2025-01-14T15:20:00Z
STRIPE_KEY 2025-01-13T09:45:00Z
Accessing Secrets in Pages Functions
// functions/api/hello.js
export async function onRequest(context) {
const apiKey = context.env.API_KEY;
return new Response(JSON.stringify({
message: 'Hello from Cloudflare Pages!',
authenticated: !!apiKey
}), {
headers: { 'Content-Type': 'application/json' }
});
}
Build-Time vs Runtime Variables
Build-time (set during npm run build):
# .env file
VITE_API_URL=https://api.example.com
NEXT_PUBLIC_GA_ID=UA-XXXXXXX
Runtime (set via Wrangler):
# Secrets accessible in Pages Functions
wrangler pages secret put STRIPE_SECRET_KEY --project-name=my-website
Troubleshooting
Issue: "Command not found: wrangler"
Cause: Wrangler CLI not installed or not in PATH
Solution:
# Install globally
npm install -g wrangler
# Or use with npx
npx wrangler pages deploy ./dist --project-name=my-website
# Verify installation
which wrangler
wrangler --version
Issue: "You are not authenticated"
Cause: Not logged in to Cloudflare
Solution:
# Login via OAuth
wrangler login
# Or set API token
export CLOUDFLARE_API_TOKEN=your-token-here
# Verify authentication
wrangler whoami
Issue: "Build directory not found"
Cause: Build hasn't run or wrong directory specified
Solution:
# Check if build directory exists
ls -la dist/ # or build/ or out/
# Run build command
npm run build
# Verify build output
ls -lah dist/
# Check common locations
for dir in dist build out public .next/out; do
[ -d "$dir" ] && echo "Found: $dir"
done
Issue: "Project not found"
Cause: Project doesn't exist in Cloudflare account
Solution:
# List existing projects
wrangler pages project list
# Create new project (happens automatically on first deploy)
wrangler pages deploy ./dist --project-name=my-new-project
# Or create via dashboard:
# https://dash.cloudflare.com/pages
Issue: "Rate limit exceeded"
Cause: Too many API requests in short time
Solution:
# Wait indicated time (usually 60 seconds)
sleep 60
# Retry deployment
wrangler pages deploy ./dist --project-name=my-website
# Check for concurrent deployments
ps aux | grep wrangler
# Verify API token isn't being used elsewhere
Issue: "File too large"
Cause: Individual file exceeds 25MB limit
Solution:
# Find large files
find dist/ -type f -size +25M
# Optimize images
# - Use modern formats (WebP, AVIF)
# - Compress with tools like imagemin
# - Use responsive images
# Split large bundles
# - Code splitting in webpack/vite
# - Dynamic imports
# - Lazy loading
# Use external hosting for large files
# - Cloudflare R2
# - AWS S3
# - CDN for media files
Issue: "Deployment successful but site not updating"
Cause: Browser cache or CDN cache
Solution:
# Hard refresh browser
# Chrome/Firefox: Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows)
# Check deployment was successful
wrangler pages deployment list --project-name=my-website
# Verify latest deployment is active
curl -I https://my-website.pages.dev
# Purge Cloudflare cache (if using custom domain)
# Via dashboard: Cache → Purge Everything
Issue: "404 errors after deployment"
Cause: SPA routing not configured
Solution:
# Create _redirects file
cat > dist/_redirects <<EOF
/* /index.html 200
EOF
# Or create _routes.json for more control
cat > dist/_routes.json <<EOF
{
"version": 1,
"include": ["/*"],
"exclude": ["/api/*"]
}
EOF
# Redeploy
wrangler pages deploy ./dist --project-name=my-website
Performance Optimization
Enable Compression
Cloudflare automatically compresses responses, but you can pre-compress:
# Pre-compress files with gzip
find dist/ -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" \) -exec gzip -k {} \;
# Pre-compress with brotli
find dist/ -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" \) -exec brotli -k {} \;
Optimize Assets
# Minify JavaScript and CSS (usually done by build tool)
npm run build # with production mode
# Optimize images
npx @squoosh/cli --output-dir dist/images optimized/ *.{jpg,png}
# Remove source maps in production
# (configure in webpack/vite config)
Cache Control Headers
# _headers file
/static/*
Cache-Control: public, max-age=31536000, immutable
/*.html
Cache-Control: public, max-age=0, must-revalidate
/api/*
Cache-Control: private, no-cache
Deployment Verification
Automated Checks
#!/bin/bash
# deploy-verify.sh
PROJECT_NAME="my-website"
DEPLOY_URL="https://$PROJECT_NAME.pages.dev"
# Deploy
wrangler pages deploy ./dist --project-name=$PROJECT_NAME
# Wait for deployment to propagate
sleep 10
# Check HTTP status
STATUS=$(curl -o /dev/null -s -w "%{http_code}" $DEPLOY_URL)
if [ "$STATUS" -eq 200 ]; then
echo "✅ Deployment successful: $DEPLOY_URL"
else
echo "❌ Deployment failed: HTTP $STATUS"
exit 1
fi
# Check critical pages
for page in "/about" "/contact" "/blog"; do
STATUS=$(curl -o /dev/null -s -w "%{http_code}" "$DEPLOY_URL$page")
if [ "$STATUS" -eq 200 ]; then
echo "✅ $page is accessible"
else
echo "⚠️ $page returned HTTP $STATUS"
fi
done
Manual Verification Checklist
- Deployment URL accessible
- Homepage loads correctly
- All pages render properly
- Images load
- Forms work
- API endpoints respond
- Mobile layout correct
- HTTPS enabled
- Performance acceptable (Lighthouse check)
- No console errors
Best Practices
1. Version Control Integration
# Tag releases
git tag -a v1.0.0 -m "Production release v1.0.0"
git push --tags
# Deploy with version info
wrangler pages deploy ./dist \
--project-name=my-website \
--commit-hash=$(git rev-parse HEAD)
2. Environment-Specific Builds
# Development build
NODE_ENV=development npm run build
wrangler pages deploy ./dist --project-name=my-website --branch=dev
# Production build
NODE_ENV=production npm run build
wrangler pages deploy ./dist --project-name=my-website --branch=main
3. Security Headers
# _headers file
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: accelerometer=(), camera=(), geolocation=(), microphone=()
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
4. Clean Build Before Deploy
# Remove old build artifacts
rm -rf dist/
# Fresh build
npm run build
# Verify build output
ls -lah dist/
# Deploy
wrangler pages deploy ./dist --project-name=my-website
5. Rollback Strategy
# List recent deployments
wrangler pages deployment list --project-name=my-website
# Save deployment IDs
# Production: abc123def456
# Previous: xyz789ghi012
# Rollback if needed
wrangler pages deployment promote xyz789ghi012 --project-name=my-website
Resources
- Wrangler CLI Docs: https://developers.cloudflare.com/workers/wrangler/
- Pages Platform Docs: https://developers.cloudflare.com/pages/
- Direct Upload Guide: https://developers.cloudflare.com/pages/get-started/direct-upload/
- Framework Guides: https://developers.cloudflare.com/pages/framework-guides/
- Cloudflare Community: https://community.cloudflare.com/
- Status Page: https://www.cloudflarestatus.com/