cloudflare-pages
Deploy full-stack applications on Cloudflare Pages. Covers Git integration, Direct Upload, Wrangler CLI, build configuration, Pages Functions (file-based routing), bindings, headers/redirects, custom domains, environment variables. Keywords: Cloudflare Pages, Pages Functions, Git deployment, Direct Upload, Wrangler, pages.dev, _headers, _redirects, _routes.json, preview deployments.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install itechmeat-llm-code-cloudflare-pages
Repository
Skill path: skills/cloudflare-pages
Deploy full-stack applications on Cloudflare Pages. Covers Git integration, Direct Upload, Wrangler CLI, build configuration, Pages Functions (file-based routing), bindings, headers/redirects, custom domains, environment variables. Keywords: Cloudflare Pages, Pages Functions, Git deployment, Direct Upload, Wrangler, pages.dev, _headers, _redirects, _routes.json, preview deployments.
Open repositoryBest for
Primary workflow: Run DevOps.
Technical facets: Full Stack, DevOps, Integration.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: itechmeat.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install cloudflare-pages into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/itechmeat/llm-code before adding cloudflare-pages to shared team environments
- Use cloudflare-pages for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: cloudflare-pages
description: "Deploy full-stack applications on Cloudflare Pages. Covers Git integration, Direct Upload, Wrangler CLI, build configuration, Pages Functions (file-based routing), bindings, headers/redirects, custom domains, environment variables. Keywords: Cloudflare Pages, Pages Functions, Git deployment, Direct Upload, Wrangler, pages.dev, _headers, _redirects, _routes.json, preview deployments."
---
# Cloudflare Pages
Full-stack application hosting with Git-based or Direct Upload deployments to Cloudflare's global network.
## Quick Navigation
- Deployment methods → `references/deployment.md`
- Build configuration → `references/build.md`
- Pages Functions → `references/functions.md`
- Bindings → `references/bindings.md`
- Headers & Redirects → `references/headers-redirects.md`
- Custom domains → `references/domains.md`
- Wrangler CLI → `references/wrangler.md`
## When to Use
- Deploying static sites or JAMstack applications
- Building full-stack apps with serverless functions
- Configuring Git-based CI/CD deployments
- Using Direct Upload for prebuilt assets
- Setting up preview deployments for branches/PRs
- Configuring custom domains and redirects
## Deployment Methods
| Method | Best For | Limits |
| --------------- | ----------------------------- | ------------------------------------ |
| Git integration | CI/CD from GitHub/GitLab | Cannot switch to Direct Upload later |
| Direct Upload | Prebuilt assets, CI pipelines | Wrangler: 20k files, 25 MiB/file |
| C3 CLI | New project scaffolding | Framework-dependent |
### Quick Deploy
```bash
# Create project
npx wrangler pages project create my-project
# Deploy
npx wrangler pages deploy ./dist
# Preview deployment (branch)
npx wrangler pages deploy ./dist --branch=feature-x
```
## Build Configuration
```bash
# Framework presets (command → output directory)
# React (Vite): npm run build → dist
# Next.js: npx @cloudflare/next-on-pages@1 → .vercel/output/static
# Nuxt.js: npm run build → dist
# Astro: npm run build → dist
# SvelteKit: npm run build → .svelte-kit/cloudflare
# Hugo: hugo → public
```
### Environment Variables (build-time)
| Variable | Value |
| --------------------- | -------------- |
| `CF_PAGES` | `1` |
| `CF_PAGES_BRANCH` | Branch name |
| `CF_PAGES_COMMIT_SHA` | Commit SHA |
| `CF_PAGES_URL` | Deployment URL |
## Pages Functions
File-based routing in `/functions` directory:
```
/functions/index.js → example.com/
/functions/api/users.js → example.com/api/users
/functions/users/[id].js → example.com/users/:id
```
```javascript
// functions/api/hello.js
export function onRequest(context) {
return new Response("Hello from Pages Function!");
}
```
### Handler Types
| Export | Trigger |
| --------------- | ----------- |
| `onRequest` | All methods |
| `onRequestGet` | GET only |
| `onRequestPost` | POST only |
### Context Object
```typescript
interface EventContext {
request: Request;
env: Env; // Bindings
params: Params; // Route parameters
waitUntil(promise: Promise<any>): void;
next(): Promise<Response>;
data: Record<string, any>;
}
```
## Bindings
Access Cloudflare resources via `context.env`:
| Binding | Access Pattern |
| ---------- | ---------------------------------------- |
| KV | `context.env.MY_KV.get("key")` |
| R2 | `context.env.MY_BUCKET.get("file")` |
| D1 | `context.env.MY_DB.prepare("...").all()` |
| Workers AI | `context.env.AI.run(model, input)` |
> For detailed binding configuration, see: `cloudflare-workers` skill.
## Headers & Redirects
Create `_headers` and `_redirects` in build output directory.
```txt
# _headers
/*
X-Frame-Options: DENY
/static/*
Cache-Control: public, max-age=31536000, immutable
```
```txt
# _redirects
/old-page /new-page 301
/blog/* https://blog.example.com/:splat
```
> **Warning:** `_headers` and `_redirects` do NOT apply to Pages Functions responses.
## Functions Invocation Routes
Control when Functions are invoked with `_routes.json`:
```json
{
"version": 1,
"include": ["/api/*"],
"exclude": ["/static/*"]
}
```
## Wrangler Configuration
```jsonc
// wrangler.jsonc
{
"name": "my-pages-app",
"pages_build_output_dir": "./dist",
"compatibility_date": "2024-01-01",
"kv_namespaces": [{ "binding": "KV", "id": "<NAMESPACE_ID>" }],
"d1_databases": [{ "binding": "DB", "database_name": "my-db", "database_id": "<ID>" }]
}
```
### Local Development
```bash
npx wrangler pages dev ./dist
# With bindings
npx wrangler pages dev ./dist --kv=MY_KV --d1=MY_DB=<ID>
```
## Critical Prohibitions
- Do NOT expect `_headers`/`_redirects` to apply to Pages Functions responses — attach headers in code
- Do NOT convert Direct Upload project to Git integration — not supported
- Do NOT exceed redirect limits — 2,000 static + 100 dynamic redirects max
- Do NOT use absolute URLs for proxying in `_redirects` — relative URLs only
- Do NOT edit bindings in dashboard when using Wrangler config — file is source of truth
- Do NOT store secrets in `wrangler.toml` — use dashboard or `.dev.vars` for local
## Common Gotchas
| Issue | Solution |
| ---------------------- | ----------------------------------------------------- |
| Functions not invoked | Check `_routes.json` include/exclude patterns |
| Headers not applied | Ensure not a Functions response; attach in code |
| Build fails | Check build command exit code (must be 0) |
| Custom domain inactive | Verify DNS CNAME points to `<site>.pages.dev` |
| Preview URLs indexed | Default `X-Robots-Tag: noindex` applied automatically |
## Quick Recipes
### Conditional Build Command
```bash
#!/bin/bash
if [ "$CF_PAGES_BRANCH" == "production" ]; then
npm run build:prod
else
npm run build:dev
fi
```
### SPA Fallback (404.html)
Upload `404.html` in build output root for SPA routing.
### Disable Functions for Static Assets
```json
// _routes.json
{
"version": 1,
"include": ["/api/*"],
"exclude": ["/*"]
}
```
## Related Skills
- `cloudflare-workers` — Worker runtime, bindings API details
- `cloudflare-d1` — D1 SQL database operations
- `cloudflare-r2` — R2 object storage
- `cloudflare-kv` — KV namespace operations
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/deployment.md
```markdown
# Deployment Methods
## Git Integration
Automatic deployments from GitHub or GitLab repositories.
### Setup
1. Go to Workers & Pages > Create application > Pages
2. Connect to Git (GitHub or GitLab)
3. Configure:
- Project name
- Production branch
- Build command
- Build output directory
- Root directory (for monorepos)
### Features
- **Preview deployments** — Automatic for non-production branches
- **PR previews** — Preview URLs posted to pull requests
- **Build status checks** — GitHub/GitLab commit status updates
- **Skip builds** — Include `[skip ci]` or `[ci skip]` in commit message
### Branch Controls
| Setting | Options |
| ----------------- | ---------------------------- |
| Production branch | Single branch (e.g., `main`) |
| Preview branches | All / None / Custom pattern |
### Custom Branch Pattern
```
# Include
feature/*
release-*
# Exclude
dependabot/*
```
### Key Limitation
> **Cannot switch from Git integration to Direct Upload** — create new project if needed.
---
## Direct Upload
Upload prebuilt assets without Git connection.
### Wrangler CLI
```bash
# Create project
npx wrangler pages project create my-project
# Deploy to production
npx wrangler pages deploy ./dist
# Deploy preview (branch)
npx wrangler pages deploy ./dist --branch=staging
# List projects
npx wrangler pages project list
# List deployments
npx wrangler pages deployment list
```
### Dashboard Upload
- Drag and drop in Cloudflare dashboard
- Limited to 1,000 files, 25 MiB per file
### Wrangler Limits
- 20,000 files per deployment
- 25 MiB per file
---
## C3 CLI (create-cloudflare)
Scaffold new projects with framework support:
```bash
npm create cloudflare@latest -- --platform=pages
```
Options:
- `--framework` — Specify framework (react, vue, svelte, etc.)
- `--ts` — Use TypeScript
- `--git` — Initialize Git repository
---
## Deployment Comparison
| Feature | Git Integration | Direct Upload |
| ----------------------- | --------------- | -------------------- |
| Auto-deploy on push | ✅ | ❌ |
| Preview deployments | ✅ Automatic | ✅ Manual (--branch) |
| CI/CD integration | Built-in | External CI required |
| Monorepo support | ✅ (V2 build) | ✅ |
| Max files | Unlimited | 20,000 (Wrangler) |
| Convert between methods | ❌ | ❌ |
---
## Preview Deployments
### URL Pattern
```
<branch>.<project>.pages.dev
```
Branch name normalization:
- Lowercase
- Non-alphanumeric → hyphen
Example: `feature/user-auth` → `feature-user-auth.my-project.pages.dev`
### Robots Tag
Preview deployments include `X-Robots-Tag: noindex` by default.
### Access Control
Restrict preview access with Cloudflare Access policies.
---
## Rollbacks
Instantly revert to previous deployment:
1. Go to project > Deployments
2. Find deployment to restore
3. Click "Rollback to this deployment"
> Rollback creates a new deployment, preserving history.
```
### references/build.md
```markdown
# Build Configuration
## Build Commands
Specify how your site is built.
### Exit Codes
- `0` — Success, proceed to deployment
- Non-zero — Build failure
### Framework Presets
| Framework | Build Command | Output Directory |
| ----------------------- | --------------------------------- | ------------------------ |
| React (Vite) | `npm run build` | `dist` |
| Next.js | `npx @cloudflare/next-on-pages@1` | `.vercel/output/static` |
| Next.js (Static Export) | `npx next build` | `out` |
| Nuxt.js | `npm run build` | `dist` |
| SvelteKit | `npm run build` | `.svelte-kit/cloudflare` |
| Astro | `npm run build` | `dist` |
| Vue (Vite) | `npm run build` | `dist` |
| Remix | `npm run build` | `build/client` |
| Gatsby | `npx gatsby build` | `public` |
| Hugo | `hugo` | `public` |
| Jekyll | `jekyll build` | `_site` |
| Eleventy | `npx @11ty/eleventy` | `_site` |
| Zola | `zola build` | `public` |
### No Build Required
Use `exit 0` if project doesn't need building:
```
Build command: exit 0
```
---
## Environment Variables
### System Variables (auto-injected)
| Variable | Description |
| --------------------- | ------------------- |
| `CI` | Always `true` |
| `CF_PAGES` | Always `1` |
| `CF_PAGES_BRANCH` | Current branch name |
| `CF_PAGES_COMMIT_SHA` | Git commit SHA |
| `CF_PAGES_URL` | Deployment URL |
### Setting Variables
**Dashboard:**
Workers & Pages > Project > Settings > Environment variables
**Local Development:**
```bash
# .dev.vars (do not commit)
API_KEY=secret123
DATABASE_URL=postgres://...
```
### Branch-Conditional Builds
```bash
#!/bin/bash
if [ "$CF_PAGES_BRANCH" == "production" ]; then
npm run build:prod
elif [ "$CF_PAGES_BRANCH" == "staging" ]; then
npm run build:staging
else
npm run build:dev
fi
```
---
## Build Caching
Speeds up subsequent builds by caching:
- Package manager caches (npm, yarn, pnpm)
- Framework caches (`.next/cache`, `node_modules/.astro`, `.cache`)
### Requirements
- V2 build system or later
- Enable in project settings
### Limits
- Cache retention: 7 days after last read
- Per-project limit: 10 GB
---
## Node.js Version
### Set via Environment Variable
```
NODE_VERSION = 20
```
or specific:
```
NODE_VERSION = 20.10.0
```
### Set via Version File
Create `.nvmrc` or `.node-version` in project root:
```
20
```
### Default Versions
| Build System | Default Node.js |
| ------------ | --------------- |
| v3 | 22.16.0 |
| v2 | 18.17.1 |
| v1 | 12.18.0 |
---
## Build Watch Paths
Control which file changes trigger builds.
### Configuration
| Field | Purpose |
| ------- | -------------------------------------- |
| Include | Only build if matched files changed |
| Exclude | Skip build if only these files changed |
### Evaluation Order
1. Check excludes
2. Check includes
3. If any match → trigger build
### Wildcards
```
# Match all in directory
project-a/*
# Match specific extensions
*.md
src/**/*.test.ts
```
### Bypass Conditions
Build watch paths bypassed when:
- Push has 0 file changes
- Push has 3000+ file changes
- Push has 20+ commits
---
## Monorepo Support
Multiple Pages projects from same repository.
### Requirements
- V2 build system or later
- Up to 5 Pages projects per repository
### Configuration
1. Set root directory for each project
2. Use build watch paths to trigger correct project
3. Configure separate environment variables per project
---
## Build System Migration
### V3 (Current)
- Ubuntu 22.04.2
- Node.js 22.16.0 default
- Latest tooling
### V2
- Ubuntu 22.04.2
- Node.js 18.17.1 default
- Monorepo support
### V1 (Deprecated)
- Ubuntu 20.04.5
- Node.js 12.18.0 default
### Skip Dependency Install
```
SKIP_DEPENDENCY_INSTALL = true
```
Then run custom install in build command.
```
### references/functions.md
```markdown
# Pages Functions
Server-side code running on Cloudflare Workers runtime.
## Getting Started
Create a `/functions` directory at project root:
```
my-project/
├── public/ # Static assets
├── functions/ # Server-side code
│ ├── index.js
│ └── api/
│ └── hello.js
└── package.json
```
## File-Based Routing
| File Path | URL |
| ------------------------- | ------------ |
| `/functions/index.js` | `/` |
| `/functions/about.js` | `/about` |
| `/functions/api/users.js` | `/api/users` |
### Dynamic Routes
**Single segment** (`[param]`):
```
/functions/users/[id].js → /users/123
```
```javascript
export function onRequest(context) {
const { id } = context.params; // "123"
return new Response(`User: ${id}`);
}
```
**Catch-all** (`[[catchall]]`):
```
/functions/files/[[path]].js → /files/a/b/c
```
```javascript
export function onRequest(context) {
const segments = context.params.path; // ["a", "b", "c"]
return new Response(`Path: ${segments.join("/")}`);
}
```
---
## Handlers
### Generic Handler
```javascript
export function onRequest(context) {
return new Response("Handles all methods");
}
```
### Method-Specific Handlers
```javascript
export function onRequestGet(context) {
return new Response("GET only");
}
export function onRequestPost(context) {
return new Response("POST only");
}
export function onRequestPut(context) {
/* ... */
}
export function onRequestDelete(context) {
/* ... */
}
export function onRequestPatch(context) {
/* ... */
}
export function onRequestHead(context) {
/* ... */
}
export function onRequestOptions(context) {
/* ... */
}
```
---
## Event Context
```typescript
interface EventContext<Env, Params, Data> {
request: Request; // Incoming request
functionPath: string; // Matched function path
env: Env; // Bindings (KV, R2, D1, etc.)
params: Params; // Route parameters
data: Data; // Shared data from middleware
waitUntil(promise: Promise<any>): void; // Background work
passThroughOnException(): void; // Fall through on error
next(input?: Request | string, init?: RequestInit): Promise<Response>;
}
```
---
## Middleware
Create `_middleware.js` in any functions directory:
```javascript
// functions/_middleware.js
export async function onRequest(context) {
// Before handler
console.log("Request:", context.request.url);
// Call next handler
const response = await context.next();
// After handler
response.headers.set("X-Custom-Header", "value");
return response;
}
```
### Middleware Chain
Export array for multiple middleware:
```javascript
async function auth(context) {
// Check auth
return context.next();
}
async function logging(context) {
console.log(context.request.url);
return context.next();
}
export const onRequest = [auth, logging];
```
### Middleware Scope
- `functions/_middleware.js` — Applies to all routes
- `functions/api/_middleware.js` — Applies to `/api/*` routes
---
## TypeScript
### Setup
```bash
npx wrangler types
```
Creates `worker-configuration.d.ts` with Env types.
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2021",
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["@cloudflare/workers-types"]
},
"include": ["functions/**/*"]
}
```
### Typed Handler
```typescript
// functions/api/users.ts
interface Env {
MY_KV: KVNamespace;
MY_DB: D1Database;
}
export const onRequestGet: PagesFunction<Env> = async (context) => {
const data = await context.env.MY_KV.get("key");
return new Response(data);
};
```
---
## Invocation Control (\_routes.json)
Control when Functions are invoked to reduce charges:
```json
{
"version": 1,
"include": ["/api/*", "/admin/*"],
"exclude": ["/static/*", "/*.ico"]
}
```
Place in build output directory (e.g., `dist/_routes.json`).
### Evaluation Order
1. Check excludes first
2. Then check includes
3. If matched → invoke Function
4. If not matched → serve static asset (no Function charge)
---
## Advanced Mode (\_worker.js)
Take full control with single Worker file:
```javascript
// dist/_worker.js
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname.startsWith("/api/")) {
return new Response(JSON.stringify({ ok: true }), {
headers: { "Content-Type": "application/json" },
});
}
// Serve static assets
return env.ASSETS.fetch(request);
},
};
```
> Advanced mode disables file-based routing.
---
## Module Support
### ES Modules
```javascript
import { helper } from "./utils.js";
```
### WebAssembly
```javascript
import wasm from "./module.wasm";
const instance = await WebAssembly.instantiate(wasm);
```
### Text/Binary
```javascript
import text from "./data.txt";
import binary from "./file.bin";
```
---
## Debugging
### Wrangler Tail
```bash
wrangler pages deployment tail
```
### Console Logging
```javascript
export function onRequest(context) {
console.log("Request URL:", context.request.url);
console.log("Headers:", Object.fromEntries(context.request.headers));
return new Response("OK");
}
```
### Source Maps
```bash
npx wrangler pages deploy --upload-source-maps
```
Or in `wrangler.toml`:
```toml
upload_source_maps = true
```
---
## Pricing
- Functions requests billed as Workers requests
- Static asset requests (not invoking Functions) are free
- Use `_routes.json` to optimize costs
```
### references/bindings.md
```markdown
# Pages Functions Bindings
Connect Functions to Cloudflare resources via `context.env`.
## Supported Bindings
| Binding | Type | Local Dev Support |
| --------------------- | ------------------------ | ----------------- |
| KV Namespace | `KVNamespace` | ✅ |
| R2 Bucket | `R2Bucket` | ✅ |
| D1 Database | `D1Database` | ✅ |
| Durable Objects | `DurableObjectNamespace` | ✅ |
| Service Binding | `Fetcher` | ✅ |
| Queues Producer | `Queue` | ✅ |
| Workers AI | `Ai` | ❌ Remote only |
| Vectorize | `VectorizeIndex` | ❌ Remote only |
| Analytics Engine | `AnalyticsEngineDataset` | ❌ |
| Hyperdrive | `Hyperdrive` | ❌ |
| Environment Variables | `string` | ✅ |
| Secrets | `string` | ✅ |
---
## Configuration Methods
### Dashboard
1. Workers & Pages > Project > Settings > Bindings
2. Add binding (select type, provide name/ID)
3. Redeploy for changes to take effect
### Wrangler Configuration
```jsonc
// wrangler.jsonc
{
"name": "my-pages-app",
"pages_build_output_dir": "./dist",
"compatibility_date": "2024-01-01",
"kv_namespaces": [{ "binding": "MY_KV", "id": "abc123" }],
"r2_buckets": [{ "binding": "MY_BUCKET", "bucket_name": "my-bucket" }],
"d1_databases": [{ "binding": "MY_DB", "database_name": "my-db", "database_id": "xyz789" }],
"services": [{ "binding": "AUTH_SERVICE", "service": "auth-worker" }],
"ai": { "binding": "AI" },
"vars": {
"API_URL": "https://api.example.com",
"DEBUG": "false"
}
}
```
---
## Local Development
### Wrangler CLI Flags
```bash
npx wrangler pages dev ./dist \
--kv=MY_KV \
--r2=MY_BUCKET \
--d1=MY_DB=<database-id> \
--service=AUTH_SERVICE=auth-worker \
--ai=AI \
--binding=API_URL=https://api.example.com
```
### Local Secrets (.dev.vars)
```bash
# .dev.vars (do not commit!)
API_SECRET=secret123
DATABASE_URL=postgres://localhost/db
```
---
## Usage Examples
### KV Namespace
```javascript
export async function onRequestGet(context) {
// Read
const value = await context.env.MY_KV.get("key");
const json = await context.env.MY_KV.get("key", { type: "json" });
// Write
await context.env.MY_KV.put("key", "value", {
expirationTtl: 3600,
metadata: { created: Date.now() },
});
// Delete
await context.env.MY_KV.delete("key");
// List
const { keys } = await context.env.MY_KV.list({ prefix: "user:" });
return new Response(JSON.stringify(keys));
}
```
> For complete KV API, see: `cloudflare-kv` skill
### R2 Bucket
```javascript
export async function onRequest(context) {
// Upload
await context.env.MY_BUCKET.put("file.txt", "content", {
httpMetadata: { contentType: "text/plain" },
});
// Download
const object = await context.env.MY_BUCKET.get("file.txt");
if (!object) {
return new Response("Not found", { status: 404 });
}
return new Response(object.body, {
headers: {
"Content-Type": object.httpMetadata?.contentType || "application/octet-stream",
},
});
}
```
> For complete R2 API, see: `cloudflare-r2` skill
### D1 Database
```javascript
export async function onRequestGet(context) {
const { results } = await context.env.MY_DB.prepare("SELECT * FROM users WHERE id = ?").bind(context.params.id).all();
return Response.json(results);
}
export async function onRequestPost(context) {
const body = await context.request.json();
const result = await context.env.MY_DB.prepare("INSERT INTO users (name, email) VALUES (?, ?)").bind(body.name, body.email).run();
return Response.json({ id: result.meta.last_row_id });
}
```
> For complete D1 API, see: `cloudflare-d1` skill
### Workers AI
```javascript
export async function onRequestPost(context) {
const body = await context.request.json();
const response = await context.env.AI.run("@cf/meta/llama-3-8b-instruct", {
messages: [{ role: "user", content: body.prompt }],
});
return Response.json(response);
}
```
> **Note:** Workers AI does not work locally — always uses remote API (incurs charges).
### Service Binding
```javascript
export async function onRequest(context) {
// Call another Worker
const authResponse = await context.env.AUTH_SERVICE.fetch(
new Request("https://auth/verify", {
headers: { Authorization: context.request.headers.get("Authorization") },
})
);
if (!authResponse.ok) {
return new Response("Unauthorized", { status: 401 });
}
return new Response("Protected content");
}
```
### Durable Objects
```javascript
export async function onRequest(context) {
const id = context.env.COUNTER.idFromName("global");
const stub = context.env.COUNTER.get(id);
const response = await stub.fetch(context.request);
return response;
}
```
> Durable Objects must be defined in a separate Worker and bound to Pages project.
### Queues Producer
```javascript
export async function onRequestPost(context) {
const body = await context.request.json();
await context.env.MY_QUEUE.send({
type: "process",
data: body,
});
return new Response("Queued", { status: 202 });
}
```
### Vectorize
```javascript
export async function onRequestPost(context) {
const body = await context.request.json();
// Query similar vectors
const results = await context.env.VECTORIZE_INDEX.query(body.vector, {
topK: 5,
returnMetadata: true,
});
return Response.json(results);
}
```
### Environment Variables
```javascript
export function onRequest(context) {
const apiUrl = context.env.API_URL;
const debug = context.env.DEBUG === "true";
return new Response(`API: ${apiUrl}, Debug: ${debug}`);
}
```
---
## Binding Environments
Configure different bindings for preview vs production:
```jsonc
// wrangler.jsonc
{
"name": "my-app",
"pages_build_output_dir": "./dist",
"d1_databases": [{ "binding": "DB", "database_name": "prod-db", "database_id": "prod-id" }],
"env": {
"preview": {
"d1_databases": [{ "binding": "DB", "database_name": "dev-db", "database_id": "dev-id" }]
}
}
}
```
> **Important:** When overriding any non-inheritable key in `env.preview` or `env.production`, you must specify ALL non-inheritable keys in that block.
```
### references/headers-redirects.md
```markdown
# Headers and Redirects
Static configuration files for HTTP headers and URL redirects.
## \_headers File
Create `_headers` in build output directory (e.g., `public/_headers` or `dist/_headers`).
### Syntax
```txt
[url-pattern]
[Header-Name]: [value]
```
### Examples
```txt
# Apply to all pages
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
# Specific path
/secure/*
Content-Security-Policy: default-src 'self'
# Cache static assets
/static/*
Cache-Control: public, max-age=31536000, immutable
# API endpoints
/api/*
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
```
### Placeholders and Splats
```txt
# Named placeholder
/users/:id
X-User-Id: :id
# Splat (catch-all)
/files/*
X-Path: :splat
```
### Detach Headers
Remove inherited headers with `! `:
```txt
# Remove header for specific path
/public/*
! Content-Security-Policy
```
### Limits
| Limit | Value |
| ----------------- | ----------------- |
| Max rules | 100 |
| Max line length | 2,000 characters |
| Duplicate headers | Joined with comma |
---
## \_redirects File
Create `_redirects` in build output directory.
### Syntax
```txt
[source] [destination] [status-code]
```
Default status code: 302 (temporary redirect)
### Examples
```txt
# Permanent redirect
/old-page /new-page 301
# Temporary redirect (default)
/temp /other
# External redirect
/twitter https://twitter.com/myaccount 301
# Trailing slash normalization
/about/ /about 301
/contact /contact/ 301
# Fragment redirect
/section /page#anchor 301
```
### Splats and Placeholders
```txt
# Splat (catch-all)
/blog/* https://blog.example.com/:splat
# Named placeholder
/users/:id /profiles/:id 301
# Multiple placeholders
/products/:category/:id /shop/:category/:id 301
```
### Proxying (200 Status)
Proxy requests to different path (relative URLs only):
```txt
# Proxy /api to /backend
/api/* /backend/:splat 200
# SPA fallback
/* /index.html 200
```
> **Limitation:** Cannot proxy to absolute URLs.
### Limits
| Limit | Value |
| ----------------- | ---------------- |
| Static redirects | 2,000 |
| Dynamic redirects | 100 |
| Total redirects | 2,100 |
| Max line length | 1,000 characters |
### Unsupported Features
- Query string matching
- Domain-level redirects
- Country/language redirects
- Cookie-based redirects
- Absolute URL proxying
---
## Execution Order
1. Redirects execute first
2. Headers applied after redirects
---
## Early Hints (103)
Automatic `Link` header generation for preloading resources.
### Automatic Generation
Pages automatically creates `Link` headers from HTML:
```html
<link rel="preload" href="/app.js" as="script" />
<link rel="preconnect" href="https://api.example.com" />
<link rel="modulepreload" href="/module.js" />
```
### Manual Link Headers
```txt
/*
Link: </app.js>; rel=preload; as=script
Link: </styles.css>; rel=preload; as=style
```
### Disable Automatic Links
```txt
/*
! Link
```
---
## Pages Functions Warning
> **Critical:** `_headers` and `_redirects` do NOT apply to responses from Pages Functions.
For Functions responses, attach headers in code:
```javascript
export function onRequest(context) {
return new Response("Hello", {
headers: {
"X-Custom-Header": "value",
"Cache-Control": "public, max-age=3600",
},
});
}
```
For redirects in Functions:
```javascript
export function onRequest(context) {
return Response.redirect("https://example.com/new-path", 301);
}
```
---
## Default Response Headers
Pages automatically adds:
| Header | Value |
| ----------------------------- | --------------------------------- |
| `Access-Control-Allow-Origin` | `*` |
| `Referrer-Policy` | `strict-origin-when-cross-origin` |
| `X-Content-Type-Options` | `nosniff` |
| `Server` | `cloudflare` |
| `Cf-Ray` | Request ID |
| `Etag` | Content hash |
Preview deployments also add:
| Header | Value |
| -------------- | --------- |
| `X-Robots-Tag` | `noindex` |
---
## Bulk Redirects
For exceeding `_redirects` limits, use account-level Bulk Redirects:
1. Rules > Bulk Redirects
2. Create redirect list
3. Enable list
> Bulk Redirects apply at account level, not project level.
```
### references/domains.md
```markdown
# Custom Domains
Configure custom domains for Pages projects.
## Adding a Custom Domain
### Dashboard Method
1. Workers & Pages > Project > Custom domains
2. Set up a domain
3. Enter domain name
4. Follow activation steps
### Domain Types
| Type | Requirements |
| ----------------------------- | -------------------------------------------------- |
| Apex domain (`example.com`) | Must add zone to Cloudflare, configure nameservers |
| Subdomain (`app.example.com`) | CNAME record only (zone optional) |
---
## DNS Configuration
### Subdomain (Recommended)
Add CNAME record in your DNS provider:
| Type | Name | Content |
| ----- | ----- | --------------------- |
| CNAME | `app` | `<project>.pages.dev` |
Example:
```
shop.example.com → my-store.pages.dev
```
### Apex Domain
1. Add domain to Cloudflare as a zone
2. Update nameservers at registrar to Cloudflare
3. CNAME record added automatically
---
## SSL/TLS
- Automatic SSL certificate issuance
- Edge Certificates enabled by default
- HTTPS enforced
### CAA Records
If you have CAA records, add:
```
example.com. CAA 0 issue "digicert.com"
example.com. CAA 0 issue "letsencrypt.org"
example.com. CAA 0 issue "pki.goog"
```
> Missing CAA records may block certificate issuance.
---
## Multiple Domains
Attach multiple custom domains to same project:
1. Primary domain: `www.example.com`
2. Redirect domain: `example.com` → redirect to `www.example.com`
---
## Removing Custom Domain
1. Delete CNAME record from DNS zone
2. Workers & Pages > Project > Custom domains
3. Remove domain from project
---
## Disable pages.dev Access
Prevent access to `*.pages.dev` subdomain:
### Method 1: Cloudflare Access
Apply Access policy to pages.dev subdomain.
### Method 2: Bulk Redirects
Create account-level Bulk Redirect:
```
<project>.pages.dev/* → https://example.com/:splat 301
```
---
## Preview Subdomain Access
Preview deployments use pattern:
```
<branch>.<project>.pages.dev
```
### Restrict Preview Access
1. Apply Cloudflare Access policy
2. Configure allowed users/groups
---
## Caching
### Default Behavior
- Pages handles caching automatically
- Assets served from Tiered Cache
- No additional configuration needed
### Custom Domain Caching
When using custom domain via Cloudflare zone:
- Zone caching rules may apply
- Avoid conflicting cache settings
### Purge Cache
After deployment, stale assets may persist:
1. Go to zone Caching settings
2. Purge Everything
> Deploy triggers automatic cache invalidation for most assets.
---
## Known Issues
### Domain Inactive After DNS Change
If domain was temporarily pointed elsewhere:
1. Domain may become inactive
2. Re-validate in project settings
3. Or use Origin Rules for temporary redirects
### Conflicting Zone Settings
Page Rules or other zone settings may override Pages behavior:
- Disable conflicting Page Rules
- Review zone caching configuration
- Check redirect rules
---
## Domain Verification
For new domains:
1. Add TXT record if required for verification
2. Wait for DNS propagation (up to 48 hours)
3. Check domain status in dashboard
---
## Troubleshooting
| Issue | Solution |
| ----------------------- | -------------------------------------------- |
| SSL certificate pending | Check CAA records, wait for issuance |
| Domain not resolving | Verify CNAME points to `<project>.pages.dev` |
| Redirect loop | Check conflicting redirects in zone |
| 522/523 errors | Verify project is deployed and healthy |
```
### references/wrangler.md
```markdown
# Wrangler CLI for Pages
Command-line interface for Pages development and deployment.
## Installation
```bash
npm install -g wrangler
# or
npm install --save-dev wrangler
```
## Project Commands
### Create Project
```bash
npx wrangler pages project create <project-name>
```
### List Projects
```bash
npx wrangler pages project list
```
### Delete Project
```bash
npx wrangler pages project delete <project-name>
```
---
## Deployment Commands
### Deploy
```bash
# Deploy to production
npx wrangler pages deploy <build-output-dir>
# Deploy to branch (preview)
npx wrangler pages deploy <build-output-dir> --branch=staging
# With commit message
npx wrangler pages deploy <build-output-dir> --commit-message="Deploy v1.2.3"
```
### List Deployments
```bash
npx wrangler pages deployment list
```
### Tail Logs
```bash
npx wrangler pages deployment tail
```
---
## Local Development
### Start Dev Server
```bash
npx wrangler pages dev <build-output-dir>
```
Default: `http://localhost:8788`
### Options
| Flag | Description |
| ------------------------ | ----------------------- |
| `--port` | Custom port |
| `--local-protocol=https` | Use HTTPS |
| `--compatibility-date` | Set compatibility date |
| `--compatibility-flags` | Set compatibility flags |
### With Bindings
```bash
npx wrangler pages dev ./dist \
--kv=MY_KV \
--r2=MY_BUCKET \
--d1=MY_DB=<database-id> \
--service=AUTH=auth-worker \
--ai=AI \
--do=COUNTER=CounterClass@counter-worker \
--binding=API_URL=https://api.example.com
```
### Keyboard Shortcuts
| Key | Action |
| --- | ------------- |
| `b` | Open browser |
| `d` | Open DevTools |
| `c` | Clear console |
| `x` | Exit |
---
## Wrangler Configuration
### Minimal wrangler.jsonc
```jsonc
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-pages-app",
"pages_build_output_dir": "./dist",
"compatibility_date": "2024-01-01"
}
```
### Full Configuration
```jsonc
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-pages-app",
"pages_build_output_dir": "./dist",
"compatibility_date": "2024-01-01",
"compatibility_flags": ["nodejs_compat"],
"kv_namespaces": [{ "binding": "KV", "id": "abc123" }],
"r2_buckets": [{ "binding": "BUCKET", "bucket_name": "my-bucket" }],
"d1_databases": [{ "binding": "DB", "database_name": "my-db", "database_id": "xyz789" }],
"services": [{ "binding": "AUTH", "service": "auth-worker" }],
"durable_objects": {
"bindings": [{ "name": "COUNTER", "class_name": "Counter", "script_name": "counter-worker" }]
},
"queues": {
"producers": [{ "binding": "QUEUE", "queue": "my-queue" }]
},
"ai": { "binding": "AI" },
"vectorize": [{ "binding": "VECTORIZE", "index_name": "my-index" }],
"analytics_engine_datasets": [{ "binding": "ANALYTICS", "dataset": "my-dataset" }],
"vars": {
"API_URL": "https://api.example.com",
"DEBUG": "false"
},
"upload_source_maps": true
}
```
### Environment Overrides
```jsonc
{
"name": "my-app",
"pages_build_output_dir": "./dist",
"d1_databases": [{ "binding": "DB", "database_name": "prod-db", "database_id": "prod-id" }],
"env": {
"preview": {
"d1_databases": [{ "binding": "DB", "database_name": "dev-db", "database_id": "dev-id" }]
}
}
}
```
---
## Download Existing Config
Generate `wrangler.toml` from existing dashboard configuration:
```bash
npx wrangler pages download config <project-name>
```
---
## Type Generation
Generate TypeScript types for bindings:
```bash
npx wrangler types
```
Creates `worker-configuration.d.ts`:
```typescript
interface Env {
MY_KV: KVNamespace;
MY_BUCKET: R2Bucket;
MY_DB: D1Database;
AI: Ai;
API_URL: string;
}
```
---
## Source Maps
Upload source maps for debugging:
```bash
npx wrangler pages deploy --upload-source-maps
```
Or in configuration:
```jsonc
{
"upload_source_maps": true
}
```
Requires Wrangler >= 3.60.0
---
## Requirements
| Feature | Requirement |
| -------------------- | ------------------- |
| Wrangler config file | Wrangler >= 3.45.0 |
| V2 build system | For Wrangler config |
| Source maps | Wrangler >= 3.60.0 |
---
## Wrangler vs Dashboard
When using Wrangler configuration:
| Setting | Source of Truth |
| --------------------- | --------------- |
| Bindings | Wrangler file |
| Environment variables | Wrangler file |
| Build settings | Dashboard |
| Git integration | Dashboard |
| Custom domains | Dashboard |
> **Important:** Do not edit bindings in dashboard when using Wrangler file — changes may be overwritten.
---
## Common Issues
| Issue | Solution |
| -------------------------- | ------------------------------------------------ |
| "Project not found" | Create project first with `pages project create` |
| Local bindings not working | Add binding flags to `pages dev` |
| Types not generated | Run `wrangler types` after adding bindings |
| Deploy fails | Check build output directory exists |
```