cloudflare-pages
npx skills add https://github.com/serendipityoneinc/srp-claude-code-marketplace --skill cloudflare-pages
Agent 安装分布
Skill 文档
Cloudflare Pages Skill
Cloudflare Pages is a JAMstack platform for deploying full-stack applications to Cloudflare’s global network. It combines static site hosting with serverless Pages Functions, automatic Git deployments, and native integrations with the Cloudflare ecosystem (Workers, KV, R2, D1, Durable Objects).
Key Value Proposition: Deploy static sites and full-stack applications with zero configuration, automatic preview deployments, unlimited bandwidth, and seamless access to Cloudflare’s edge computing and storage services.
When to Use This Skill
- Deploying static sites or JAMstack applications
- Building full-stack apps with serverless functions
- Configuring Pages Functions with bindings (KV, R2, D1)
- Setting up Git-based CI/CD with preview deployments
- Configuring headers, redirects, and routing
- Troubleshooting build or deployment issues
- Migrating from other hosting platforms to Pages
When NOT to Use This Skill
- For Cloudflare Workers alone (use workers skill)
- For general Cloudflare CDN/DNS configuration
- For non-web applications or APIs without UI
- For container-based deployments (Pages is serverless)
Core Concepts
Architecture Overview
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â Cloudflare Pages â
â Global Edge Network (300+ PoPs) â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â
âââââââââââââââââââââââ¼ââââââââââââââââââââââ
â â â
â¼ â¼ â¼
âââââââââââââââââ âââââââââââââââââ âââââââââââââââââ
â Static Assets â â Pages Functionsâ â Bindings â
ââââââââââââââââ⤠ââââââââââââââââ⤠âââââââââââââââââ¤
â ⢠HTML/CSS/JS â â ⢠/functions/ â â ⢠KV â
â ⢠Images â â ⢠Middleware â â ⢠R2 â
â ⢠_headers â â ⢠Routing â â ⢠D1 â
â ⢠_redirects â â ⢠TypeScript â â ⢠DO â
â ⢠_routes.jsonâ â ⢠Workers API â â ⢠Queues â
âââââââââââââââââ âââââââââââââââââ â ⢠AI â
â â âââââââââââââââââ
âââââââââââââââââââââââ¼ââââââââââââââââââââââ
â
âââââââââââââââââââââââ¼ââââââââââââââââââââââ
â¼ â¼ â¼
âââââââââââââââââ âââââââââââââââââ âââââââââââââââââ
â Deployment â â Domains â â Builds â
ââââââââââââââââ⤠ââââââââââââââââ⤠âââââââââââââââââ¤
â ⢠Git (GH/GL) â â ⢠Custom â â ⢠20 min max â
â ⢠Direct â â ⢠*.pages.dev â â ⢠500/mo free â
â ⢠Wrangler â â ⢠Preview URLsâ â ⢠Env vars â
â ⢠API â â â â ⢠Presets â
âââââââââââââââââ âââââââââââââââââ âââââââââââââââââ
Platform Limits
| Resource | Free | Pro | Business | Enterprise |
|---|---|---|---|---|
| Builds/month | 500 | 5,000 | 20,000 | Unlimited |
| Custom domains | 100 | 250 | 500 | 500 |
| Files per site | 20,000 | 20,000 | 20,000 | 20,000 |
| File size | 25 MB | 25 MB | 25 MB | 25 MB |
| Preview deployments | Unlimited | Unlimited | Unlimited | Unlimited |
| Build timeout | 20 min | 20 min | 20 min | 20 min |
Configuration Limits
| Config | Limit |
|---|---|
| Header rules | 100 max |
| Static redirects | 2,000 max |
| Dynamic redirects | 100 max |
| Redirect line length | 1,000 chars |
| Header line length | 2,000 chars |
| Projects per account | 100 (soft) |
Quick Start
Deploy with Git Integration
# 1. Connect repository in Cloudflare Dashboard
# Workers & Pages > Create application > Connect to Git
# 2. Configure build settings
# - Production branch: main
# - Build command: npm run build
# - Build output: dist
# 3. Push to deploy
git push origin main
Deploy with Wrangler CLI
# Install Wrangler
npm install -g wrangler
# Login to Cloudflare
wrangler login
# Create a new project
npx wrangler pages project create my-site
# Deploy to production
npx wrangler pages deploy ./dist
# Deploy preview
npx wrangler pages deploy ./dist --branch=staging
Deploy via Dashboard
- Go to Workers & Pages > Create application
- Click Get started under Pages
- Drag and drop your build folder or zip file
- Enter project name and deploy
Build Configuration
Framework Presets
Cloudflare provides presets for 31+ frameworks:
| Framework | Build Command | Output Directory |
|---|---|---|
| React (Vite) | npm run build |
dist |
| Next.js | npx @cloudflare/next-on-pages@1 |
.vercel/output/static |
| Astro | npm run build |
dist |
| SvelteKit | npm run build |
.svelte-kit/cloudflare |
| Nuxt | npm run build |
dist |
| Remix | npm run build |
public |
| Vue (Vite) | npm run build |
dist |
| Angular | npm run build |
dist/<project> |
| Gatsby | gatsby build |
public |
| Hugo | hugo |
public |
| Docusaurus | npm run build |
build |
| Eleventy | npx @11ty/eleventy |
_site |
System Environment Variables
These are automatically injected during builds:
| Variable | Description | Example |
|---|---|---|
CF_PAGES |
Always 1 on Pages |
1 |
CF_PAGES_COMMIT_SHA |
Git commit hash | a1b2c3d... |
CF_PAGES_BRANCH |
Branch name | main |
CF_PAGES_URL |
Deployment URL | https://abc.pages.dev |
CI |
Always true |
true |
Custom Environment Variables
# Set via dashboard: Settings > Environment variables
# Or in wrangler.toml
[vars]
API_URL = "https://api.example.com"
# Secrets (encrypted)
# Set via: wrangler pages secret put SECRET_NAME
Monorepo Configuration
my-monorepo/
âââ apps/
â âââ web/ # Root directory: apps/web
â âââ src/
â âââ package.json
âââ packages/
Set Root directory to apps/web in build settings.
Pages Functions
Directory Structure
my-project/
âââ functions/
â âââ _middleware.js # Global middleware
â âââ api/
â â âââ _middleware.js # API-specific middleware
â â âââ index.js # GET /api
â â âââ users/
â â â âââ index.js # GET /api/users
â â â âââ [id].js # GET /api/users/:id
â â âââ [[catchall]].js # Catch-all
â âââ health.js # GET /health
âââ public/
â âââ index.html
âââ package.json
Basic Function
// functions/api/hello.ts
export const onRequestGet: PagesFunction = async (context) => {
return new Response(JSON.stringify({ message: "Hello!" }), {
headers: { "Content-Type": "application/json" }
});
};
// Handle multiple methods
export const onRequest: PagesFunction = async (context) => {
const { request } = context;
if (request.method === "POST") {
const body = await request.json();
return new Response(JSON.stringify(body));
}
return new Response("Method not allowed", { status: 405 });
};
Dynamic Routes
// functions/users/[id].ts
export const onRequestGet: PagesFunction = async (context) => {
const { id } = context.params; // string
return new Response(`User: ${id}`);
};
// functions/files/[[path]].ts (catch-all)
export const onRequestGet: PagesFunction = async (context) => {
const { path } = context.params; // string[]
return new Response(`Path: ${path.join("/")}`);
};
Middleware
// functions/_middleware.ts
export const onRequest: PagesFunction = async (context) => {
// Before handler
console.log(`${context.request.method} ${context.request.url}`);
try {
// Call next handler
const response = await context.next();
// After handler - modify response
response.headers.set("X-Custom-Header", "value");
return response;
} catch (err) {
return new Response("Server Error", { status: 500 });
}
};
// Chain multiple middlewares
export const onRequest = [errorHandler, auth, logging];
async function errorHandler(context) {
try {
return await context.next();
} catch (err) {
return new Response(err.message, { status: 500 });
}
}
async function auth(context) {
const token = context.request.headers.get("Authorization");
if (!token) {
return new Response("Unauthorized", { status: 401 });
}
return context.next();
}
TypeScript Support
// functions/api/data.ts
interface Env {
KV: KVNamespace;
DB: D1Database;
BUCKET: R2Bucket;
API_KEY: string;
}
export const onRequestGet: PagesFunction<Env> = async (context) => {
const { env, request, params, waitUntil, passThroughOnException } = context;
// Access bindings
const value = await env.KV.get("key");
const result = await env.DB.prepare("SELECT * FROM users").all();
return Response.json({ value, users: result.results });
};
Bindings
KV Namespace
// functions/api/kv.ts
interface Env {
MY_KV: KVNamespace;
}
export const onRequest: PagesFunction<Env> = async ({ env }) => {
// Write
await env.MY_KV.put("key", "value");
await env.MY_KV.put("json", JSON.stringify({ foo: "bar" }));
await env.MY_KV.put("expiring", "data", { expirationTtl: 3600 });
// Read
const value = await env.MY_KV.get("key");
const json = await env.MY_KV.get("json", { type: "json" });
// List
const list = await env.MY_KV.list({ prefix: "user:" });
// Delete
await env.MY_KV.delete("key");
return Response.json({ value, json });
};
# Local development
npx wrangler pages dev ./dist --kv=MY_KV
R2 Bucket
// functions/api/files.ts
interface Env {
BUCKET: R2Bucket;
}
export const onRequest: PagesFunction<Env> = async ({ env, request }) => {
const url = new URL(request.url);
const key = url.pathname.slice(1);
switch (request.method) {
case "PUT":
await env.BUCKET.put(key, request.body, {
httpMetadata: { contentType: request.headers.get("content-type") }
});
return new Response("Uploaded");
case "GET":
const object = await env.BUCKET.get(key);
if (!object) return new Response("Not found", { status: 404 });
return new Response(object.body, {
headers: { "Content-Type": object.httpMetadata?.contentType || "application/octet-stream" }
});
case "DELETE":
await env.BUCKET.delete(key);
return new Response("Deleted");
}
};
# Local development
npx wrangler pages dev ./dist --r2=BUCKET
D1 Database
// functions/api/users.ts
interface Env {
DB: D1Database;
}
export const onRequestGet: PagesFunction<Env> = async ({ env }) => {
const { results } = await env.DB
.prepare("SELECT * FROM users WHERE active = ?")
.bind(1)
.all();
return Response.json(results);
};
export const onRequestPost: PagesFunction<Env> = async ({ env, request }) => {
const { name, email } = await request.json();
const result = await env.DB
.prepare("INSERT INTO users (name, email) VALUES (?, ?)")
.bind(name, email)
.run();
return Response.json({ id: result.meta.last_row_id });
};
# Local development
npx wrangler pages dev ./dist --d1=DB=<database-id>
Durable Objects
// functions/api/counter.ts
interface Env {
COUNTER: DurableObjectNamespace;
}
export const onRequest: PagesFunction<Env> = async ({ env, request }) => {
const id = env.COUNTER.idFromName("global");
const stub = env.COUNTER.get(id);
return stub.fetch(request);
};
# Local development (requires Worker with DO)
npx wrangler pages dev ./dist --do=COUNTER=CounterDO@counter-worker
Service Bindings
// functions/api/proxy.ts
interface Env {
AUTH_SERVICE: Fetcher;
}
export const onRequest: PagesFunction<Env> = async ({ env, request }) => {
// Call another Worker
return env.AUTH_SERVICE.fetch(request);
};
Queue Producer
// functions/api/jobs.ts
interface Env {
MY_QUEUE: Queue;
}
export const onRequestPost: PagesFunction<Env> = async ({ env, request }) => {
const job = await request.json();
await env.MY_QUEUE.send({
type: "process",
data: job
});
return Response.json({ queued: true });
};
Workers AI
// functions/api/ai.ts
interface Env {
AI: Ai;
}
export const onRequestPost: PagesFunction<Env> = async ({ env, request }) => {
const { prompt } = await request.json();
const response = await env.AI.run("@cf/meta/llama-2-7b-chat-int8", {
messages: [{ role: "user", content: prompt }]
});
return Response.json(response);
};
Static Configuration
Headers (_headers)
# Apply to all paths
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
# API CORS headers
/api/*
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
# Cache static assets
/assets/*
Cache-Control: public, max-age=31536000, immutable
# Security headers for HTML
/*.html
Content-Security-Policy: default-src 'self'
X-XSS-Protection: 1; mode=block
# Remove header (prefix with !)
/public/*
! X-Robots-Tag
Note: Custom headers do NOT apply to Pages Functions responses.
Redirects (_redirects)
# Simple redirect (302 default)
/home /
# Permanent redirect
/old-page /new-page 301
# External redirect
/twitter https://twitter.com/example
# Splat (wildcard)
/blog/* https://blog.example.com/:splat
# Placeholder
/users/:id /profiles/:id 301
# Proxy (200 status, relative URLs only)
/api/* /functions/api/:splat 200
# Force trailing slash
/about /about/ 301
Limits: 2,000 static + 100 dynamic redirects max.
Routes (_routes.json)
{
"version": 1,
"include": ["/api/*", "/auth/*"],
"exclude": ["/api/health", "/static/*"]
}
- Controls which paths invoke Functions
excludetakes precedence overinclude- Paths not matching either are served as static assets
- Max 100 rules combined
Deployment Methods
Git Integration (Recommended)
GitHub Setup:
- Workers & Pages > Create application > Connect to Git
- Select GitHub and authorize
- Choose repository
- Configure build settings
- Deploy
GitLab Setup:
- Requires Maintainer role or higher
- Grants access to all repositories on account
Branch Configuration:
- Production branch:
main(configurable) - Preview branches: All non-production (configurable)
- Custom rules: Include/exclude patterns
Direct Upload (Wrangler)
# Create project
npx wrangler pages project create my-app
# List projects
npx wrangler pages project list
# Deploy production
npx wrangler pages deploy ./dist
# Deploy preview
npx wrangler pages deploy ./dist --branch=feature-x
# List deployments
npx wrangler pages deployment list --project-name=my-app
API Deployment
# Create deployment via API
curl -X POST \
"https://api.cloudflare.com/client/v4/accounts/{account_id}/pages/projects/{project_name}/deployments" \
-H "Authorization: Bearer {api_token}" \
-F "manifest=@manifest.json" \
-F "file1=@dist/index.html"
Local Development
Wrangler Dev Server
# Basic development
npx wrangler pages dev ./dist
# With bindings
npx wrangler pages dev ./dist \
--kv=MY_KV \
--r2=BUCKET \
--d1=DB=<database-id> \
--port=8788
# With live reload (framework dev server)
npx wrangler pages dev -- npm run dev
# Proxy to another server
npx wrangler pages dev --proxy=3000
wrangler.toml Configuration
name = "my-pages-project"
pages_build_output_dir = "./dist"
# Compatibility
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]
# Environment variables
[vars]
API_URL = "https://api.example.com"
# KV binding
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123"
# R2 binding
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"
# D1 binding
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xyz789"
# Durable Objects
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"
script_name = "counter-worker"
# Service binding
[[services]]
binding = "AUTH"
service = "auth-worker"
Branch Build Controls
Preview Deployments
Settings > Builds & deployments > Configure Preview deployments
Options:
- All non-production branches: Default, deploys everything
- None: Disables preview builds
- Custom branches: Selective deployment
Custom Branch Rules
# Include patterns
feat/* # Match feat/login, feat/dashboard
release-* # Match release-1.0, release-2.0
# Exclude patterns
dependabot/* # Skip dependency updates
wip/* # Skip work-in-progress
# Order: excludes evaluated first, then includes
Skip Builds
Add to commit message:
git commit -m "Update docs [CI SKIP]"
git commit -m "Minor fix [skip ci]"
Rollbacks
Via Dashboard
- Go to project > Deployments
- Find the deployment to rollback to
- Click three dots > Rollback to this deployment
Via Wrangler
# List deployments
npx wrangler pages deployment list --project-name=my-app
# Rollback (redeploy a previous deployment)
# Note: Direct rollback command not available;
# redeploy the specific commit or use dashboard
Troubleshooting
Build Failures
Error: Build command failed
Solutions:
- Check build logs for specific error
- Verify build command and output directory
- Test locally:
npm run build - Check Node.js version compatibility
- Review environment variables
Function 500 Errors
Error: Internal Server Error
Solutions:
- Check function logs in dashboard
- Verify bindings are configured
- Test locally with
wrangler pages dev - Check for unhandled promise rejections
Static Asset 404
Error: Page not found
Solutions:
- Verify build output directory is correct
- Check
_routes.jsonisn’t excluding the path - Confirm file exists in build output
- Review
_redirectsfor conflicts
Binding Not Found
Error: Cannot read property of undefined
Solutions:
- Verify binding is configured in dashboard
- Check binding name matches code
- For local dev, pass binding flags to wrangler
- Verify wrangler.toml configuration
Headers/Redirects Not Working
Possible causes:
- File not in correct location (must be in build output)
- Syntax errors in file
- For Functions responses, use code instead
- Exceeded limit (100 headers, 2100 redirects)
Best Practices
Project Structure
my-app/
âââ functions/ # Server-side code
â âââ _middleware.ts # Global middleware
â âââ api/ # API routes
âââ public/ # Static assets (copied to dist)
â âââ _headers # Header rules
â âââ _redirects # Redirect rules
â âââ robots.txt
âââ src/ # Source code
âââ dist/ # Build output (gitignored)
âââ wrangler.toml # Wrangler config
âââ package.json
Security Recommendations
- Use environment variables for secrets
- Implement CORS properly for APIs
- Add security headers (CSP, X-Frame-Options)
- Validate and sanitize user input
- Use HTTPS-only custom domains
- Review function permissions and bindings
Performance Tips
- Use
_routes.jsonto skip Functions for static paths - Set appropriate Cache-Control headers
- Optimize images before upload
- Use code splitting for large apps
- Leverage Cloudflare’s global CDN
Resources
Official Documentation
Tools
Related Services
Community
Version History
- 1.0.0 (2026-01-13): Initial skill release
- Complete Pages platform documentation
- Static site and full-stack deployment guides
- Pages Functions with routing and middleware
- All bindings (KV, R2, D1, DO, Queues, AI)
- Build configuration and framework presets
- Headers, redirects, and routes configuration
- Git integration and direct upload methods
- Local development with Wrangler
- Branch build controls and rollbacks
- Troubleshooting guide