| name | cloudflare-workers |
| description | Comprehensive guide for building serverless applications with Cloudflare Workers. Use when developing Workers, configuring bindings, implementing runtime APIs, testing Workers, using Wrangler CLI, deploying to production, or building edge functions with JavaScript/TypeScript/Python/Rust. |
| license | MIT |
| version | 1.0.0 |
Cloudflare Workers Skill
Cloudflare Workers is a serverless execution environment that runs JavaScript, TypeScript, Python, and Rust code on Cloudflare's global edge network across 300+ cities worldwide.
Reference
When to Use This Skill
Use this skill when:
- Creating new Cloudflare Workers applications
- Configuring Worker bindings (D1, KV, R2, Durable Objects, etc.)
- Implementing Worker runtime APIs (fetch, cache, HTMLRewriter, WebSockets)
- Writing and testing Workers locally with Wrangler
- Deploying Workers to production
- Implementing cron triggers and scheduled jobs
- Building API endpoints and middleware
- Working with Workers static assets
- Setting up CI/CD for Workers
- Migrating from service workers to ES modules
- Debugging and monitoring Workers
- Optimizing Worker performance
Core Concepts
Execution Model
V8 Isolates: Workers run in lightweight V8 isolates (not containers):
- Millisecond cold starts (faster than containers)
- Zero infrastructure management
- Automatic global scaling
- Pay-per-request pricing
Request Lifecycle:
- Request arrives at nearest Cloudflare data center
- Worker executes in V8 isolate
- Response returned to client
- Isolate may be reused for subsequent requests
Key Characteristics:
- Maximum CPU time: 50ms (Free), 30 seconds (Paid)
- Maximum memory: 128MB
- Executes at the edge (closest to user)
- No state between requests (use Durable Objects for state)
Worker Formats
ES Modules (Recommended):
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
return new Response('Hello World!');
}
};
Service Worker (Legacy):
addEventListener('fetch', (event) => {
event.respondWith(new Response('Hello World!'));
});
Handler Types
Workers support multiple event types:
Fetch Handler (HTTP requests):
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
return new Response('Hello');
}
};
Scheduled Handler (Cron jobs):
export default {
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
await fetch('https://api.example.com/cleanup');
}
};
Queue Handler (Message processing):
export default {
async queue(batch: MessageBatch, env: Env, ctx: ExecutionContext): Promise<void> {
for (const message of batch.messages) {
await processMessage(message.body);
}
}
};
Email Handler (Email routing):
export default {
async email(message: ForwardableEmailMessage, env: Env, ctx: ExecutionContext): Promise<void> {
await message.forward('destination@example.com');
}
};
Tail Handler (Log aggregation):
export default {
async tail(events: TraceItem[], env: Env, ctx: ExecutionContext): Promise<void> {
}
};
API Key Configuration
Cloudflare Workers require API credentials for CLI operations. The system searches for API keys in this order:
process.env - Runtime environment variables
<project-root>/.env - Project-level environment file
.claude/.env - Claude configuration directory
.claude/skills/.env - Skills shared configuration
.claude/skills/cloudflare-workers/.env - Skill-specific configuration
Required Environment Variables:
CLOUDFLARE_API_TOKEN=your_api_token_here
CLOUDFLARE_ACCOUNT_ID=your_account_id_here
Where to Get Credentials:
- API Token: Cloudflare Dashboard → My Profile → API Tokens → Create Token
- Use "Edit Cloudflare Workers" template for Workers-specific permissions
- Account ID: Cloudflare Dashboard → Overview → Account ID (right sidebar)
Example .env File:
CLOUDFLARE_API_TOKEN=abc123...
CLOUDFLARE_ACCOUNT_ID=def456...
Wrangler CLI
Installation & Setup
npm install -g wrangler
npx wrangler <command>
wrangler login
wrangler --version
Project Management
wrangler init my-worker
wrangler init my-worker --template <template-url>
wrangler dev
wrangler dev --remote
wrangler dev --port 8080
wrangler deploy
wrangler deploy --dry-run
wrangler deploy --env staging
wrangler tail
wrangler tail --format pretty
wrangler tail --status error
wrangler deployments list
wrangler rollback [version-id]
wrangler versions list
Configuration (wrangler.toml)
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]
routes = [
{ pattern = "example.com/*", zone_name = "example.com" }
]
[vars]
ENVIRONMENT = "production"
API_VERSION = "v1"
[triggers]
crons = ["0 0 * * *"]
[build]
command = "npm run build"
watch_dirs = ["src"]
[env.staging]
name = "my-worker-staging"
routes = [
{ pattern = "staging.example.com/*", zone_name = "example.com" }
]
[env.staging.vars]
ENVIRONMENT = "staging"
Secrets Management
wrangler secret put API_KEY
wrangler secret list
wrangler secret delete API_KEY
echo "VALUE" | wrangler secret put API_KEY
Runtime APIs
Fetch API
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
const method = request.method;
const headers = request.headers;
const body = await request.text();
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' })
});
const data = await response.json();
return new Response(JSON.stringify(data), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=3600'
}
});
}
};
Headers API
const userAgent = request.headers.get('User-Agent');
const allHeaders = Object.fromEntries(request.headers);
const headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.append('X-Custom-Header', 'value');
const country = request.cf?.country;
const colo = request.cf?.colo;
const clientIP = request.headers.get('CF-Connecting-IP');
return new Response(body, { headers });
Cache API
export default {
async fetch(request: Request): Promise<Response> {
const cache = caches.default;
let response = await cache.match(request);
if (!response) {
response = await fetch(request);
const cacheResponse = response.clone();
ctx.waitUntil(cache.put(request, cacheResponse));
}
return response;
}
};
Cache with custom key:
const cacheKey = new Request(url.toString(), request);
const response = await cache.match(cacheKey);
if (!response) {
response = await fetch(request);
ctx.waitUntil(cache.put(cacheKey, response.clone()));
}
Cache with TTL:
const response = await fetch('https://api.example.com/data', {
cf: {
cacheTtl: 3600,
cacheEverything: true,
cacheKey: 'custom-key'
}
});
HTMLRewriter
export default {
async fetch(request: Request): Promise<Response> {
const response = await fetch(request);
return new HTMLRewriter()
.on('title', {
element(element) {
element.setInnerContent('New Title');
}
})
.on('a[href]', {
element(element) {
const href = element.getAttribute('href');
element.setAttribute('href', href.replace('http://', 'https://'));
}
})
.on('script', {
element(element) {
element.remove();
}
})
.transform(response);
}
};
Text manipulation:
new HTMLRewriter()
.on('p', {
text(text) {
if (text.text.includes('replace-me')) {
text.replace('new-text');
}
}
})
.transform(response);
WebSockets
export default {
async fetch(request: Request): Promise<Response> {
const upgradeHeader = request.headers.get('Upgrade');
if (upgradeHeader !== 'websocket') {
return new Response('Expected WebSocket', { status: 426 });
}
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
server.accept();
server.addEventListener('message', (event) => {
server.send(`Echo: ${event.data}`);
});
server.addEventListener('close', () => {
console.log('WebSocket closed');
});
return new Response(null, {
status: 101,
webSocket: client
});
}
};
Streams API
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();
writer.write(new TextEncoder().encode('chunk 1'));
writer.write(new TextEncoder().encode('chunk 2'));
writer.close();
return new Response(readable, {
headers: { 'Content-Type': 'text/plain' }
});
Stream transformation:
const response = await fetch('https://example.com/large-file');
const { readable, writable } = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk);
}
});
response.body.pipeTo(writable);
return new Response(readable);
Context API
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
ctx.waitUntil(
fetch('https://analytics.example.com/log', {
method: 'POST',
body: JSON.stringify({ url: request.url })
})
);
ctx.passThroughOnException();
return new Response('OK');
}
};
Web Crypto API
const data = new TextEncoder().encode('message');
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode('secret'),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign', 'verify']
);
const signature = await crypto.subtle.sign('HMAC', key, data);
const valid = await crypto.subtle.verify('HMAC', key, signature, data);
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
const uuid = crypto.randomUUID();
const equal = crypto.timingSafeEqual(buffer1, buffer2);
Bindings
Environment Variables
[vars]
API_URL = "https://api.example.com"
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const apiUrl = env.API_URL;
return new Response(apiUrl);
}
};
KV Namespace
[[kv_namespaces]]
binding = "MY_KV"
id = "your-namespace-id"
export default {
async fetch(request: Request, env: Env): Promise<Response> {
await env.MY_KV.put('key', 'value', {
expirationTtl: 3600,
metadata: { userId: '123' }
});
const value = await env.MY_KV.get('key');
const json = await env.MY_KV.get('key', 'json');
const buffer = await env.MY_KV.get('key', 'arrayBuffer');
const stream = await env.MY_KV.get('key', 'stream');
const { value, metadata } = await env.MY_KV.getWithMetadata('key');
await env.MY_KV.delete('key');
const list = await env.MY_KV.list({ prefix: 'user:' });
return new Response(value);
}
};
R2 Bucket
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"
export default {
async fetch(request: Request, env: Env): Promise<Response> {
await env.MY_BUCKET.put('file.txt', 'content', {
httpMetadata: {
contentType: 'text/plain',
contentLanguage: 'en-US',
cacheControl: 'public, max-age=3600'
},
customMetadata: {
uploadedBy: 'user123'
}
});
const object = await 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',
'ETag': object.etag
}
});
await env.MY_BUCKET.delete('file.txt');
const listed = await env.MY_BUCKET.list({ prefix: 'uploads/' });
const multipart = await env.MY_BUCKET.createMultipartUpload('large-file.bin');
const part1 = await multipart.uploadPart(1, chunk1);
const part2 = await multipart.uploadPart(2, chunk2);
await multipart.complete([part1, part2]);
}
};
D1 Database
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-database-id"
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const result = await env.DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(userId).first();
const info = await env.DB.prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
).bind('Alice', 'alice@example.com').run();
const results = await env.DB.batch([
env.DB.prepare('UPDATE accounts SET balance = balance - 100 WHERE id = ?').bind(1),
env.DB.prepare('UPDATE accounts SET balance = balance + 100 WHERE id = ?').bind(2)
]);
const { results: rows } = await env.DB.prepare(
'SELECT * FROM users'
).all();
return new Response(JSON.stringify(result));
}
};
Durable Objects
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"
script_name = "my-worker"
export class Counter {
state: DurableObjectState;
constructor(state: DurableObjectState, env: Env) {
this.state = state;
}
async fetch(request: Request): Promise<Response> {
let count = (await this.state.storage.get<number>('count')) || 0;
count++;
await this.state.storage.put('count', count);
return new Response(JSON.stringify({ count }));
}
async alarm(): Promise<void> {
await this.state.storage.deleteAll();
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const id = env.COUNTER.idFromName('global');
const counter = env.COUNTER.get(id);
return counter.fetch(request);
}
};
Queues
[[queues.producers]]
binding = "MY_QUEUE"
queue = "my-queue"
[[queues.consumers]]
queue = "my-queue"
max_batch_size = 10
max_batch_timeout = 30
export default {
async fetch(request: Request, env: Env): Promise<Response> {
await env.MY_QUEUE.send({
type: 'email',
to: 'user@example.com'
});
await env.MY_QUEUE.sendBatch([
{ body: { message: 1 } },
{ body: { message: 2 } }
]);
return new Response('Queued');
}
};
export default {
async queue(batch: MessageBatch, env: Env, ctx: ExecutionContext): Promise<void> {
for (const message of batch.messages) {
try {
await processMessage(message.body);
message.ack();
} catch (error) {
message.retry();
}
}
}
};
Workers AI
[ai]
binding = "AI"
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{ role: 'user', content: 'What is edge computing?' }
]
});
const imageResponse = await env.AI.run('@cf/stabilityai/stable-diffusion-xl-base-1.0', {
prompt: 'A sunset over mountains'
});
const embeddings = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: ['Hello world', 'Cloudflare Workers']
});
const audio = await request.arrayBuffer();
const transcription = await env.AI.run('@cf/openai/whisper', {
audio: [...new Uint8Array(audio)]
});
return new Response(JSON.stringify(response));
}
};
Vectorize (Vector Database)
[[vectorize]]
binding = "VECTORIZE_INDEX"
index_name = "my-index"
export default {
async fetch(request: Request, env: Env): Promise<Response> {
await env.VECTORIZE_INDEX.insert([
{
id: '1',
values: [0.1, 0.2, 0.3],
metadata: { text: 'Hello world' }
}
]);
const results = await env.VECTORIZE_INDEX.query(
[0.1, 0.2, 0.3],
{ topK: 5 }
);
return new Response(JSON.stringify(results));
}
};
Development Patterns
Routing
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
switch (url.pathname) {
case '/':
return new Response('Home');
case '/about':
return new Response('About');
default:
return new Response('Not Found', { status: 404 });
}
}
};
Using Hono framework:
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Home'));
app.get('/api/users/:id', async (c) => {
const id = c.req.param('id');
const user = await getUser(id);
return c.json(user);
});
export default app;
Middleware Pattern
async function auth(request: Request, env: Env): Promise<Response | null> {
const token = request.headers.get('Authorization');
if (!token) {
return new Response('Unauthorized', { status: 401 });
}
return null;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const authResponse = await auth(request, env);
if (authResponse) return authResponse;
return new Response('Protected content');
}
};
Error Handling
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
try {
const response = await processRequest(request, env);
return response;
} catch (error) {
console.error('Error:', error);
ctx.waitUntil(
fetch('https://logging.example.com/error', {
method: 'POST',
body: JSON.stringify({
error: error.message,
stack: error.stack,
url: request.url
})
})
);
return new Response('Internal Server Error', { status: 500 });
}
}
};
CORS
function corsHeaders(origin: string) {
return {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
};
}
export default {
async fetch(request: Request): Promise<Response> {
const origin = request.headers.get('Origin') || '*';
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: corsHeaders(origin)
});
}
const response = await handleRequest(request);
const headers = new Headers(response.headers);
Object.entries(corsHeaders(origin)).forEach(([key, value]) => {
headers.set(key, value);
});
return new Response(response.body, {
status: response.status,
headers
});
}
};
Rate Limiting
async function rateLimit(ip: string, env: Env): Promise<boolean> {
const key = `ratelimit:${ip}`;
const limit = 100;
const window = 60;
const current = await env.MY_KV.get(key);
const count = current ? parseInt(current) : 0;
if (count >= limit) {
return false;
}
await env.MY_KV.put(key, (count + 1).toString(), {
expirationTtl: window
});
return true;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
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');
}
};
Testing
Local Testing with Wrangler
wrangler dev
curl http://localhost:8787
wrangler dev --remote
Unit Testing with Vitest
npm install -D vitest @cloudflare/vitest-pool-workers
vitest.config.ts:
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.toml' },
},
},
},
});
Test file:
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';
import worker from '../src/index';
describe('Worker', () => {
it('responds with Hello World', async () => {
const request = new Request('http://example.com');
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx);
expect(await response.text()).toBe('Hello World!');
});
});
Integration Testing
import { unstable_dev } from 'wrangler';
describe('Worker integration tests', () => {
let worker;
beforeAll(async () => {
worker = await unstable_dev('src/index.ts', {
experimental: { disableExperimentalWarning: true }
});
});
afterAll(async () => {
await worker.stop();
});
it('should return 200', async () => {
const resp = await worker.fetch();
expect(resp.status).toBe(200);
});
});
Deployment
Basic Deployment
wrangler deploy
wrangler deploy --env staging
wrangler deploy --dry-run
Environments
name = "my-worker"
[env.staging]
name = "my-worker-staging"
routes = [
{ pattern = "staging.example.com/*", zone_name = "example.com" }
]
[env.production]
name = "my-worker-production"
routes = [
{ pattern = "api.example.com/*", zone_name = "example.com" }
]
wrangler deploy --env staging
wrangler deploy --env production
CI/CD with GitHub Actions
name: Deploy Worker
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm install
- run: npm test
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Gradual Deployments
wrangler versions deploy --percentage 10
wrangler versions deploy --percentage 100
wrangler rollback
Static Assets
Configuration
name = "my-worker"
main = "src/index.ts"
[assets]
directory = "./public"
binding = "ASSETS"
Serving Static Assets
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const asset = await env.ASSETS.fetch(request);
if (asset.status === 200) {
return asset;
}
return new Response('Not Found', { status: 404 });
}
};
Single Page Application (SPA)
[assets]
directory = "./dist"
binding = "ASSETS"
html_handling = "force-https"
not_found_handling = "single-page-application"
Observability
Logging
export default {
async fetch(request: Request): Promise<Response> {
console.log('Request URL:', request.url);
console.error('Error occurred');
console.warn('Warning message');
console.log(JSON.stringify({
level: 'info',
message: 'Request processed',
url: request.url,
timestamp: new Date().toISOString()
}));
return new Response('OK');
}
};
Real-time Logs
wrangler tail
wrangler tail --status error
wrangler tail --method POST
wrangler tail --sampling-rate 0.5
Analytics
export default {
async fetch(request: Request, env: Env): Promise<Response> {
env.ANALYTICS.writeDataPoint({
blobs: ['example'],
doubles: [123.45],
indexes: ['index_value']
});
return new Response('Logged');
}
};
Error Tracking (Sentry)
import * as Sentry from '@sentry/cloudflare';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
Sentry.init({
dsn: env.SENTRY_DSN,
environment: env.ENVIRONMENT
});
try {
return await handleRequest(request);
} catch (error) {
Sentry.captureException(error);
throw error;
}
}
};
Performance Optimization
Best Practices
- Minimize Bundle Size: Keep Workers under 1MB
- Use Bindings: Direct bindings are faster than fetch
- Cache Aggressively: Use Cache API and KV
- Stream Large Responses: Use streams instead of buffering
- Batch Operations: Combine D1 queries with batch()
- waitUntil for Background Tasks: Don't block response
- Avoid Synchronous I/O: All operations should be async
Code Splitting
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/heavy') {
const { processHeavy } = await import('./heavy');
return processHeavy(request);
}
return new Response('OK');
}
};
Caching Strategy
const CACHE_TTL = 3600;
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const cache = caches.default;
const cacheKey = new Request(request.url);
let response = await cache.match(cacheKey);
if (response) return response;
const kvCached = await env.MY_KV.get(request.url);
if (kvCached) {
response = new Response(kvCached);
ctx.waitUntil(cache.put(cacheKey, response.clone()));
return response;
}
response = await fetch(request);
ctx.waitUntil(Promise.all([
cache.put(cacheKey, response.clone()),
env.MY_KV.put(request.url, await response.clone().text(), {
expirationTtl: CACHE_TTL
})
]));
return response;
}
};
Common Patterns
API Gateway
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
const app = new Hono();
app.use('*', cors());
app.use('*', logger());
app.get('/api/users', async (c) => {
const users = await c.env.DB.prepare('SELECT * FROM users').all();
return c.json(users.results);
});
app.get('/api/users/:id', async (c) => {
const id = c.req.param('id');
const user = await c.env.DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(id).first();
if (!user) {
return c.json({ error: 'Not found' }, 404);
}
return c.json(user);
});
app.post('/api/users', async (c) => {
const body = await c.req.json();
const result = await c.env.DB.prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
).bind(body.name, body.email).run();
return c.json({ id: result.meta.last_row_id }, 201);
});
export default app;
Authentication
import { sign, verify } from 'hono/jwt';
async function authenticate(request: Request, env: Env): Promise<any> {
const authHeader = request.headers.get('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
throw new Error('Missing token');
}
const token = authHeader.substring(7);
const payload = await verify(token, env.JWT_SECRET);
return payload;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
try {
const user = await authenticate(request, env);
return new Response(`Hello ${user.name}`);
} catch (error) {
return new Response('Unauthorized', { status: 401 });
}
}
};
Image Proxy
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
const imageUrl = url.searchParams.get('url');
if (!imageUrl) {
return new Response('Missing url parameter', { status: 400 });
}
const response = await fetch(imageUrl);
return new Response(response.body, {
headers: {
'Content-Type': response.headers.get('Content-Type') || 'image/jpeg',
'Cache-Control': 'public, max-age=86400',
'CDN-Cache-Control': 'public, max-age=31536000'
}
});
}
};
Webhook Handler
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const signature = request.headers.get('X-Hub-Signature-256');
const body = await request.text();
const valid = await verifySignature(body, signature, env.WEBHOOK_SECRET);
if (!valid) {
return new Response('Invalid signature', { status: 401 });
}
ctx.waitUntil(
env.WEBHOOK_QUEUE.send(JSON.parse(body))
);
return new Response('OK');
}
};
Troubleshooting
Common Issues
CPU Time Exceeded:
- Optimize expensive operations
- Use streams for large data
- Move processing to Durable Objects
- Consider splitting work across multiple requests
Memory Exceeded:
- Stream large responses instead of buffering
- Clear unused variables
- Use chunked processing
Script Size Too Large:
- Remove unused dependencies
- Use code splitting
- Minify production builds
- Check
wrangler deploy --dry-run --outdir=dist output
Binding Not Found:
- Check wrangler.toml configuration
- Ensure binding name matches env property
- Redeploy after configuration changes
CORS Errors:
- Add proper CORS headers
- Handle OPTIONS requests
- Check allowed origins
Debugging
wrangler dev --local
wrangler dev --remote
wrangler deploy --dry-run --outdir=dist
wrangler tail --format pretty
Resources
Implementation Checklist
Initial Setup
Development
Testing
Deployment
Production