Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:59:44 +08:00
commit 7e2a535be7
8 changed files with 2972 additions and 0 deletions

View 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

View 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/