skills-templates/cloudflare-pages/SKILL.md
Cloudflare Pages - Full-stack JAMstack platform with global CDN, serverless functions, Git integration, and native bindings to Workers, KV, R2, D1, and Durable Objects
npx skillsauth add enuno/claude-command-and-control cloudflare-pagesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
┌─────────────────────────────────────────────────────────────────┐
│ 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 │
└───────────────┘ └───────────────┘ └───────────────┘
| 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 |
| 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) |
# 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
# 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
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 |
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 |
# 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
my-monorepo/
├── apps/
│ └── web/ # Root directory: apps/web
│ ├── src/
│ └── package.json
└── packages/
Set Root directory to apps/web in build settings.
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
// 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 });
};
// 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("/")}`);
};
// 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();
}
// 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 });
};
// 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
// 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
// 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>
// 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
// 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);
};
// 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 });
};
// 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);
};
# 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.
# 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.
{
"version": 1,
"include": ["/api/*", "/auth/*"],
"exclude": ["/api/health", "/static/*"]
}
exclude takes precedence over includeGitHub Setup:
GitLab Setup:
Branch Configuration:
main (configurable)# 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
# 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 "[email protected]" \
-F "file1=@dist/index.html"
# 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
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"
Settings > Builds & deployments > Configure Preview deployments
Options:
# 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
Add to commit message:
git commit -m "Update docs [CI SKIP]"
git commit -m "Minor fix [skip ci]"
# 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
Error: Build command failed
Solutions:
npm run buildError: Internal Server Error
Solutions:
wrangler pages devError: Page not found
Solutions:
_routes.json isn't excluding the path_redirects for conflictsError: Cannot read property of undefined
Solutions:
Possible causes:
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
_routes.json to skip Functions for static pathstools
MemPalace local-first AI memory system. Use when setting up persistent memory for Claude Code sessions, mining project files or conversation transcripts, querying past context, configuring MCP tools, managing the knowledge graph, or troubleshooting palace operations.
tools
LangSmith Python SDK — trace, evaluate, and monitor LLM applications. Covers @traceable decorator, trace context manager, Client API, evaluate() / aevaluate(), comparative evaluation, custom evaluators, dataset management, prompt caching, ASGI middleware, and pytest plugin.
development
LangGraph (Python) — build stateful, controllable agent graphs with checkpointing, streaming, persistence, interrupts, fault tolerance, and durable execution. Covers both Graph API (StateGraph) and Functional API (@entrypoint/@task).
development
LangGraph Graph API (Python) — build explicit DAG agent workflows with StateGraph, typed state, nodes, edges, Command routing, Send fan-out, checkpointers, interrupts, and streaming. Use when you need explicit control flow and graph topology.