| name | edge-patterns |
| description | Edge Computing patterns: Cloudflare Workers (Durable Objects, KV, D1, R2, Wrangler), Vercel Edge Middleware (geo-routing, A/B testing, Edge Config), Deno Deploy, edge runtime constraints (no fs/child_process, bundle limits), streaming responses, edge caching (Cache API, stale-while-revalidate), and testing with Miniflare. |
Edge Patterns
Reference for building on edge runtimes — Cloudflare Workers, Vercel Edge, and Deno Deploy.
When to Activate
- Building Cloudflare Workers or Pages Functions
- Writing Vercel Edge Middleware
- Deploying to Deno Deploy
- Debugging "not supported in edge runtime" errors
- Implementing geo-routing, A/B testing, or auth at the edge
- Setting up edge caching strategies
- Migrating a Node.js API route to an edge-compatible runtime to reduce cold-start latency
- Designing stateful edge logic using Durable Objects or Deno KV
- Choosing between Cloudflare, Vercel Edge, and Deno Deploy for a new globally distributed service
Edge Runtime Constraints
Edge runtimes execute in V8 isolates — not Node.js. Key differences:
| Feature | Node.js | Edge Runtime |
|---|
| File system | fs module | ❌ Not available |
| Child processes | child_process | ❌ Not available |
| TCP sockets | net, dgram | ❌ Not available |
| Native modules | node-gyp bindings | ❌ Not available |
| npm packages | All | Only Web-API compatible |
| Bundle size | Unlimited | Cloudflare: 1MB / Vercel: 4MB |
| CPU time | Unlimited | Cloudflare: 50ms / Vercel: 25s wall |
| Cold starts | Seconds | Milliseconds (isolate reuse) |
Available Web Standard APIs:
fetch, Request, Response, Headers
URL, URLSearchParams
crypto.subtle (Web Crypto API)
ReadableStream, WritableStream, TransformStream
TextEncoder, TextDecoder
setTimeout, setInterval (limited in Workers)
Blob, FormData
Cloudflare Workers
Worker Anatomy
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/api/health') {
return Response.json({ status: 'ok' });
}
return new Response('Not Found', { status: 404 });
},
};
interface Env {
MY_KV: KVNamespace;
MY_DB: D1Database;
MY_BUCKET: R2Bucket;
MY_SECRET: string;
}
wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123"
preview_id = "def456"
[[d1_databases]]
binding = "MY_DB"
database_name = "my-db"
database_id = "uuid-here"
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"
[vars]
ENVIRONMENT = "production"
KV (Key-Value Store)
await env.MY_KV.put('user:123', JSON.stringify(user), {
expirationTtl: 3600,
});
const raw = await env.MY_KV.get('user:123');
const user = raw ? JSON.parse(raw) : null;
const user = await env.MY_KV.get<User>('user:123', 'json');
await env.MY_KV.delete('user:123');
const { keys, list_complete } = await env.MY_KV.list({ prefix: 'user:' });
D1 (SQLite at Edge)
const { results } = await env.MY_DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(userId).all<User>();
const { success } = await env.MY_DB.prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
).bind(name, email).run();
const [create, notify] = await env.MY_DB.batch([
env.MY_DB.prepare('INSERT INTO users (name) VALUES (?)').bind(name),
env.MY_DB.prepare('INSERT INTO events (type, user) VALUES (?, ?)').bind('created', name),
]);
R2 (Object Storage)
await env.MY_BUCKET.put(key, request.body, {
httpMetadata: { contentType: 'image/png' },
customMetadata: { uploadedBy: userId },
});
const object = await env.MY_BUCKET.get(key);
if (!object) return new Response('Not Found', { status: 404 });
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream',
'ETag': object.etag,
},
});
Durable Objects (Stateful Edge)
export class SessionRoom {
private state: DurableObjectState;
private sessions: Map<string, WebSocket> = new Map();
constructor(state: DurableObjectState, env: Env) {
this.state = state;
this.state.acceptWebSocket();
}
async fetch(request: Request): Promise<Response> {
const { pathname } = new URL(request.url);
if (pathname === '/websocket') {
const pair = new WebSocketPair();
this.state.acceptWebSocket(pair[1]);
return new Response(null, { status: , : pair[] });
}
count = ( ...<>() ?? ) + ;
...(, count);
.({ count });
}
() {
( session ..()) {
session.(message);
}
}
}
waitUntil — Background Tasks
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const response = await handleRequest(request, env);
ctx.waitUntil(logAnalytics(request, response, env));
ctx.waitUntil(updateCache(request, env));
return response;
},
};
Wrangler Dev Workflow
wrangler dev
wrangler deploy
wrangler deploy --env staging
wrangler tail
wrangler d1 migrations create my-db add-users-table
wrangler d1 migrations apply my-db
wrangler kv key put --binding=MY_KV "key" "value"
wrangler kv key get --binding=MY_KV "key"
Vercel Edge Middleware
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname, searchParams } = request.nextUrl;
const country = request.geo?.country ?? 'US';
if (pathname === '/pricing' && country === 'DE') {
return NextResponse.rewrite(new URL('/pricing/eu', request.url));
}
const bucket = request.cookies.get('ab-bucket')?.value
?? (Math.random() < 0.5 ? 'a' : 'b');
const response = NextResponse.next();
response.cookies.set('ab-bucket', bucket, { maxAge: });
(bucket === ) {
response..(, );
}
response;
}
config = {
: [
,
,
,
],
};
Edge Config (Feature Flags at Edge)
import { get } from '@vercel/edge-config';
export async function middleware(request: NextRequest) {
const maintenanceMode = await get<boolean>('maintenance_mode');
if (maintenanceMode) {
return NextResponse.rewrite(new URL('/maintenance', request.url));
}
return NextResponse.next();
}
vercel env pull .env.local
vercel edge-config update '{"maintenance_mode": true}'
Deno Deploy
Deno.serve(async (req) => {
const url = new URL(req.url);
if (url.pathname === '/api/hello') {
return Response.json({ message: 'Hello from Deno Deploy!' });
}
return new Response('Not Found', { status: 404 });
});
Deno KV
const kv = await Deno.openKv();
await kv.set(['users', userId], { name, email }, { expireIn: 3600_000 });
const entry = await kv.get<User>(['users', userId]);
const user = entry.value;
const result = await kv.atomic()
.check({ key: ['counter'], versionstamp: null })
.set(['counter'], 0)
.commit();
Deploy via GitHub Actions
name: Deploy to Deno Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: denoland/deployctl@v1
with:
project: my-deno-project
entrypoint: main.ts
For streaming responses (TransformStream, Server-Sent Events), edge caching (Cache API, stale-while-revalidate), Miniflare testing, and common mistakes, see skill edge-patterns-advanced.
Reference
serverless-patterns — Cold starts, Step Functions, Lambda Powertools, idempotency
deployment-patterns — Docker, CI/CD, rollback strategies
wasm-patterns — Run Rust WASM in Cloudflare Workers