import ComparisonTable from ’../../components/ComparisonTable.astro’;
Edge functions run your code in data centers geographically close to users — minimizing latency without managing servers. Cloudflare Workers and Vercel Edge Functions are the two dominant platforms, but they serve different primary use cases.
Quick Verdict
Choose Cloudflare Workers if: You’re building standalone edge APIs, need the global network reach, want full-featured edge storage (KV, R2, D1), or have compute-intensive edge workloads.
Choose Vercel Edge Functions if: Your primary deployment is a Next.js application and you want middleware, A/B testing, or personalization with minimal configuration.
Feature Comparison
<ComparisonTable headers={[“Feature”, “Cloudflare Workers”, “Vercel Edge Functions”]} rows={[ [“Runtime”, “V8 isolates (Workers runtime)”, “V8 isolates (Edge Runtime)”], [“Global PoPs”, “300+”, “~100”], [“Cold start”, “~0ms (isolates)”, “~0ms (isolates)”], [“CPU time limit”, “50ms (free), 30s (paid)”, “1s”], [“Memory limit”, “128MB”, “128MB”], [“Request size”, “100MB body”, “4MB body”], [“Free tier”, “100K requests/day”, “500K requests/month (shared)”], [“Storage”, “KV, R2, D1, Durable Objects”, “Vercel KV (Redis), Blob”], [“WebSockets”, “Durable Objects”, “Limited”], [“Cron jobs”, “Workers Cron Triggers”, “Not native”], [“Framework integration”, “Framework-agnostic”, “Deep Next.js integration”], ]} />
Runtime Environment
Both use V8 isolates — the same engine as Chrome — instead of Node.js containers. This means:
- Near-zero cold starts (microseconds vs. 100ms+ for Lambda)
- No Node.js APIs (no
fs, limitedcrypto, no native modules) - Web standard APIs (fetch, URL, Response, crypto.subtle)
What you can use:
// Both Cloudflare Workers and Vercel Edge Functions support:
// Web Fetch API, URL, Headers, Request, Response
// TextEncoder/TextDecoder
// crypto.subtle (Web Crypto)
// Streams API
// AbortController
// What you CANNOT use:
// fs (no file system)
// child_process
// Most npm packages that require Node.js internals
// Anything that needs a persistent TCP connection
Cloudflare Workers
Basic Worker:
// worker.ts
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/api/hello') {
return Response.json({
message: 'Hello from the edge!',
location: request.cf?.city ?? 'unknown',
country: request.cf?.country ?? 'unknown',
});
}
return new Response('Not Found', { status: 404 });
},
} satisfies ExportedHandler<Env>;
interface Env {
MY_KV: KVNamespace;
MY_DB: D1Database;
}
Cloudflare KV — distributed key-value storage:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const cache_key = new URL(request.url).pathname;
// Try cache first
const cached = await env.MY_KV.get(cache_key, 'json');
if (cached) {
return Response.json(cached, {
headers: { 'X-Cache': 'HIT' }
});
}
// Fetch from origin
const data = await fetchFromDatabase(env);
// Cache for 5 minutes
await env.MY_KV.put(cache_key, JSON.stringify(data), {
expirationTtl: 300
});
return Response.json(data, {
headers: { 'X-Cache': 'MISS' }
});
}
}
Cloudflare D1 — SQLite at the edge:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method === 'GET') {
const { results } = await env.DB.prepare(
'SELECT * FROM products WHERE active = 1 LIMIT 20'
).all();
return Response.json(results);
}
if (request.method === 'POST') {
const body = await request.json<{ name: string; price: number }>();
await env.DB.prepare(
'INSERT INTO products (name, price) VALUES (?, ?)'
).bind(body.name, body.price).run();
return Response.json({ success: true }, { status: 201 });
}
return new Response('Method Not Allowed', { status: 405 });
}
}
Durable Objects — stateful edge (unique to Cloudflare):
// Persistent state, consistent globally — no other edge platform has this
export class Counter {
state: DurableObjectState;
constructor(state: DurableObjectState) {
this.state = state;
}
async fetch(request: Request): Promise<Response> {
let value = await this.state.storage.get<number>('value') ?? 0;
if (request.method === 'POST') {
value++;
await this.state.storage.put('value', value);
}
return Response.json({ value });
}
}
Use cases for Durable Objects: real-time collaboration, rate limiting with precision, game state, live counters.
Vercel Edge Functions
Next.js Middleware (most common use case):
// middleware.ts — runs on every request before routing
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// A/B testing — split traffic
const bucket = Math.random() > 0.5 ? 'a' : 'b';
const response = NextResponse.next();
response.cookies.set('ab-bucket', bucket);
// Geolocation-based redirect
const country = request.geo?.country;
if (country === 'DE' && !pathname.startsWith('/de')) {
return NextResponse.redirect(new URL(`/de${pathname}`, request.url));
}
// Auth check
const token = request.cookies.get('auth-token');
if (pathname.startsWith('/dashboard') && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
Edge API Route in Next.js:
// app/api/fast/route.ts
import { NextRequest } from 'next/server';
export const runtime = 'edge'; // Declare edge runtime
export async function GET(request: NextRequest) {
const geo = {
city: request.geo?.city ?? 'unknown',
country: request.geo?.country ?? 'unknown',
region: request.geo?.region ?? 'unknown',
};
return Response.json({
message: 'Edge response',
location: geo,
timestamp: Date.now(),
});
}
Vercel KV (Redis at the edge):
import { kv } from '@vercel/kv';
export const runtime = 'edge';
export async function GET(request: Request) {
const key = 'visitor-count';
const count = await kv.incr(key);
return Response.json({ visitors: count });
}
Pricing Comparison
| Metric | Cloudflare Workers Free | Cloudflare Workers Paid | Vercel Pro |
|---|---|---|---|
| Requests | 100K/day | $0.30/million | 500K/month (shared) |
| CPU time | 10ms/request | 30s limit | 1s limit |
| KV reads | 100K/day | $0.50/million | $0.30/million |
| KV writes | 1K/day | $5/million | $2/million |
| Monthly base | Free | $5/month | $20/month |
For API-heavy workloads (millions of requests), Cloudflare is significantly cheaper.
Performance at the Edge
Request latency by location (typical):
| User Location | Cloudflare Workers | Vercel Edge |
|---|---|---|
| New York | 3ms | 5ms |
| London | 4ms | 6ms |
| Tokyo | 5ms | 8ms |
| São Paulo | 6ms | 12ms |
| Mumbai | 5ms | 15ms |
| Sydney | 5ms | 10ms |
Cloudflare’s 300+ PoPs vs Vercel’s ~100 means better coverage in emerging markets and regions where Vercel has fewer data centers.
When to Choose Each
Choose Cloudflare Workers:
- Building standalone edge APIs (not tied to Next.js)
- Need D1, KV, R2 at the edge (persistent state)
- Global audience — especially Asia, Latin America, Africa
- Real-time applications (WebSockets via Durable Objects)
- High-volume compute (Cloudflare’s pricing is better at scale)
- Cron jobs and scheduled tasks
Choose Vercel Edge Functions:
- Your app is Next.js-first and deployed on Vercel
- Need middleware for auth, redirects, or feature flags
- Team is already on Vercel platform
- A/B testing and personalization for Next.js routes
- Don’t need the full storage/compute platform
Bottom Line
Cloudflare Workers is the more capable edge computing platform — more PoPs, longer CPU time limits, complete storage ecosystem, and better economics at scale. Vercel Edge Functions wins when your primary concern is seamless Next.js integration with minimal configuration. For standalone edge APIs or new edge-native projects, Cloudflare Workers is the clear choice.