mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-20 06:11:27 -07:00
update skills
This commit is contained in:
88
.agents/skills/cloudflare-deploy/references/pages/README.md
Normal file
88
.agents/skills/cloudflare-deploy/references/pages/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Cloudflare Pages
|
||||
|
||||
JAMstack platform for full-stack apps on Cloudflare's global network.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Git-based deploys**: Auto-deploy from GitHub/GitLab
|
||||
- **Preview deployments**: Unique URL per branch/PR
|
||||
- **Pages Functions**: File-based serverless routing (Workers runtime)
|
||||
- **Static + dynamic**: Smart asset caching + edge compute
|
||||
- **Smart Placement**: Automatic function optimization based on traffic patterns
|
||||
- **Framework optimized**: SvelteKit, Astro, Nuxt, Qwik, Solid Start
|
||||
|
||||
## Deployment Methods
|
||||
|
||||
### 1. Git Integration (Production)
|
||||
Dashboard → Workers & Pages → Create → Connect to Git → Configure build
|
||||
|
||||
### 2. Direct Upload
|
||||
```bash
|
||||
npx wrangler pages deploy ./dist --project-name=my-project
|
||||
npx wrangler pages deploy ./dist --project-name=my-project --branch=staging
|
||||
```
|
||||
|
||||
### 3. C3 CLI
|
||||
```bash
|
||||
npm create cloudflare@latest my-app
|
||||
# Select framework → auto-setup + deploy
|
||||
```
|
||||
|
||||
## vs Workers
|
||||
|
||||
- **Pages**: Static sites, JAMstack, frameworks, git workflow, file-based routing
|
||||
- **Workers**: Pure APIs, complex routing, WebSockets, scheduled tasks, email handlers
|
||||
- **Combine**: Pages Functions use Workers runtime, can bind to Workers
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Create
|
||||
npm create cloudflare@latest
|
||||
|
||||
# Local dev
|
||||
npx wrangler pages dev ./dist
|
||||
|
||||
# Deploy
|
||||
npx wrangler pages deploy ./dist --project-name=my-project
|
||||
|
||||
# Types
|
||||
npx wrangler types --path='./functions/types.d.ts'
|
||||
|
||||
# Secrets
|
||||
echo "value" | npx wrangler pages secret put KEY --project-name=my-project
|
||||
|
||||
# Logs
|
||||
npx wrangler pages deployment tail --project-name=my-project
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Pages Docs](https://developers.cloudflare.com/pages/)
|
||||
- [Functions API](https://developers.cloudflare.com/pages/functions/api-reference/)
|
||||
- [Framework Guides](https://developers.cloudflare.com/pages/framework-guides/)
|
||||
- [Discord #functions](https://discord.com/channels/595317990191398933/910978223968518144)
|
||||
|
||||
## Reading Order
|
||||
|
||||
**New to Pages?** Start here:
|
||||
1. README.md (you are here) - Overview & quick start
|
||||
2. [configuration.md](./configuration.md) - Project setup, wrangler.jsonc, bindings
|
||||
3. [api.md](./api.md) - Functions API, routing, context
|
||||
4. [patterns.md](./patterns.md) - Common implementations
|
||||
5. [gotchas.md](./gotchas.md) - Troubleshooting & pitfalls
|
||||
|
||||
**Quick reference?** Jump to relevant file above.
|
||||
|
||||
## In This Reference
|
||||
|
||||
- [configuration.md](./configuration.md) - wrangler.jsonc, build, env vars, Smart Placement
|
||||
- [api.md](./api.md) - Functions API, bindings, context, advanced mode
|
||||
- [patterns.md](./patterns.md) - Full-stack patterns, framework integration
|
||||
- [gotchas.md](./gotchas.md) - Build issues, limits, debugging, framework warnings
|
||||
|
||||
## See Also
|
||||
|
||||
- [pages-functions](../pages-functions/) - File-based routing, middleware
|
||||
- [d1](../d1/) - SQL database for Pages Functions
|
||||
- [kv](../kv/) - Key-value storage for caching/state
|
||||
204
.agents/skills/cloudflare-deploy/references/pages/api.md
Normal file
204
.agents/skills/cloudflare-deploy/references/pages/api.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Functions API
|
||||
|
||||
## File-Based Routing
|
||||
|
||||
```
|
||||
/functions/index.ts → example.com/
|
||||
/functions/api/users.ts → example.com/api/users
|
||||
/functions/api/users/[id].ts → example.com/api/users/:id
|
||||
/functions/api/users/[[path]].ts → example.com/api/users/* (catchall)
|
||||
/functions/_middleware.ts → Runs before all routes
|
||||
```
|
||||
|
||||
**Rules**: `[param]` = single segment, `[[param]]` = multi-segment catchall, more specific wins.
|
||||
|
||||
## Request Handlers
|
||||
|
||||
```typescript
|
||||
import type { PagesFunction } from '@cloudflare/workers-types';
|
||||
|
||||
interface Env {
|
||||
DB: D1Database;
|
||||
KV: KVNamespace;
|
||||
}
|
||||
|
||||
// All methods
|
||||
export const onRequest: PagesFunction<Env> = async (context) => {
|
||||
return new Response('All methods');
|
||||
};
|
||||
|
||||
// Method-specific
|
||||
export const onRequestGet: PagesFunction<Env> = async (context) => {
|
||||
const { request, env, params, data } = context;
|
||||
|
||||
const user = await env.DB.prepare(
|
||||
'SELECT * FROM users WHERE id = ?'
|
||||
).bind(params.id).first();
|
||||
|
||||
return Response.json(user);
|
||||
};
|
||||
|
||||
export const onRequestPost: PagesFunction<Env> = async (context) => {
|
||||
const body = await context.request.json();
|
||||
return Response.json({ success: true });
|
||||
};
|
||||
|
||||
// Also: onRequestPut, onRequestPatch, onRequestDelete, onRequestHead, onRequestOptions
|
||||
```
|
||||
|
||||
## Context Object
|
||||
|
||||
```typescript
|
||||
interface EventContext<Env, Params, Data> {
|
||||
request: Request; // HTTP request
|
||||
env: Env; // Bindings (KV, D1, R2, etc.)
|
||||
params: Params; // Route parameters
|
||||
data: Data; // Middleware-shared data
|
||||
waitUntil: (promise: Promise<any>) => void; // Background tasks
|
||||
next: () => Promise<Response>; // Next handler
|
||||
passThroughOnException: () => void; // Error fallback (not in advanced mode)
|
||||
}
|
||||
```
|
||||
|
||||
## Dynamic Routes
|
||||
|
||||
```typescript
|
||||
// Single segment: functions/users/[id].ts
|
||||
export const onRequestGet: PagesFunction = async ({ params }) => {
|
||||
// /users/123 → params.id = "123"
|
||||
return Response.json({ userId: params.id });
|
||||
};
|
||||
|
||||
// Multi-segment: functions/files/[[path]].ts
|
||||
export const onRequestGet: PagesFunction = async ({ params }) => {
|
||||
// /files/docs/api/v1.md → params.path = ["docs", "api", "v1.md"]
|
||||
const filePath = (params.path as string[]).join('/');
|
||||
return new Response(filePath);
|
||||
};
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
```typescript
|
||||
// functions/_middleware.ts
|
||||
// Single
|
||||
export const onRequest: PagesFunction = async (context) => {
|
||||
const response = await context.next();
|
||||
response.headers.set('X-Custom-Header', 'value');
|
||||
return response;
|
||||
};
|
||||
|
||||
// Chained (runs in order)
|
||||
const errorHandler: PagesFunction = async (context) => {
|
||||
try {
|
||||
return await context.next();
|
||||
} catch (err) {
|
||||
return new Response(err.message, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
const auth: PagesFunction = async (context) => {
|
||||
const token = context.request.headers.get('Authorization');
|
||||
if (!token) return new Response('Unauthorized', { status: 401 });
|
||||
context.data.userId = await verifyToken(token);
|
||||
return context.next();
|
||||
};
|
||||
|
||||
export const onRequest = [errorHandler, auth];
|
||||
```
|
||||
|
||||
**Scope**: `functions/_middleware.ts` → all; `functions/api/_middleware.ts` → `/api/*` only
|
||||
|
||||
## Bindings Usage
|
||||
|
||||
```typescript
|
||||
export const onRequestGet: PagesFunction<Env> = async ({ env }) => {
|
||||
// KV
|
||||
const cached = await env.KV.get('key', 'json');
|
||||
await env.KV.put('key', JSON.stringify({data: 'value'}), {expirationTtl: 3600});
|
||||
|
||||
// D1
|
||||
const result = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first();
|
||||
|
||||
// R2, Queue, AI - see respective reference docs
|
||||
|
||||
return Response.json({success: true});
|
||||
};
|
||||
```
|
||||
|
||||
## Advanced Mode
|
||||
|
||||
Full Workers API, bypasses file-based routing:
|
||||
|
||||
```javascript
|
||||
// functions/_worker.js
|
||||
export default {
|
||||
async fetch(request, env, ctx) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Custom routing
|
||||
if (url.pathname.startsWith('/api/')) {
|
||||
return new Response('API response');
|
||||
}
|
||||
|
||||
// REQUIRED: Serve static assets
|
||||
return env.ASSETS.fetch(request);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**When to use**: WebSockets, complex routing, scheduled handlers, email handlers.
|
||||
|
||||
## Smart Placement
|
||||
|
||||
Automatically optimizes function execution location based on traffic patterns.
|
||||
|
||||
**Configuration** (in wrangler.jsonc):
|
||||
```jsonc
|
||||
{
|
||||
"placement": {
|
||||
"mode": "smart" // Enables optimization (default: off)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**How it works**: Analyzes traffic patterns over time and places functions closer to users or data sources (e.g., D1 databases). Requires no code changes.
|
||||
|
||||
**Trade-offs**: Initial requests may see slightly higher latency during learning period (hours-days). Performance improves as system optimizes.
|
||||
|
||||
**When to use**: Global apps with centralized databases or geographically concentrated traffic sources.
|
||||
|
||||
## getRequestContext (Framework SSR)
|
||||
|
||||
Access bindings in framework code:
|
||||
|
||||
```typescript
|
||||
// SvelteKit
|
||||
import type { RequestEvent } from '@sveltejs/kit';
|
||||
export async function load({ platform }: RequestEvent) {
|
||||
const data = await platform.env.DB.prepare('SELECT * FROM users').all();
|
||||
return { users: data.results };
|
||||
}
|
||||
|
||||
// Astro
|
||||
const { DB } = Astro.locals.runtime.env;
|
||||
const data = await DB.prepare('SELECT * FROM users').all();
|
||||
|
||||
// Solid Start (server function)
|
||||
import { getRequestEvent } from 'solid-js/web';
|
||||
const event = getRequestEvent();
|
||||
const data = await event.locals.runtime.env.DB.prepare('SELECT * FROM users').all();
|
||||
```
|
||||
|
||||
**✅ Supported adapters** (2026):
|
||||
- **SvelteKit**: `@sveltejs/adapter-cloudflare`
|
||||
- **Astro**: Built-in Cloudflare adapter
|
||||
- **Nuxt**: Set `nitro.preset: 'cloudflare-pages'` in `nuxt.config.ts`
|
||||
- **Qwik**: Built-in Cloudflare adapter
|
||||
- **Solid Start**: `@solidjs/start-cloudflare-pages`
|
||||
|
||||
**❌ Deprecated/Unsupported**:
|
||||
- **Next.js**: Official adapter (`@cloudflare/next-on-pages`) deprecated. Use Vercel or self-host on Workers.
|
||||
- **Remix**: Official adapter (`@remix-run/cloudflare-pages`) deprecated. Migrate to supported frameworks.
|
||||
|
||||
See [gotchas.md](./gotchas.md#framework-specific) for migration guidance.
|
||||
@@ -0,0 +1,201 @@
|
||||
# Configuration
|
||||
|
||||
## wrangler.jsonc
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"name": "my-pages-project",
|
||||
"pages_build_output_dir": "./dist",
|
||||
"compatibility_date": "2026-01-01", // Use current date for new projects
|
||||
"compatibility_flags": ["nodejs_compat"],
|
||||
"placement": {
|
||||
"mode": "smart" // Optional: Enable Smart Placement
|
||||
},
|
||||
"kv_namespaces": [{"binding": "KV", "id": "abcd1234..."}],
|
||||
"d1_databases": [{"binding": "DB", "database_id": "xxxx-xxxx", "database_name": "production-db"}],
|
||||
"r2_buckets": [{"binding": "BUCKET", "bucket_name": "my-bucket"}],
|
||||
"durable_objects": {"bindings": [{"name": "COUNTER", "class_name": "Counter", "script_name": "counter-worker"}]},
|
||||
"services": [{"binding": "API", "service": "api-worker"}],
|
||||
"queues": {"producers": [{"binding": "QUEUE", "queue": "my-queue"}]},
|
||||
"vectorize": [{"binding": "VECTORIZE", "index_name": "my-index"}],
|
||||
"ai": {"binding": "AI"},
|
||||
"analytics_engine_datasets": [{"binding": "ANALYTICS"}],
|
||||
"vars": {"API_URL": "https://api.example.com", "ENVIRONMENT": "production"},
|
||||
"env": {
|
||||
"preview": {
|
||||
"vars": {"API_URL": "https://staging-api.example.com"},
|
||||
"kv_namespaces": [{"binding": "KV", "id": "preview-namespace-id"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Build Config
|
||||
|
||||
**Git deployment**: Dashboard → Project → Settings → Build settings
|
||||
Set build command, output dir, env vars. Framework auto-detection configures automatically.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Local (.dev.vars)
|
||||
```bash
|
||||
# .dev.vars (never commit)
|
||||
SECRET_KEY="local-secret-key"
|
||||
API_TOKEN="dev-token-123"
|
||||
```
|
||||
|
||||
### Production
|
||||
```bash
|
||||
echo "secret-value" | npx wrangler pages secret put SECRET_KEY --project-name=my-project
|
||||
npx wrangler pages secret list --project-name=my-project
|
||||
npx wrangler pages secret delete SECRET_KEY --project-name=my-project
|
||||
```
|
||||
|
||||
Access: `env.SECRET_KEY`
|
||||
|
||||
## Static Config Files
|
||||
|
||||
### _redirects
|
||||
Place in build output (e.g., `dist/_redirects`):
|
||||
|
||||
```txt
|
||||
/old-page /new-page 301 # 301 redirect
|
||||
/blog/* /news/:splat 301 # Splat wildcard
|
||||
/users/:id /members/:id 301 # Placeholders
|
||||
/api/* /api-v2/:splat 200 # Proxy (no redirect)
|
||||
```
|
||||
|
||||
**Limits**: 2,100 total (2,000 static + 100 dynamic), 1,000 char/line
|
||||
**Note**: Functions take precedence
|
||||
|
||||
### _headers
|
||||
```txt
|
||||
/secure/*
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
|
||||
/api/*
|
||||
Access-Control-Allow-Origin: *
|
||||
|
||||
/static/*
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
```
|
||||
|
||||
**Limits**: 100 rules, 2,000 char/line
|
||||
**Note**: Only static assets; Functions set headers in Response
|
||||
|
||||
### _routes.json
|
||||
Controls which requests invoke Functions (auto-generated for most frameworks):
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"include": ["/*"],
|
||||
"exclude": ["/build/*", "/static/*", "/assets/*", "/*.{ico,png,jpg,css,js}"]
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose**: Functions are metered; static requests are free. `exclude` takes precedence. Max 100 rules, 100 char/rule.
|
||||
|
||||
## TypeScript
|
||||
|
||||
```bash
|
||||
npx wrangler types --path='./functions/types.d.ts'
|
||||
```
|
||||
|
||||
Point `types` in `functions/tsconfig.json` to generated file.
|
||||
|
||||
## Smart Placement
|
||||
|
||||
Automatically optimizes function execution location based on request patterns.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"placement": {
|
||||
"mode": "smart" // Enable optimization (default: off)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**How it works**: System analyzes traffic over hours/days and places function execution closer to:
|
||||
- User clusters (e.g., regional traffic)
|
||||
- Data sources (e.g., D1 database primary location)
|
||||
|
||||
**Benefits**:
|
||||
- Lower latency for read-heavy apps with centralized databases
|
||||
- Better performance for apps with regional traffic patterns
|
||||
|
||||
**Trade-offs**:
|
||||
- Initial learning period: First requests may be slower while system optimizes
|
||||
- Optimization time: Performance improves over 24-48 hours
|
||||
|
||||
**When to enable**: Global apps with D1/Durable Objects in specific regions, or apps with concentrated geographic traffic.
|
||||
|
||||
**When to skip**: Evenly distributed global traffic with no data locality constraints.
|
||||
|
||||
## Remote Bindings (Local Dev)
|
||||
|
||||
Connect local dev server to production bindings instead of local mocks:
|
||||
|
||||
```bash
|
||||
# All bindings remote
|
||||
npx wrangler pages dev ./dist --remote
|
||||
|
||||
# Specific bindings remote (others local)
|
||||
npx wrangler pages dev ./dist --remote --kv=KV --d1=DB
|
||||
```
|
||||
|
||||
**Use cases**:
|
||||
- Test against production data (read-only operations)
|
||||
- Debug binding-specific behavior
|
||||
- Validate changes before deployment
|
||||
|
||||
**⚠️ Warning**:
|
||||
- Writes affect **real production data**
|
||||
- Use only for read-heavy debugging or with non-production accounts
|
||||
- Consider creating separate preview environments instead
|
||||
|
||||
**Requirements**: Must be logged in (`npx wrangler login`) with access to bindings.
|
||||
|
||||
## Local Dev
|
||||
|
||||
```bash
|
||||
# Basic
|
||||
npx wrangler pages dev ./dist
|
||||
|
||||
# With bindings
|
||||
npx wrangler pages dev ./dist --kv KV --d1 DB=local-db-id
|
||||
|
||||
# Remote bindings (production data)
|
||||
npx wrangler pages dev ./dist --remote
|
||||
|
||||
# Persistence
|
||||
npx wrangler pages dev ./dist --persist-to=./.wrangler/state/v3
|
||||
|
||||
# Proxy mode (SSR frameworks)
|
||||
npx wrangler pages dev -- npm run dev
|
||||
```
|
||||
|
||||
## Limits (as of Jan 2026)
|
||||
|
||||
| Resource | Free | Paid |
|
||||
|----------|------|------|
|
||||
| **Functions Requests** | 100k/day | Unlimited (metered) |
|
||||
| **Function CPU Time** | 10ms/req | 30ms/req (Workers Paid) |
|
||||
| **Function Memory** | 128MB | 128MB |
|
||||
| **Script Size** | 1MB compressed | 10MB compressed |
|
||||
| **Deployments** | 500/month | 5,000/month |
|
||||
| **Files per Deploy** | 20,000 | 20,000 |
|
||||
| **File Size** | 25MB | 25MB |
|
||||
| **Build Time** | 20min | 20min |
|
||||
| **Redirects** | 2,100 (2k static + 100 dynamic) | Same |
|
||||
| **Header Rules** | 100 | 100 |
|
||||
| **Route Rules** | 100 | 100 |
|
||||
| **Subrequests** | 50/request | 1,000/request (Workers Paid) |
|
||||
|
||||
**Notes**:
|
||||
- Functions use Workers runtime; Workers Paid plan increases limits
|
||||
- Free plan sufficient for most projects
|
||||
- Static requests always free (not counted toward limits)
|
||||
|
||||
[Full limits](https://developers.cloudflare.com/pages/platform/limits/)
|
||||
203
.agents/skills/cloudflare-deploy/references/pages/gotchas.md
Normal file
203
.agents/skills/cloudflare-deploy/references/pages/gotchas.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Gotchas
|
||||
|
||||
## Functions Not Running
|
||||
|
||||
**Problem**: Function endpoints return 404 or don't execute
|
||||
**Causes**: `_routes.json` excludes path; wrong file extension (`.jsx`/`.tsx`); Functions dir not at output root
|
||||
**Solution**: Check `_routes.json`, rename to `.ts`/`.js`, verify build output structure
|
||||
|
||||
## 404 on Static Assets
|
||||
|
||||
**Problem**: Static files not serving
|
||||
**Causes**: Build output dir misconfigured; Functions catching requests; Advanced mode missing `env.ASSETS.fetch()`
|
||||
**Solution**: Verify output dir, add exclusions to `_routes.json`, call `env.ASSETS.fetch()` in `_worker.js`
|
||||
|
||||
## Bindings Not Working
|
||||
|
||||
**Problem**: `env.BINDING` undefined or errors
|
||||
**Causes**: wrangler.jsonc syntax error; wrong binding IDs; missing `.dev.vars`; out-of-sync types
|
||||
**Solution**: Validate config, verify IDs, create `.dev.vars`, run `npx wrangler types`
|
||||
|
||||
## Build Failures
|
||||
|
||||
**Problem**: Deployment fails during build
|
||||
**Causes**: Wrong build command/output dir; Node version incompatibility; missing env vars; 20min timeout; OOM
|
||||
**Solution**: Check Dashboard → Deployments → Build log; verify settings; add `.nvmrc`; optimize build
|
||||
|
||||
## Middleware Not Running
|
||||
|
||||
**Problem**: Middleware doesn't execute
|
||||
**Causes**: Wrong filename (not `_middleware.ts`); missing `onRequest` export; didn't call `next()`
|
||||
**Solution**: Rename file with underscore prefix; export handler; call `next()` or return Response
|
||||
|
||||
## Headers/Redirects Not Working
|
||||
|
||||
**Problem**: `_headers` or `_redirects` not applying
|
||||
**Causes**: Only work for static assets; Functions override; syntax errors; exceeded limits
|
||||
**Solution**: Set headers in Response object for Functions; verify syntax; check limits (100 headers, 2,100 redirects)
|
||||
|
||||
## TypeScript Errors
|
||||
|
||||
**Problem**: Type errors in Functions code
|
||||
**Causes**: Types not generated; Env interface doesn't match wrangler.jsonc
|
||||
**Solution**: Run `npx wrangler types --path='./functions/types.d.ts'`; update Env interface
|
||||
|
||||
## Local Dev Issues
|
||||
|
||||
**Problem**: Dev server errors or bindings don't work
|
||||
**Causes**: Port conflict; bindings not passed; local vs HTTPS differences
|
||||
**Solution**: Use `--port=3000`; pass bindings via CLI or wrangler.jsonc; account for HTTP/HTTPS differences
|
||||
|
||||
## Performance Issues
|
||||
|
||||
**Problem**: Slow responses or CPU limit errors
|
||||
**Causes**: Functions invoked for static assets; cold starts; 10ms CPU limit; large bundle
|
||||
**Solution**: Exclude static via `_routes.json`; optimize hot paths; keep bundle < 1MB
|
||||
|
||||
## Framework-Specific
|
||||
|
||||
### ⚠️ Deprecated Frameworks
|
||||
|
||||
**Next.js**: Official adapter (`@cloudflare/next-on-pages`) **deprecated** and unmaintained.
|
||||
- **Problem**: No updates since 2024; incompatible with Next.js 15+; missing App Router features
|
||||
- **Cause**: Cloudflare discontinued official support; community fork exists but limited
|
||||
- **Solutions**:
|
||||
1. **Recommended**: Use Vercel (official Next.js host)
|
||||
2. **Advanced**: Self-host on Workers using custom adapter (complex, unsupported)
|
||||
3. **Migration**: Switch to SvelteKit/Nuxt (similar DX, full Pages support)
|
||||
|
||||
**Remix**: Official adapter (`@remix-run/cloudflare-pages`) **deprecated**.
|
||||
- **Problem**: No maintenance from Remix team; compatibility issues with Remix v2+
|
||||
- **Cause**: Remix team deprecated all framework adapters
|
||||
- **Solutions**:
|
||||
1. **Recommended**: Migrate to SvelteKit (similar file-based routing, better DX)
|
||||
2. **Alternative**: Use Astro (static-first with optional SSR)
|
||||
3. **Workaround**: Continue using deprecated adapter (no future support)
|
||||
|
||||
### ✅ Supported Frameworks
|
||||
|
||||
**SvelteKit**:
|
||||
- Use `@sveltejs/adapter-cloudflare`
|
||||
- Access bindings via `platform.env` in server load functions
|
||||
- Set `platform: 'cloudflare'` in `svelte.config.js`
|
||||
|
||||
**Astro**:
|
||||
- Built-in Cloudflare adapter
|
||||
- Access bindings via `Astro.locals.runtime.env`
|
||||
|
||||
**Nuxt**:
|
||||
- Set `nitro.preset: 'cloudflare-pages'` in `nuxt.config.ts`
|
||||
- Access bindings via `event.context.cloudflare.env`
|
||||
|
||||
**Qwik, Solid Start**:
|
||||
- Built-in or official Cloudflare adapters available
|
||||
- Check respective framework docs for binding access
|
||||
|
||||
## Debugging
|
||||
|
||||
```typescript
|
||||
// Log request details
|
||||
console.log('Request:', { method: request.method, url: request.url });
|
||||
console.log('Env:', Object.keys(env));
|
||||
console.log('Params:', params);
|
||||
```
|
||||
|
||||
**View logs**: `npx wrangler pages deployment tail --project-name=my-project`
|
||||
|
||||
## Smart Placement Issues
|
||||
|
||||
### Increased Cold Start Latency
|
||||
|
||||
**Problem**: First requests slower after enabling Smart Placement
|
||||
**Cause**: Initial optimization period while system learns traffic patterns
|
||||
**Solution**: Expected behavior during first 24-48 hours; monitor latency trends over time
|
||||
|
||||
### Inconsistent Response Times
|
||||
|
||||
**Problem**: Latency varies significantly across requests during initial deployment
|
||||
**Cause**: Smart Placement testing different execution locations to find optimal placement
|
||||
**Solution**: Normal during learning phase; stabilizes after traffic patterns emerge (1-2 days)
|
||||
|
||||
### No Performance Improvement
|
||||
|
||||
**Problem**: Smart Placement enabled but no latency reduction observed
|
||||
**Cause**: Traffic evenly distributed globally, or no data locality constraints
|
||||
**Solution**: Smart Placement most effective with centralized data (D1/DO) or regional traffic; disable if no benefit
|
||||
|
||||
## Remote Bindings Issues
|
||||
|
||||
### Accidentally Modified Production Data
|
||||
|
||||
**Problem**: Local dev with `--remote` altered production database/KV
|
||||
**Cause**: Remote bindings connect directly to production resources; writes are real
|
||||
**Solution**:
|
||||
- Use `--remote` only for read-heavy debugging
|
||||
- Create separate preview environments for testing
|
||||
- Never use `--remote` for write operations during development
|
||||
|
||||
### Remote Binding Auth Errors
|
||||
|
||||
**Problem**: `npx wrangler pages dev --remote` fails with "Unauthorized" or auth error
|
||||
**Cause**: Not logged in, session expired, or insufficient account permissions
|
||||
**Solution**:
|
||||
1. Run `npx wrangler login` to re-authenticate
|
||||
2. Verify account has access to project and bindings
|
||||
3. Check binding IDs match production configuration
|
||||
|
||||
### Slow Local Dev with Remote Bindings
|
||||
|
||||
**Problem**: Local dev server slow when using `--remote`
|
||||
**Cause**: Every request makes network calls to production bindings
|
||||
**Solution**: Use local bindings for development; reserve `--remote` for final validation
|
||||
|
||||
## Common Errors
|
||||
|
||||
### "Module not found"
|
||||
**Cause**: Dependencies not bundled or build output incorrect
|
||||
**Solution**: Check build output directory, ensure dependencies bundled
|
||||
|
||||
### "Binding not found"
|
||||
**Cause**: Binding not configured or types out of sync
|
||||
**Solution**: Verify wrangler.jsonc, run `npx wrangler types`
|
||||
|
||||
### "Request exceeded CPU limit"
|
||||
**Cause**: Code execution too slow or heavy compute
|
||||
**Solution**: Optimize hot paths, upgrade to Workers Paid
|
||||
|
||||
### "Script too large"
|
||||
**Cause**: Bundle size exceeds limit
|
||||
**Solution**: Tree-shake, use dynamic imports, code-split
|
||||
|
||||
### "Too many subrequests"
|
||||
**Cause**: Exceeded 50 subrequest limit
|
||||
**Solution**: Batch or reduce fetch calls
|
||||
|
||||
### "KV key not found"
|
||||
**Cause**: Key doesn't exist or wrong namespace
|
||||
**Solution**: Check namespace matches environment
|
||||
|
||||
### "D1 error"
|
||||
**Cause**: Wrong database_id or missing migrations
|
||||
**Solution**: Verify config, run `wrangler d1 migrations list`
|
||||
|
||||
## Limits Reference (Jan 2026)
|
||||
|
||||
| Resource | Free | Paid |
|
||||
|----------|------|------|
|
||||
| Functions Requests | 100k/day | Unlimited |
|
||||
| CPU Time | 10ms/req | 30ms/req |
|
||||
| Memory | 128MB | 128MB |
|
||||
| Script Size | 1MB | 10MB |
|
||||
| Subrequests | 50/req | 1,000/req |
|
||||
| Deployments | 500/month | 5,000/month |
|
||||
|
||||
**Tip**: Hitting CPU limit? Optimize hot paths or upgrade to Workers Paid plan.
|
||||
|
||||
[Full limits](https://developers.cloudflare.com/pages/platform/limits/)
|
||||
|
||||
## Getting Help
|
||||
|
||||
1. Check [Pages Docs](https://developers.cloudflare.com/pages/)
|
||||
2. Search [Discord #functions](https://discord.com/channels/595317990191398933/910978223968518144)
|
||||
3. Review [Workers Examples](https://developers.cloudflare.com/workers/examples/)
|
||||
4. Check framework-specific docs/adapters
|
||||
204
.agents/skills/cloudflare-deploy/references/pages/patterns.md
Normal file
204
.agents/skills/cloudflare-deploy/references/pages/patterns.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Patterns
|
||||
|
||||
## API Routes
|
||||
|
||||
```typescript
|
||||
// functions/api/todos/[id].ts
|
||||
export const onRequestGet: PagesFunction<Env> = async ({ env, params }) => {
|
||||
const todo = await env.DB.prepare('SELECT * FROM todos WHERE id = ?').bind(params.id).first();
|
||||
if (!todo) return new Response('Not found', { status: 404 });
|
||||
return Response.json(todo);
|
||||
};
|
||||
|
||||
export const onRequestPut: PagesFunction<Env> = async ({ env, params, request }) => {
|
||||
const body = await request.json();
|
||||
await env.DB.prepare('UPDATE todos SET title = ?, completed = ? WHERE id = ?')
|
||||
.bind(body.title, body.completed, params.id).run();
|
||||
return Response.json({ success: true });
|
||||
};
|
||||
// Also: onRequestDelete, onRequestPost
|
||||
```
|
||||
|
||||
## Auth Middleware
|
||||
|
||||
```typescript
|
||||
// functions/_middleware.ts
|
||||
const auth: PagesFunction<Env> = async (context) => {
|
||||
if (context.request.url.includes('/public/')) return context.next();
|
||||
const authHeader = context.request.headers.get('Authorization');
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = await verifyJWT(authHeader.substring(7), context.env.JWT_SECRET);
|
||||
context.data.user = payload;
|
||||
return context.next();
|
||||
} catch (err) {
|
||||
return new Response('Invalid token', { status: 401 });
|
||||
}
|
||||
};
|
||||
export const onRequest = [auth];
|
||||
```
|
||||
|
||||
## CORS
|
||||
|
||||
```typescript
|
||||
// functions/api/_middleware.ts
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
};
|
||||
|
||||
export const onRequest: PagesFunction = async (context) => {
|
||||
if (context.request.method === 'OPTIONS') {
|
||||
return new Response(null, {headers: corsHeaders});
|
||||
}
|
||||
const response = await context.next();
|
||||
Object.entries(corsHeaders).forEach(([k, v]) => response.headers.set(k, v));
|
||||
return response;
|
||||
};
|
||||
```
|
||||
|
||||
## Form Handling
|
||||
|
||||
```typescript
|
||||
// functions/api/contact.ts
|
||||
export const onRequestPost: PagesFunction<Env> = async ({ request, env }) => {
|
||||
const formData = await request.formData();
|
||||
await env.QUEUE.send({name: formData.get('name'), email: formData.get('email')});
|
||||
return new Response('<h1>Thanks!</h1>', { headers: { 'Content-Type': 'text/html' } });
|
||||
};
|
||||
```
|
||||
|
||||
## Background Tasks
|
||||
|
||||
```typescript
|
||||
export const onRequestPost: PagesFunction = async ({ request, waitUntil }) => {
|
||||
const data = await request.json();
|
||||
waitUntil(fetch('https://api.example.com/webhook', {
|
||||
method: 'POST', body: JSON.stringify(data)
|
||||
}));
|
||||
return Response.json({ queued: true });
|
||||
};
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```typescript
|
||||
// functions/_middleware.ts
|
||||
const errorHandler: PagesFunction = async (context) => {
|
||||
try {
|
||||
return await context.next();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
if (context.request.url.includes('/api/')) {
|
||||
return Response.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
return new Response(`<h1>Error</h1><p>${error.message}</p>`, {
|
||||
status: 500, headers: { 'Content-Type': 'text/html' }
|
||||
});
|
||||
}
|
||||
};
|
||||
export const onRequest = [errorHandler];
|
||||
```
|
||||
|
||||
## Caching
|
||||
|
||||
```typescript
|
||||
// functions/api/data.ts
|
||||
export const onRequestGet: PagesFunction<Env> = async ({ env, request }) => {
|
||||
const cacheKey = `data:${new URL(request.url).pathname}`;
|
||||
const cached = await env.KV.get(cacheKey, 'json');
|
||||
if (cached) return Response.json(cached, { headers: { 'X-Cache': 'HIT' } });
|
||||
|
||||
const data = await env.DB.prepare('SELECT * FROM data').first();
|
||||
await env.KV.put(cacheKey, JSON.stringify(data), {expirationTtl: 3600});
|
||||
return Response.json(data, {headers: {'X-Cache': 'MISS'}});
|
||||
};
|
||||
```
|
||||
|
||||
## Smart Placement for Database Apps
|
||||
|
||||
Enable Smart Placement for apps with D1 or centralized data sources:
|
||||
|
||||
```jsonc
|
||||
// wrangler.jsonc
|
||||
{
|
||||
"name": "global-app",
|
||||
"placement": {
|
||||
"mode": "smart"
|
||||
},
|
||||
"d1_databases": [{
|
||||
"binding": "DB",
|
||||
"database_id": "your-db-id"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// functions/api/data.ts
|
||||
export const onRequestGet: PagesFunction<Env> = async ({ env }) => {
|
||||
// Smart Placement optimizes execution location over time
|
||||
// Balances user location vs database location
|
||||
const data = await env.DB.prepare('SELECT * FROM products LIMIT 10').all();
|
||||
return Response.json(data);
|
||||
};
|
||||
```
|
||||
|
||||
**Best for**: Read-heavy apps with D1/Durable Objects in specific regions.
|
||||
**Not needed**: Apps without data locality constraints or with evenly distributed traffic.
|
||||
|
||||
## Framework Integration
|
||||
|
||||
**Supported** (2026): SvelteKit, Astro, Nuxt, Qwik, Solid Start
|
||||
|
||||
```bash
|
||||
npm create cloudflare@latest my-app -- --framework=svelte
|
||||
```
|
||||
|
||||
### SvelteKit
|
||||
```typescript
|
||||
// src/routes/+page.server.ts
|
||||
export const load = async ({ platform }) => {
|
||||
const todos = await platform.env.DB.prepare('SELECT * FROM todos').all();
|
||||
return { todos: todos.results };
|
||||
};
|
||||
```
|
||||
|
||||
### Astro
|
||||
```astro
|
||||
---
|
||||
const { DB } = Astro.locals.runtime.env;
|
||||
const todos = await DB.prepare('SELECT * FROM todos').all();
|
||||
---
|
||||
<ul>{todos.results.map(t => <li>{t.title}</li>)}</ul>
|
||||
```
|
||||
|
||||
### Nuxt
|
||||
```typescript
|
||||
// server/api/todos.get.ts
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { DB } = event.context.cloudflare.env;
|
||||
return await DB.prepare('SELECT * FROM todos').all();
|
||||
});
|
||||
```
|
||||
|
||||
**⚠️ Framework Status** (2026):
|
||||
- ✅ **Supported**: SvelteKit, Astro, Nuxt, Qwik, Solid Start
|
||||
- ❌ **Deprecated**: Next.js (`@cloudflare/next-on-pages`), Remix (`@remix-run/cloudflare-pages`)
|
||||
|
||||
For deprecated frameworks, see [gotchas.md](./gotchas.md#framework-specific) for migration options.
|
||||
|
||||
[Framework Guides](https://developers.cloudflare.com/pages/framework-guides/)
|
||||
|
||||
## Monorepo
|
||||
|
||||
Dashboard → Settings → Build → Root directory. Set to subproject (e.g., `apps/web`).
|
||||
|
||||
## Best Practices
|
||||
|
||||
**Performance**: Exclude static via `_routes.json`; cache with KV; keep bundle < 1MB
|
||||
**Security**: Use secrets (not vars); validate inputs; rate limit with KV/DO
|
||||
**Workflow**: Preview per branch; local dev with `wrangler pages dev`; instant rollbacks in Dashboard
|
||||
Reference in New Issue
Block a user