Initial commit
This commit is contained in:
821
skills/cloudflare-pages-config/SKILL.md
Normal file
821
skills/cloudflare-pages-config/SKILL.md
Normal file
@@ -0,0 +1,821 @@
|
||||
# Cloudflare Pages Configuration Skill
|
||||
|
||||
Expert knowledge for configuring Cloudflare Pages projects, managing environment variables, custom domains, and build settings.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides comprehensive patterns and best practices for configuring and managing Cloudflare Pages projects through Wrangler CLI and the Cloudflare Dashboard.
|
||||
|
||||
## Configuration File: wrangler.toml
|
||||
|
||||
### Basic Structure
|
||||
|
||||
```toml
|
||||
# Project identification
|
||||
name = "my-pages-project"
|
||||
|
||||
# Compatibility date (use latest for new features)
|
||||
compatibility_date = "2025-01-15"
|
||||
|
||||
# Build configuration
|
||||
[build]
|
||||
command = "npm run build"
|
||||
cwd = "."
|
||||
watch_dirs = ["src", "public"]
|
||||
|
||||
# Upload configuration
|
||||
[[build.upload]]
|
||||
format = "directory"
|
||||
dir = "dist"
|
||||
|
||||
# Production environment
|
||||
[env.production]
|
||||
# Production-specific settings
|
||||
|
||||
# Preview environment
|
||||
[env.preview]
|
||||
# Preview-specific settings
|
||||
```
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
```toml
|
||||
name = "advanced-pages-project"
|
||||
compatibility_date = "2025-01-15"
|
||||
|
||||
# Build configuration with custom Node version
|
||||
[build]
|
||||
command = "npm ci && npm run build"
|
||||
cwd = "."
|
||||
watch_dirs = ["src", "public", "content"]
|
||||
|
||||
# Node.js version selection
|
||||
[build.env_vars]
|
||||
NODE_VERSION = "18"
|
||||
NODE_ENV = "production"
|
||||
|
||||
# Upload multiple directories
|
||||
[[build.upload]]
|
||||
format = "directory"
|
||||
dir = "dist"
|
||||
exclusions = ["*.map", "*.md"]
|
||||
|
||||
# Production environment configuration
|
||||
[env.production]
|
||||
workers_dev = false
|
||||
route = "example.com/*"
|
||||
|
||||
# Staging environment
|
||||
[env.staging]
|
||||
workers_dev = false
|
||||
route = "staging.example.com/*"
|
||||
|
||||
# Preview environment
|
||||
[env.preview]
|
||||
workers_dev = true
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Types of Variables
|
||||
|
||||
**1. Build-Time Variables** (Set during build)
|
||||
- Embedded in compiled code
|
||||
- Public (visible in browser)
|
||||
- Set via `.env` files or build scripts
|
||||
- Examples: API URLs, feature flags, public IDs
|
||||
|
||||
**2. Runtime Variables** (Secrets)
|
||||
- Accessed in Pages Functions
|
||||
- Private (not visible in browser)
|
||||
- Set via Wrangler CLI
|
||||
- Examples: API keys, database credentials
|
||||
|
||||
### Setting Build-Time Variables
|
||||
|
||||
```bash
|
||||
# .env file (for local development)
|
||||
VITE_API_URL=https://api.example.com
|
||||
NEXT_PUBLIC_GA_ID=UA-XXXXXXX-1
|
||||
PUBLIC_FEATURE_FLAG=true
|
||||
|
||||
# Build with variables
|
||||
npm run build
|
||||
```
|
||||
|
||||
Common frameworks:
|
||||
- **Vite**: `VITE_*` prefix
|
||||
- **Next.js**: `NEXT_PUBLIC_*` prefix
|
||||
- **Create React App**: `REACT_APP_*` prefix
|
||||
- **Nuxt**: `NUXT_PUBLIC_*` prefix
|
||||
- **SvelteKit**: `PUBLIC_*` prefix
|
||||
|
||||
### Setting Runtime Secrets
|
||||
|
||||
```bash
|
||||
# Set individual secret
|
||||
wrangler pages secret put API_KEY --project-name=my-website
|
||||
|
||||
# Set multiple secrets
|
||||
wrangler pages secret put DATABASE_URL --project-name=my-website
|
||||
wrangler pages secret put STRIPE_SECRET --project-name=my-website
|
||||
wrangler pages secret put AUTH_SECRET --project-name=my-website
|
||||
|
||||
# List secrets (values are hidden)
|
||||
wrangler pages secret list --project-name=my-website
|
||||
|
||||
# Delete secret
|
||||
wrangler pages secret delete API_KEY --project-name=my-website
|
||||
```
|
||||
|
||||
### Bulk Secret Management
|
||||
|
||||
```bash
|
||||
# Script to set multiple secrets from file
|
||||
#!/bin/bash
|
||||
PROJECT_NAME="my-website"
|
||||
|
||||
# Read from .env.production (DO NOT commit this file!)
|
||||
while IFS='=' read -r key value; do
|
||||
# Skip empty lines and comments
|
||||
[[ -z "$key" || "$key" =~ ^#.* ]] && continue
|
||||
|
||||
# Set secret
|
||||
echo "$value" | wrangler pages secret put "$key" --project-name="$PROJECT_NAME"
|
||||
echo "✓ Set secret: $key"
|
||||
done < .env.production
|
||||
|
||||
echo "✅ All secrets configured"
|
||||
```
|
||||
|
||||
### Accessing Secrets in Pages Functions
|
||||
|
||||
```javascript
|
||||
// functions/api/data.js
|
||||
|
||||
export async function onRequest(context) {
|
||||
// Access runtime secrets
|
||||
const apiKey = context.env.API_KEY;
|
||||
const dbUrl = context.env.DATABASE_URL;
|
||||
|
||||
// Use in API calls
|
||||
const response = await fetch('https://api.example.com/data', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Domains
|
||||
|
||||
### Adding Custom Domain
|
||||
|
||||
**Via Cloudflare Dashboard:**
|
||||
1. Navigate to: https://dash.cloudflare.com/
|
||||
2. Select your account
|
||||
3. Go to: Workers & Pages → Your Project → Custom domains
|
||||
4. Click "Set up a custom domain"
|
||||
5. Enter domain name (e.g., `www.example.com`)
|
||||
6. Follow DNS instructions
|
||||
|
||||
### DNS Configuration
|
||||
|
||||
**Option 1: CNAME (Recommended)**
|
||||
```
|
||||
Type: CNAME
|
||||
Name: www
|
||||
Content: your-project.pages.dev
|
||||
Proxy: Enabled (orange cloud)
|
||||
```
|
||||
|
||||
**Option 2: A Record (Apex domain)**
|
||||
```
|
||||
Type: A
|
||||
Name: @
|
||||
Content: [Cloudflare IP provided in dashboard]
|
||||
Proxy: Enabled (orange cloud)
|
||||
```
|
||||
|
||||
**Option 3: AAAA Record (IPv6)**
|
||||
```
|
||||
Type: AAAA
|
||||
Name: @
|
||||
Content: [Cloudflare IPv6 provided in dashboard]
|
||||
Proxy: Enabled (orange cloud)
|
||||
```
|
||||
|
||||
### Redirecting Apex to www
|
||||
|
||||
```
|
||||
# _redirects file
|
||||
https://example.com/* https://www.example.com/:splat 301
|
||||
```
|
||||
|
||||
### Multiple Custom Domains
|
||||
|
||||
You can add multiple custom domains to one project:
|
||||
|
||||
```
|
||||
Primary: www.example.com
|
||||
Additional:
|
||||
- example.com (redirects to www)
|
||||
- app.example.com
|
||||
- beta.example.com
|
||||
```
|
||||
|
||||
Each gets automatic HTTPS certificate.
|
||||
|
||||
## Headers Configuration
|
||||
|
||||
### Creating _headers File
|
||||
|
||||
```
|
||||
# _headers file (place in build output directory)
|
||||
|
||||
# Global headers (apply to all pages)
|
||||
/*
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
X-XSS-Protection: 1; mode=block
|
||||
Referrer-Policy: strict-origin-when-cross-origin
|
||||
Permissions-Policy: geolocation=(), microphone=(), camera=()
|
||||
|
||||
# Security headers for HTML pages
|
||||
/*.html
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
|
||||
X-Frame-Options: SAMEORIGIN
|
||||
|
||||
# Cache static assets aggressively
|
||||
/assets/*
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
|
||||
/static/*
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
|
||||
# Don't cache HTML
|
||||
/*.html
|
||||
Cache-Control: public, max-age=0, must-revalidate
|
||||
|
||||
# API route headers
|
||||
/api/*
|
||||
Access-Control-Allow-Origin: *
|
||||
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
|
||||
Access-Control-Allow-Headers: Content-Type, Authorization
|
||||
Content-Type: application/json
|
||||
|
||||
# Font files
|
||||
/*.woff2
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
Access-Control-Allow-Origin: *
|
||||
```
|
||||
|
||||
### Common Header Patterns
|
||||
|
||||
#### Security Headers (Recommended)
|
||||
|
||||
```
|
||||
/*
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
X-XSS-Protection: 1; mode=block
|
||||
Referrer-Policy: strict-origin-when-cross-origin
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
|
||||
```
|
||||
|
||||
#### CORS Headers
|
||||
|
||||
```
|
||||
/api/*
|
||||
Access-Control-Allow-Origin: https://example.com
|
||||
Access-Control-Allow-Methods: GET, POST, OPTIONS
|
||||
Access-Control-Allow-Headers: Content-Type, Authorization
|
||||
Access-Control-Max-Age: 86400
|
||||
```
|
||||
|
||||
#### Cache Headers
|
||||
|
||||
```
|
||||
# Static assets - cache forever
|
||||
/assets/*
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
|
||||
# HTML - never cache
|
||||
/*.html
|
||||
Cache-Control: public, max-age=0, must-revalidate
|
||||
|
||||
# API responses - don't cache
|
||||
/api/*
|
||||
Cache-Control: no-store, no-cache, must-revalidate
|
||||
```
|
||||
|
||||
## Redirects & Rewrites
|
||||
|
||||
### Creating _redirects File
|
||||
|
||||
```
|
||||
# _redirects file (place in build output directory)
|
||||
|
||||
# Redirect old URLs
|
||||
/old-page /new-page 301
|
||||
/blog/old-post /blog/new-post 301
|
||||
|
||||
# Redirect with splat (wildcard)
|
||||
/blog/* /posts/:splat 301
|
||||
|
||||
# Redirect entire domain
|
||||
https://old-domain.com/* https://new-domain.com/:splat 301
|
||||
|
||||
# Force HTTPS (Cloudflare does this by default, but explicit)
|
||||
http://example.com/* https://example.com/:splat 301
|
||||
|
||||
# Redirect apex to www
|
||||
https://example.com/* https://www.example.com/:splat 301
|
||||
|
||||
# SPA fallback (serve index.html for all routes)
|
||||
/* /index.html 200
|
||||
|
||||
# API proxy (rewrite without redirect)
|
||||
/api/* https://backend.example.com/:splat 200
|
||||
|
||||
# Temporary redirect
|
||||
/maintenance /503.html 302
|
||||
|
||||
# Redirect based on path parameter
|
||||
/user/:id /users/:id 301
|
||||
```
|
||||
|
||||
### Advanced Redirect Patterns
|
||||
|
||||
#### Country-Based Redirects
|
||||
|
||||
```
|
||||
# _redirects
|
||||
/ /en 302 Country=US
|
||||
/ /fr 302 Country=FR
|
||||
/ /de 302 Country=DE
|
||||
/ /es 302 Country=ES
|
||||
```
|
||||
|
||||
#### Language Redirects
|
||||
|
||||
```
|
||||
# _redirects
|
||||
/ /en 302 Language=en
|
||||
/ /fr 302 Language=fr
|
||||
/ /es 302 Language=es
|
||||
```
|
||||
|
||||
#### Conditional Redirects
|
||||
|
||||
```
|
||||
# _redirects
|
||||
# Redirect mobile users to mobile site
|
||||
/ /mobile 302 Condition=mobile
|
||||
|
||||
# A/B testing
|
||||
/ /variant-a 302 Cookie=ab_test=variant_a
|
||||
/ /variant-b 302 Cookie=ab_test=variant_b
|
||||
```
|
||||
|
||||
## Build Configuration Patterns
|
||||
|
||||
### Framework-Specific Configurations
|
||||
|
||||
#### Next.js Static Export
|
||||
|
||||
```toml
|
||||
name = "nextjs-pages-site"
|
||||
compatibility_date = "2025-01-15"
|
||||
|
||||
[build]
|
||||
command = "npm run build && npm run export"
|
||||
|
||||
[[build.upload]]
|
||||
format = "directory"
|
||||
dir = "out"
|
||||
```
|
||||
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"export": "next export"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Vite + React
|
||||
|
||||
```toml
|
||||
name = "vite-react-site"
|
||||
compatibility_date = "2025-01-15"
|
||||
|
||||
[build]
|
||||
command = "npm run build"
|
||||
|
||||
[[build.upload]]
|
||||
format = "directory"
|
||||
dir = "dist"
|
||||
```
|
||||
|
||||
#### Astro
|
||||
|
||||
```toml
|
||||
name = "astro-site"
|
||||
compatibility_date = "2025-01-15"
|
||||
|
||||
[build]
|
||||
command = "npm run build"
|
||||
|
||||
[[build.upload]]
|
||||
format = "directory"
|
||||
dir = "dist"
|
||||
```
|
||||
|
||||
#### Hugo
|
||||
|
||||
```toml
|
||||
name = "hugo-blog"
|
||||
compatibility_date = "2025-01-15"
|
||||
|
||||
[build]
|
||||
command = "hugo --minify"
|
||||
|
||||
[[build.upload]]
|
||||
format = "directory"
|
||||
dir = "public"
|
||||
```
|
||||
|
||||
### Monorepo Configuration
|
||||
|
||||
```toml
|
||||
name = "frontend-monorepo"
|
||||
compatibility_date = "2025-01-15"
|
||||
|
||||
[build]
|
||||
command = "cd packages/website && npm run build"
|
||||
cwd = "../.."
|
||||
watch_dirs = ["packages/website/src"]
|
||||
|
||||
[[build.upload]]
|
||||
format = "directory"
|
||||
dir = "packages/website/dist"
|
||||
```
|
||||
|
||||
## Pages Functions Configuration
|
||||
|
||||
### Function Routes
|
||||
|
||||
Create `functions` directory in your project:
|
||||
|
||||
```
|
||||
project/
|
||||
├── public/ # Static files
|
||||
├── functions/ # Pages Functions (API routes)
|
||||
│ ├── api/
|
||||
│ │ ├── hello.js
|
||||
│ │ └── users/
|
||||
│ │ └── [id].js
|
||||
│ └── _middleware.js
|
||||
└── wrangler.toml
|
||||
```
|
||||
|
||||
### Function Example
|
||||
|
||||
```javascript
|
||||
// functions/api/hello.js
|
||||
export async function onRequest(context) {
|
||||
return new Response(JSON.stringify({
|
||||
message: 'Hello from Cloudflare Pages!',
|
||||
timestamp: new Date().toISOString()
|
||||
}), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Routes
|
||||
|
||||
```javascript
|
||||
// functions/api/users/[id].js
|
||||
export async function onRequest(context) {
|
||||
const id = context.params.id;
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
userId: id,
|
||||
name: `User ${id}`
|
||||
}), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
```javascript
|
||||
// functions/_middleware.js
|
||||
export async function onRequest(context) {
|
||||
// Add custom header to all requests
|
||||
const response = await context.next();
|
||||
response.headers.set('X-Custom-Header', 'My Value');
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
## Project Organization Best Practices
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
cloudflare-pages-project/
|
||||
├── .env.example # Template for environment variables
|
||||
├── .env # Local development (git-ignored)
|
||||
├── .env.production # Production secrets (git-ignored)
|
||||
├── .gitignore # Exclude secrets and build artifacts
|
||||
├── wrangler.toml # Cloudflare Pages configuration
|
||||
├── package.json # Dependencies and scripts
|
||||
├── src/ # Source code
|
||||
│ ├── components/
|
||||
│ ├── pages/
|
||||
│ └── utils/
|
||||
├── public/ # Static assets (copied as-is)
|
||||
│ ├── _headers # Custom headers
|
||||
│ ├── _redirects # Redirects and rewrites
|
||||
│ ├── robots.txt
|
||||
│ └── favicon.ico
|
||||
├── functions/ # Pages Functions (API routes)
|
||||
│ ├── api/
|
||||
│ │ └── endpoint.js
|
||||
│ └── _middleware.js
|
||||
└── dist/ # Build output (git-ignored)
|
||||
```
|
||||
|
||||
### gitignore Configuration
|
||||
|
||||
```gitignore
|
||||
# Environment variables (CRITICAL - never commit secrets!)
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
.env.*.local
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
.next/
|
||||
.output/
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Wrangler
|
||||
.wrangler/
|
||||
```
|
||||
|
||||
### Environment Variable Templates
|
||||
|
||||
```bash
|
||||
# .env.example (commit this)
|
||||
# Copy to .env for local development
|
||||
|
||||
# Build-time variables (public)
|
||||
VITE_API_URL=https://api.example.com
|
||||
VITE_GA_ID=UA-XXXXXXX-X
|
||||
|
||||
# Runtime secrets (private) - set via: wrangler pages secret put
|
||||
# API_KEY=your-api-key-here
|
||||
# DATABASE_URL=your-database-url
|
||||
# STRIPE_SECRET=your-stripe-secret
|
||||
```
|
||||
|
||||
## Multi-Environment Setup
|
||||
|
||||
### Environment Strategy
|
||||
|
||||
```
|
||||
Production (main branch)
|
||||
├── URL: https://project.pages.dev
|
||||
├── Custom domain: https://www.example.com
|
||||
└── Secrets: Production API keys
|
||||
|
||||
Staging (staging branch)
|
||||
├── URL: https://staging.project.pages.dev
|
||||
├── Custom domain: https://staging.example.com
|
||||
└── Secrets: Staging API keys
|
||||
|
||||
Preview (all other branches)
|
||||
├── URL: https://[commit-hash].project.pages.dev
|
||||
└── Secrets: Shared preview secrets
|
||||
```
|
||||
|
||||
### Branch-Based Configuration
|
||||
|
||||
```toml
|
||||
# wrangler.toml
|
||||
|
||||
name = "my-multi-env-project"
|
||||
compatibility_date = "2025-01-15"
|
||||
|
||||
[build]
|
||||
command = "npm run build"
|
||||
|
||||
[[build.upload]]
|
||||
dir = "dist"
|
||||
|
||||
[env.production]
|
||||
# Production-specific config
|
||||
route = "www.example.com/*"
|
||||
|
||||
[env.staging]
|
||||
# Staging-specific config
|
||||
route = "staging.example.com/*"
|
||||
```
|
||||
|
||||
## Monitoring & Analytics
|
||||
|
||||
### Built-in Analytics
|
||||
|
||||
View analytics in Cloudflare Dashboard:
|
||||
- Requests per second
|
||||
- Bandwidth usage
|
||||
- Cache hit rate
|
||||
- Unique visitors
|
||||
- Geographic distribution
|
||||
|
||||
Access: https://dash.cloudflare.com/ → Pages → Your Project → Analytics
|
||||
|
||||
### Custom Analytics Integration
|
||||
|
||||
```html
|
||||
<!-- Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'GA_MEASUREMENT_ID');
|
||||
</script>
|
||||
|
||||
<!-- Plausible Analytics (privacy-friendly) -->
|
||||
<script defer data-domain="example.com" src="https://plausible.io/js/script.js"></script>
|
||||
```
|
||||
|
||||
### Real User Monitoring (RUM)
|
||||
|
||||
```javascript
|
||||
// functions/_middleware.js
|
||||
export async function onRequest(context) {
|
||||
const start = Date.now();
|
||||
const response = await context.next();
|
||||
const duration = Date.now() - start;
|
||||
|
||||
// Log performance metric
|
||||
console.log(`Request took ${duration}ms`);
|
||||
|
||||
// Add timing header
|
||||
response.headers.set('Server-Timing', `total;dur=${duration}`);
|
||||
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
## Security Configuration
|
||||
|
||||
### Content Security Policy
|
||||
|
||||
```
|
||||
# _headers
|
||||
/*
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
|
||||
```
|
||||
|
||||
### Rate Limiting (via Pages Functions)
|
||||
|
||||
```javascript
|
||||
// functions/_middleware.js
|
||||
const rateLimitMap = new Map();
|
||||
|
||||
export async function onRequest(context) {
|
||||
const clientIP = context.request.headers.get('CF-Connecting-IP');
|
||||
const now = Date.now();
|
||||
const windowMs = 60000; // 1 minute
|
||||
const maxRequests = 100;
|
||||
|
||||
if (!rateLimitMap.has(clientIP)) {
|
||||
rateLimitMap.set(clientIP, { count: 1, resetTime: now + windowMs });
|
||||
} else {
|
||||
const record = rateLimitMap.get(clientIP);
|
||||
|
||||
if (now > record.resetTime) {
|
||||
record.count = 1;
|
||||
record.resetTime = now + windowMs;
|
||||
} else {
|
||||
record.count++;
|
||||
if (record.count > maxRequests) {
|
||||
return new Response('Too Many Requests', { status: 429 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return context.next();
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting Common Configuration Issues
|
||||
|
||||
### Issue: Environment Variables Not Working
|
||||
|
||||
**Build-time variables:**
|
||||
```bash
|
||||
# Check framework prefix
|
||||
VITE_API_URL=... # Vite
|
||||
NEXT_PUBLIC_API=... # Next.js
|
||||
REACT_APP_API=... # CRA
|
||||
|
||||
# Verify build includes variables
|
||||
npm run build
|
||||
grep -r "api.example.com" dist/
|
||||
```
|
||||
|
||||
**Runtime secrets:**
|
||||
```bash
|
||||
# Verify secrets are set
|
||||
wrangler pages secret list --project-name=my-website
|
||||
|
||||
# Check function can access
|
||||
cat > functions/test.js <<EOF
|
||||
export async function onRequest(context) {
|
||||
return new Response(JSON.stringify({
|
||||
hasApiKey: !!context.env.API_KEY
|
||||
}));
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Issue: Custom Domain Not Working
|
||||
|
||||
**Checklist:**
|
||||
1. DNS records added? (Check Dashboard)
|
||||
2. DNS propagated? (Check with `dig` or `nslookup`)
|
||||
3. SSL certificate issued? (Can take up to 24 hours)
|
||||
4. Proxying enabled? (Orange cloud in DNS settings)
|
||||
|
||||
```bash
|
||||
# Check DNS
|
||||
dig www.example.com +short
|
||||
|
||||
# Check HTTPS
|
||||
curl -I https://www.example.com
|
||||
```
|
||||
|
||||
### Issue: _headers or _redirects Not Applied
|
||||
|
||||
**Verify file location:**
|
||||
```bash
|
||||
# Must be in build output directory
|
||||
ls -la dist/_headers
|
||||
ls -la dist/_redirects
|
||||
|
||||
# Check after build
|
||||
npm run build
|
||||
ls -la dist/
|
||||
```
|
||||
|
||||
**Test redirect:**
|
||||
```bash
|
||||
curl -I https://my-website.pages.dev/old-page
|
||||
# Should show: Location: /new-page
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **Pages Configuration**: https://developers.cloudflare.com/pages/configuration/
|
||||
- **Build Configuration**: https://developers.cloudflare.com/pages/platform/build-configuration/
|
||||
- **Custom Domains**: https://developers.cloudflare.com/pages/platform/custom-domains/
|
||||
- **Headers**: https://developers.cloudflare.com/pages/platform/headers/
|
||||
- **Redirects**: https://developers.cloudflare.com/pages/platform/redirects/
|
||||
- **Pages Functions**: https://developers.cloudflare.com/pages/platform/functions/
|
||||
- **Environment Variables**: https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables
|
||||
755
skills/wrangler-deployment/SKILL.md
Normal file
755
skills/wrangler-deployment/SKILL.md
Normal file
@@ -0,0 +1,755 @@
|
||||
# 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
|
||||
|
||||
1. **Wrangler CLI** (>= 3.0.0)
|
||||
```bash
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
2. **Node.js** (>= 18.0.0)
|
||||
```bash
|
||||
node --version # Should be >= v18.0.0
|
||||
```
|
||||
|
||||
3. **Cloudflare Account**
|
||||
- Free account sufficient for most use cases
|
||||
- Pages included in all plans
|
||||
- Sign up: https://dash.cloudflare.com/sign-up
|
||||
|
||||
### Authentication Setup
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```yaml
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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)
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "react-scripts build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Build output: `build/`
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
wrangler pages deploy ./build --project-name=my-react-app
|
||||
```
|
||||
|
||||
#### Vue.js
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "vue-cli-service build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Build output: `dist/`
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
wrangler pages deploy ./dist --project-name=my-vue-app
|
||||
```
|
||||
|
||||
#### Next.js (Static Export)
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "next build && next export"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Build output: `out/`
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
wrangler pages deploy ./out --project-name=my-nextjs-app
|
||||
```
|
||||
|
||||
#### Svelte
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Build output: `dist/`
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
wrangler pages deploy ./dist --project-name=my-svelte-app
|
||||
```
|
||||
|
||||
#### Astro
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "astro build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Build output: `dist/`
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
wrangler pages deploy ./dist --project-name=my-astro-app
|
||||
```
|
||||
|
||||
#### Hugo
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```javascript
|
||||
// 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`):
|
||||
```bash
|
||||
# .env file
|
||||
VITE_API_URL=https://api.example.com
|
||||
NEXT_PUBLIC_GA_ID=UA-XXXXXXX
|
||||
```
|
||||
|
||||
**Runtime** (set via Wrangler):
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
#!/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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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/
|
||||
Reference in New Issue
Block a user