home / skills / brixtonpham / claude-config / cloudflare
npx playbooks add skill brixtonpham/claude-config --skill cloudflareReview the files below or copy the command above to add this skill to your agents.
---
name: cloudflare
description: Guide for building applications on Cloudflare's edge platform. Use when implementing serverless functions (Workers), edge databases (D1), storage (R2, KV), real-time apps (Durable Objects), AI features (Workers AI, AI Gateway), static sites (Pages), or any edge computing solutions.
license: MIT
version: 1.0.0
---
# Cloudflare Developer Platform Skill
Cloudflare Developer Platform is a comprehensive edge computing ecosystem for building full-stack applications on Cloudflare's global network. It includes serverless functions, databases, storage, AI/ML capabilities, and static site hosting.
## When to Use This Skill
Use this skill when:
- Building serverless applications on the edge
- Implementing edge databases (D1 SQLite)
- Working with object storage (R2) or key-value stores (KV)
- Creating real-time applications with WebSockets (Durable Objects)
- Integrating AI/ML capabilities (Workers AI, AI Gateway, Agents)
- Deploying static sites with serverless functions (Pages)
- Building full-stack applications with frameworks (Next.js, Remix, Astro, etc.)
- Implementing message queues and background jobs (Queues)
- Optimizing for global performance and low latency
## Core Concepts
### Edge Computing Platform
**Cloudflare's Edge Network**: Code runs on servers globally distributed across 300+ cities, executing requests from the nearest location for ultra-low latency.
**Key Components**:
- **Workers**: Serverless functions on the edge
- **D1**: SQLite database with global read replication
- **KV**: Distributed key-value store with eventual consistency
- **R2**: Object storage with zero egress fees
- **Durable Objects**: Stateful compute with WebSocket support
- **Queues**: Message queue system for async processing
- **Pages**: Static site hosting with serverless functions
- **Workers AI**: Run AI models on the edge
- **AI Gateway**: Unified interface for AI providers
### Execution Model
**V8 Isolates**: Lightweight execution environments (faster than containers) with:
- Millisecond cold starts
- Zero infrastructure management
- Automatic scaling
- Pay-per-request pricing
**Handler Types**:
- `fetch`: HTTP requests
- `scheduled`: Cron jobs
- `queue`: Message processing
- `tail`: Log aggregation
- `email`: Email handling
- `alarm`: Durable Object timers
## Getting Started with Workers
### Installation
```bash
# Install Wrangler CLI
npm install -g wrangler
# Login to Cloudflare
wrangler login
# Create new project
wrangler init my-worker
cd my-worker
# Start local development
wrangler dev
# Deploy to production
wrangler deploy
```
### Basic Worker
```typescript
// src/index.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
return new Response('Hello from Cloudflare Workers!');
}
};
```
### Configuration (wrangler.toml)
```toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
# Environment variables
[vars]
ENVIRONMENT = "production"
# Bindings (added per product below)
```
### Language Support
- **JavaScript/TypeScript**: Primary language (full Node.js compatibility)
- **Python**: Beta support via Workers Python
- **Rust**: Compile to WebAssembly
## Storage Products
### D1 (SQLite Database)
**Use Cases**: Relational data, complex queries, ACID transactions
**Setup**:
```bash
# Create database
wrangler d1 create my-database
# Add to wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "YOUR_DATABASE_ID"
# Generate and apply schema
wrangler d1 execute my-database --file=./schema.sql
```
**Usage**:
```typescript
export default {
async fetch(request: Request, env: Env) {
// Query
const result = await env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(userId).first();
// Insert
await env.DB.prepare(
"INSERT INTO users (name, email) VALUES (?, ?)"
).bind("Alice", "[email protected]").run();
// Batch (atomic)
await env.DB.batch([
env.DB.prepare("UPDATE accounts SET balance = balance - 100 WHERE id = ?").bind(user1),
env.DB.prepare("UPDATE accounts SET balance = balance + 100 WHERE id = ?").bind(user2)
]);
return new Response(JSON.stringify(result));
}
};
```
**Key Features**:
- Global read replication (low-latency reads)
- Single-writer consistency
- Standard SQLite syntax
- 25GB database size limit
### KV (Key-Value Store)
**Use Cases**: Cache, sessions, feature flags, rate limiting
**Setup**:
```bash
# Create namespace
wrangler kv:namespace create MY_KV
# Add to wrangler.toml
[[kv_namespaces]]
binding = "KV"
id = "YOUR_NAMESPACE_ID"
```
**Usage**:
```typescript
export default {
async fetch(request: Request, env: Env) {
// Put with TTL
await env.KV.put("session:token", JSON.stringify(data), {
expirationTtl: 3600 // 1 hour
});
// Get
const data = await env.KV.get("session:token", "json");
// Delete
await env.KV.delete("session:token");
// List with prefix
const list = await env.KV.list({ prefix: "user:123:" });
return new Response(JSON.stringify(data));
}
};
```
**Key Features**:
- Sub-millisecond reads (edge-cached)
- Eventual consistency (~60 seconds globally)
- 25MB value size limit
- Automatic expiration (TTL)
### R2 (Object Storage)
**Use Cases**: File storage, media hosting, backups, static assets
**Setup**:
```bash
# Create bucket
wrangler r2 bucket create my-bucket
# Add to wrangler.toml
[[r2_buckets]]
binding = "R2_BUCKET"
bucket_name = "my-bucket"
```
**Usage**:
```typescript
export default {
async fetch(request: Request, env: Env) {
// Put object
await env.R2_BUCKET.put("path/to/file.jpg", fileBuffer, {
httpMetadata: {
contentType: "image/jpeg"
}
});
// Get object
const object = await env.R2_BUCKET.get("path/to/file.jpg");
if (!object) {
return new Response("Not found", { status: 404 });
}
// Stream response
return new Response(object.body, {
headers: {
"Content-Type": object.httpMetadata?.contentType || "application/octet-stream"
}
});
// Delete
await env.R2_BUCKET.delete("path/to/file.jpg");
// List
const list = await env.R2_BUCKET.list({ prefix: "uploads/" });
}
};
```
**Key Features**:
- S3-compatible API
- **Zero egress fees** (huge cost advantage)
- Unlimited storage
- 5TB object size limit
- Multipart upload support
### Durable Objects
**Use Cases**: Real-time apps, WebSockets, coordination, stateful logic
**Setup**:
```toml
# wrangler.toml
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"
script_name = "my-worker"
```
**Usage**:
```typescript
// Define Durable Object class
export class Counter {
state: DurableObjectState;
constructor(state: DurableObjectState, env: Env) {
this.state = state;
}
async fetch(request: Request) {
// Get current count
let count = (await this.state.storage.get<number>('count')) || 0;
// Increment
count++;
await this.state.storage.put('count', count);
return new Response(JSON.stringify({ count }));
}
}
// Use in Worker
export default {
async fetch(request: Request, env: Env) {
// Get Durable Object instance
const id = env.COUNTER.idFromName("global-counter");
const counter = env.COUNTER.get(id);
// Forward request
return counter.fetch(request);
}
};
```
**WebSocket Example**:
```typescript
export class ChatRoom {
state: DurableObjectState;
sessions: Set<WebSocket>;
constructor(state: DurableObjectState) {
this.state = state;
this.sessions = new Set();
}
async fetch(request: Request) {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
this.state.acceptWebSocket(server);
this.sessions.add(server);
return new Response(null, { status: 101, webSocket: client });
}
async webSocketMessage(ws: WebSocket, message: string) {
// Broadcast to all connected clients
for (const session of this.sessions) {
session.send(message);
}
}
async webSocketClose(ws: WebSocket) {
this.sessions.delete(ws);
}
}
```
**Key Features**:
- Single-instance coordination (strong consistency)
- Persistent storage (1GB limit on paid plans)
- WebSocket support
- Automatic hibernation for inactive objects
### Queues
**Use Cases**: Background jobs, email sending, async processing
**Setup**:
```toml
# wrangler.toml
[[queues.producers]]
binding = "MY_QUEUE"
queue = "my-queue"
[[queues.consumers]]
queue = "my-queue"
max_batch_size = 10
max_batch_timeout = 30
```
**Usage**:
```typescript
// Producer: Send messages
export default {
async fetch(request: Request, env: Env) {
await env.MY_QUEUE.send({
type: 'email',
to: '[email protected]',
subject: 'Welcome!'
});
return new Response('Message queued');
}
};
// Consumer: Process messages
export default {
async queue(batch: MessageBatch<any>, env: Env) {
for (const message of batch.messages) {
try {
await processMessage(message.body);
message.ack(); // Acknowledge success
} catch (error) {
message.retry(); // Retry on failure
}
}
}
};
```
**Key Features**:
- At-least-once delivery
- Automatic retries (exponential backoff)
- Dead-letter queue support
- Batch processing
## AI Products
### Workers AI
**Use Cases**: Run AI models directly on the edge
**Setup**:
```toml
# wrangler.toml
[ai]
binding = "AI"
```
**Usage**:
```typescript
export default {
async fetch(request: Request, env: Env) {
// Text generation
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{ role: 'user', content: 'What is edge computing?' }
]
});
// Image classification
const imageResponse = await env.AI.run('@cf/microsoft/resnet-50', {
image: imageBuffer
});
// Text embeddings
const embeddings = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: 'Hello world'
});
return new Response(JSON.stringify(response));
}
};
```
**Available Models**:
- LLMs: Llama 3, Mistral, Gemma, Qwen
- Image: Stable Diffusion, DALL-E, ResNet
- Embeddings: BGE, GTE
- Translation, summarization, sentiment analysis
### AI Gateway
**Use Cases**: Unified interface for AI providers with caching, rate limiting, analytics
**Setup**:
```typescript
// OpenAI via AI Gateway
const response = await fetch(
'https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/openai/chat/completions',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello!' }]
})
}
);
```
**Features**:
- Request caching (reduce costs)
- Rate limiting
- Analytics and logging
- Supports OpenAI, Anthropic, HuggingFace, etc.
### Agents
**Use Cases**: Build AI agents with tools and workflows
```typescript
import { Agent } from '@cloudflare/agents';
export default {
async fetch(request: Request, env: Env) {
const agent = new Agent({
model: '@cf/meta/llama-3-8b-instruct',
tools: [
{
name: 'get_weather',
description: 'Get current weather',
parameters: {
type: 'object',
properties: {
location: { type: 'string' }
}
},
handler: async ({ location }) => {
// Fetch weather data
return { temperature: 72, conditions: 'sunny' };
}
}
]
});
const result = await agent.run('What is the weather in San Francisco?');
return new Response(JSON.stringify(result));
}
};
```
### AI Search (RAG)
**Use Cases**: Build retrieval-augmented generation applications
```typescript
import { VectorizeIndex } from '@cloudflare/workers-types';
export default {
async fetch(request: Request, env: Env) {
// Generate embeddings
const embeddings = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: query
});
// Search vector database
const results = await env.VECTORIZE_INDEX.query(embeddings.data[0], {
topK: 5
});
// Generate response with context
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{
role: 'system',
content: `Context: ${results.matches.map(m => m.metadata.text).join('\n')}`
},
{ role: 'user', content: query }
]
});
return new Response(JSON.stringify(response));
}
};
```
## Cloudflare Pages
### Static Sites + Serverless Functions
**Deployment**:
```bash
# Deploy via Git (recommended)
# Connect GitHub repo in Cloudflare dashboard
# Or deploy via CLI
wrangler pages deploy ./dist
```
### Pages Functions
Directory-based routing in `functions/`:
```
functions/
├── api/
│ ├── users/
│ │ └── [id].ts # /api/users/:id
│ └── posts.ts # /api/posts
└── _middleware.ts # Global middleware
```
**Example Function**:
```typescript
// functions/api/users/[id].ts
export async function onRequestGet(context) {
const { params, env } = context;
const user = await env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(params.id).first();
return new Response(JSON.stringify(user), {
headers: { 'Content-Type': 'application/json' }
});
}
```
**Middleware**:
```typescript
// functions/_middleware.ts
export async function onRequest(context) {
const start = Date.now();
const response = await context.next();
const duration = Date.now() - start;
console.log(`${context.request.method} ${context.request.url} - ${duration}ms`);
return response;
}
```
### Framework Support
**Next.js**:
```bash
npx create-next-app@latest my-app
cd my-app
npm install -D @cloudflare/next-on-pages
npx @cloudflare/next-on-pages
wrangler pages deploy .vercel/output/static
```
**Remix**:
```bash
npx create-remix@latest --template cloudflare/remix
```
**Astro**:
```bash
npm create astro@latest
# Select "Cloudflare" adapter during setup
```
**SvelteKit**:
```bash
npm create svelte@latest
npm install -D @sveltejs/adapter-cloudflare
```
## Wrangler CLI Essentials
### Core Commands
```bash
# Development
wrangler dev # Local development server
wrangler dev --remote # Dev on real Cloudflare infrastructure
# Deployment
wrangler deploy # Deploy to production
wrangler deploy --dry-run # Preview changes without deploying
# Logs
wrangler tail # Real-time logs
wrangler tail --format pretty # Formatted logs
# Versions
wrangler deployments list # List deployments
wrangler rollback [version] # Rollback to previous version
# Secrets
wrangler secret put SECRET_NAME # Add secret
wrangler secret list # List secrets
wrangler secret delete SECRET_NAME # Delete secret
```
### Project Management
```bash
# Create projects
wrangler init my-worker # Create Worker
wrangler pages project create # Create Pages project
# Database
wrangler d1 create my-db # Create D1 database
wrangler d1 execute my-db --file=schema.sql
wrangler d1 execute my-db --command="SELECT * FROM users"
# KV
wrangler kv:namespace create MY_KV
wrangler kv:key put --binding=MY_KV "key" "value"
wrangler kv:key get --binding=MY_KV "key"
# R2
wrangler r2 bucket create my-bucket
wrangler r2 object put my-bucket/file.txt --file=./file.txt
```
## Integration Patterns
### Full-Stack Application Architecture
```
┌─────────────────────────────────────────┐
│ Cloudflare Pages (Frontend) │
│ Next.js / Remix / Astro / SvelteKit │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Workers (API Layer / BFF) │
│ - Routing │
│ - Authentication │
│ - Business logic │
└─┬──────┬──────┬──────┬──────┬───────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────────────┐
│ D1 │ │ KV │ │ R2 │ │ DO │ │ Workers AI │
└────┘ └────┘ └────┘ └────┘ └────────────┘
```
### Polyglot Storage Pattern
```typescript
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// KV: Fast cache
const cached = await env.KV.get(url.pathname);
if (cached) return new Response(cached);
// D1: Structured data
const user = await env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(userId).first();
// R2: Media files
const avatar = await env.R2_BUCKET.get(`avatars/${user.id}.jpg`);
// Durable Objects: Real-time coordination
const chat = env.CHAT_ROOM.get(env.CHAT_ROOM.idFromName(roomId));
// Queue: Async processing
await env.EMAIL_QUEUE.send({ to: user.email, template: 'welcome' });
return new Response(JSON.stringify({ user, avatar }));
}
};
```
### Authentication Pattern
```typescript
import { verifyJWT, createJWT } from './jwt';
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// Login
if (url.pathname === '/api/login') {
const { email, password } = await request.json();
const user = await env.DB.prepare(
"SELECT * FROM users WHERE email = ?"
).bind(email).first();
if (!user || !await verifyPassword(password, user.password_hash)) {
return new Response('Invalid credentials', { status: 401 });
}
const token = await createJWT({ userId: user.id }, env.JWT_SECRET);
return new Response(JSON.stringify({ token }), {
headers: { 'Content-Type': 'application/json' }
});
}
// Protected route
const authHeader = request.headers.get('Authorization');
if (!authHeader) {
return new Response('Unauthorized', { status: 401 });
}
const token = authHeader.replace('Bearer ', '');
const payload = await verifyJWT(token, env.JWT_SECRET);
// Store session in KV
await env.KV.put(`session:${payload.userId}`, JSON.stringify(payload), {
expirationTtl: 86400 // 24 hours
});
return new Response('Authenticated');
}
};
```
### Cache Strategy
```typescript
export default {
async fetch(request: Request, env: Env) {
const cache = caches.default;
const cacheKey = new Request(request.url);
// Check cache
let response = await cache.match(cacheKey);
if (response) return response;
// Check KV (distributed cache)
const kvCached = await env.KV.get(request.url);
if (kvCached) {
response = new Response(kvCached);
await cache.put(cacheKey, response.clone());
return response;
}
// Fetch from origin (D1, R2, etc.)
const data = await fetchFromOrigin(request, env);
response = new Response(data);
// Store in both caches
await cache.put(cacheKey, response.clone());
await env.KV.put(request.url, data, { expirationTtl: 3600 });
return response;
}
};
```
## Best Practices
### Performance
1. **Minimize Cold Starts**: Keep Workers lightweight (<1MB bundled)
2. **Use Bindings Over Fetch**: Direct bindings are faster than HTTP calls
3. **Edge Caching**: Leverage KV and Cache API for frequently accessed data
4. **Batch Operations**: Use D1 batch for multiple queries
5. **Stream Large Responses**: Use `Response.body` streams for large files
### Security
1. **Secrets Management**: Use `wrangler secret` for API keys
2. **Environment Isolation**: Separate production/staging/development
3. **Input Validation**: Sanitize user input
4. **Rate Limiting**: Use KV or Durable Objects for rate limiting
5. **CORS**: Configure proper CORS headers
### Cost Optimization
1. **R2 for Large Files**: Zero egress fees vs S3
2. **KV for Caching**: Reduce D1/R2 requests
3. **Request Deduplication**: Cache identical requests
4. **Efficient Queries**: Index D1 tables properly
5. **Monitor Usage**: Use Cloudflare Analytics
### Development Workflow
1. **Local Development**: Use `wrangler dev` for testing
2. **Type Safety**: Use TypeScript with `@cloudflare/workers-types`
3. **Testing**: Use Vitest with `unstable_dev()`
4. **CI/CD**: GitHub Actions with `cloudflare/wrangler-action`
5. **Gradual Deployments**: Use percentage-based rollouts
## Common Patterns
### API Gateway
```typescript
import { Hono } from 'hono';
const app = new Hono();
app.get('/api/users/:id', async (c) => {
const user = await c.env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(c.req.param('id')).first();
return c.json(user);
});
app.post('/api/users', async (c) => {
const { name, email } = await c.req.json();
await c.env.DB.prepare(
"INSERT INTO users (name, email) VALUES (?, ?)"
).bind(name, email).run();
return c.json({ success: true }, 201);
});
export default app;
```
### Image Transformation
```typescript
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
const imageKey = url.pathname.replace('/images/', '');
// Get from R2
const object = await env.R2_BUCKET.get(imageKey);
if (!object) {
return new Response('Not found', { status: 404 });
}
// Transform with Cloudflare Images
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata?.contentType || 'image/jpeg',
'Cache-Control': 'public, max-age=86400',
'cf-image-resize': JSON.stringify({
width: 800,
height: 600,
fit: 'cover'
})
}
});
}
};
```
### Rate Limiting (KV)
```typescript
async function rateLimit(ip: string, env: Env): Promise<boolean> {
const key = `ratelimit:${ip}`;
const limit = 100; // requests per minute
const window = 60; // seconds
const current = await env.KV.get(key);
const count = current ? parseInt(current) : 0;
if (count >= limit) {
return false; // Rate limit exceeded
}
await env.KV.put(key, (count + 1).toString(), {
expirationTtl: window
});
return true;
}
export default {
async fetch(request: Request, env: Env) {
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
if (!await rateLimit(ip, env)) {
return new Response('Rate limit exceeded', { status: 429 });
}
return new Response('OK');
}
};
```
### Scheduled Jobs
```toml
# wrangler.toml
[triggers]
crons = ["0 0 * * *"] # Daily at midnight
```
```typescript
export default {
async scheduled(event: ScheduledEvent, env: Env) {
// Cleanup old sessions
const sessions = await env.KV.list({ prefix: 'session:' });
for (const key of sessions.keys) {
const session = await env.KV.get(key.name, 'json');
if (session.expiresAt < Date.now()) {
await env.KV.delete(key.name);
}
}
}
};
```
## Troubleshooting
### Common Issues
**"Module not found" errors**
- Ensure dependencies are in `package.json`
- Run `npm install` before deploying
- Check compatibility_date in wrangler.toml
**Database connection errors (D1)**
- Verify database_id in wrangler.toml
- Check database exists: `wrangler d1 list`
- Run migrations: `wrangler d1 execute DB --file=schema.sql`
**KV not found errors**
- Create namespace: `wrangler kv:namespace create MY_KV`
- Add binding to wrangler.toml
- Deploy after configuration changes
**Cold start timeout**
- Reduce bundle size (<1MB ideal)
- Remove unnecessary dependencies
- Use dynamic imports for large libraries
**CORS errors**
- Add CORS headers to responses:
```typescript
return new Response(data, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
}
});
```
**Deployment fails**
- Check wrangler version: `wrangler --version`
- Verify authentication: `wrangler whoami`
- Review build errors in console output
### Debugging
```bash
# Real-time logs
wrangler tail
# Local debugging with breakpoints
wrangler dev --local
# Remote debugging
wrangler dev --remote
# Check deployment status
wrangler deployments list
```
## Decision Matrix
| Need | Choose |
|------|--------|
| Sub-millisecond reads | KV |
| SQL queries | D1 |
| Large files (>25MB) | R2 |
| Real-time WebSockets | Durable Objects |
| Async background jobs | Queues |
| ACID transactions | D1 |
| Strong consistency | Durable Objects |
| Zero egress costs | R2 |
| AI inference | Workers AI |
| Static site hosting | Pages |
| Serverless functions | Workers |
| Multi-provider AI | AI Gateway |
## Framework-Specific Guides
### Next.js
- Use `@cloudflare/next-on-pages` adapter
- Configure `next.config.js` for edge runtime
- Deploy via `wrangler pages deploy`
### Remix
- Use official Cloudflare template
- Configure `server.ts` for Workers
- Access bindings via `context.cloudflare.env`
### Astro
- Use `@astrojs/cloudflare` adapter
- Enable SSR in `astro.config.mjs`
- Access env via `Astro.locals.runtime.env`
### SvelteKit
- Use `@sveltejs/adapter-cloudflare`
- Configure in `svelte.config.js`
- Access platform via `event.platform.env`
## Resources
- **Documentation**: https://developers.cloudflare.com
- **Wrangler CLI**: https://developers.cloudflare.com/workers/wrangler/
- **Discord Community**: https://discord.cloudflare.com
- **Examples**: https://developers.cloudflare.com/workers/examples/
- **GitHub**: https://github.com/cloudflare
- **Status Page**: https://www.cloudflarestatus.com
## Implementation Checklist
### Workers Setup
- [ ] Install Wrangler CLI (`npm install -g wrangler`)
- [ ] Login to Cloudflare (`wrangler login`)
- [ ] Create project (`wrangler init`)
- [ ] Configure wrangler.toml
- [ ] Add environment variables/secrets
- [ ] Test locally (`wrangler dev`)
- [ ] Deploy (`wrangler deploy`)
### Storage Setup (as needed)
- [ ] Create D1 database and apply schema
- [ ] Create KV namespace
- [ ] Create R2 bucket
- [ ] Configure Durable Objects
- [ ] Set up Queues
- [ ] Add bindings to wrangler.toml
### Pages Setup
- [ ] Connect Git repository or use CLI
- [ ] Configure build settings
- [ ] Set environment variables
- [ ] Add Pages Functions (if needed)
- [ ] Deploy and test
### Production Checklist
- [ ] Set up custom domain
- [ ] Configure DNS records
- [ ] Enable SSL/TLS
- [ ] Set up monitoring/analytics
- [ ] Configure rate limiting
- [ ] Implement error handling
- [ ] Set up CI/CD pipeline
- [ ] Test gradual deployments
- [ ] Document rollback procedure
- [ ] Configure logging/observability