Introduction
Edge computing brings your code closer to users, dramatically reducing latency. For startups, edge functions offer the power of serverless with better performance. This guide compares the two leading options: Cloudflare Workers and Vercel Edge Functions.
Why Edge Computing Matters
The Problem with Traditional Serverless
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Traditional vs Edge โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Traditional Serverless (Lambda): โ
โ โ
โ User โ Internet โ US-East-1 Lambda โ Response โ
โ โ โ
โ โโ Latency: 50-200ms for distant users โ
โ โ
โ Edge Computing: โ
โ โ
โ User โ Nearest Edge Server โ Response โ
โ โ โ
โ โโ Latency: 5-20ms worldwide โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
What Can Run on Edge
# Edge function use cases
use_cases:
- "API proxying and request transformation"
- "A/B testing"
- "Geolocation-based content"
- "Authentication verification"
- "Rate limiting"
- "Request validation"
- "Static site enhancement"
- "Real-time personalization"
Cloudflare Workers
Getting Started
# Install Wrangler (Cloudflare CLI)
npm install -g wrangler
# Create a new worker
wrangler generate my-worker
# Development
wrangler dev
# Deploy
wrangler deploy
Worker Example
// worker.js
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url)
// Route handling
if (url.pathname === '/api/users') {
return handleUsers(request)
}
if (url.pathname.startsWith('/api/')) {
return handleApiProxy(request)
}
// Default: fetch from origin
return fetch(request)
}
}
async function handleUsers(request) {
// Get users from database
const users = await getUsersFromD1()
return new Response(JSON.stringify(users), {
headers: { 'Content-Type': 'application/json' }
})
}
D1 Database (SQLite at Edge)
// Using D1 (edge database)
export default {
async fetch(request, env, ctx) {
const { pathname } = new URL(request.url)
if (pathname === '/users') {
// Query D1 database
const { results } = await env.DB.prepare(
'SELECT * FROM users LIMIT 10'
).all()
return new Response(JSON.stringify(results))
}
return new Response('Not found', { status: 404 })
}
}
# wrangler.toml
name = "my-app"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "abc-123"
KV for Fast Storage
// Workers KV (key-value store)
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url)
// Read from KV
if (url.pathname === '/config') {
const config = await env.KV.get('app_config')
return new Response(config)
}
// Write to KV
if (url.pathname === '/set' && request.method === 'POST') {
const body = await request.json()
await env.KV.put(body.key, body.value)
return new Response('OK')
}
return new Response('Not found', { status: 404 })
}
}
Vercel Edge Functions
Getting Started
// app/api/hello/route.ts
export const runtime = 'edge'
export default function handler(request: Request) {
return new Response(
JSON.stringify({
message: 'Hello from Edge!',
location: request.headers.get('x-vercel-id') || 'unknown',
}),
{
headers: { 'Content-Type': 'application/json' },
}
)
}
Edge Runtime Features
// Using edge-specific APIs
export const runtime = 'edge'
export default async function handler(request: Request) {
// Geolocation data
const country = request.geo?.country || 'US'
const city = request.geo?.city || 'Unknown'
// Personalize content based on location
const content = {
us: { greeting: 'Hello!', currency: 'USD' },
uk: { greeting: 'Hello!', currency: 'GBP' },
jp: { greeting: 'Konnichiwa!', currency: 'JPY' },
}[country] || { greeting: 'Hello!', currency: 'USD' }
return new Response(JSON.stringify({
...content,
location: { country, city }
}), {
headers: { 'Content-Type': 'application/json' }
})
}
Edge Config
// Instant config updates without deployment
import { EdgeConfig } from '@vercel/edge-config'
export const runtime = 'edge'
export default async function handler(request: Request) {
// Connect to Edge Config
const config = await EdgeConfig.connect(process.env.EDGE_CONFIG)
// Get values (instantly updated)
const featureFlags = await config.get('featureFlags')
const abTest = await config.get('abTest')
// Use config values
const showNewFeature = featureFlags?.newDashboard
return new Response(JSON.stringify({ showNewFeature, abTest }), {
headers: { 'Content-Type': 'application/json' }
})
}
Performance Comparison
Cold Start Times
# Cold start comparison (2025)
cloudflare_workers:
cold_start: "5-10ms"
min_latency: "5ms"
global_points: "300+"
vercel_edge:
cold_start: "10-20ms"
min_latency: "10ms"
global_points: "250+"
Pricing Comparison
# Monthly pricing (2025)
cloudflare_workers:
free:
requests: "10M/month"
compute: "1M CPU seconds"
bandwidth: "1GB"
paid:
requests: "$5/10M"
compute: "$0.000625/CPU second"
bandwidth: "$0.15/GB"
vercel_edge:
free:
requests: "100K/month"
bandwidth: "1GB"
paid:
requests: "$0.000019/request"
bandwidth: "$0.15/GB"
When to Choose
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Edge Platform Selection โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Choose CLOUDFLARE WORKERS if: โ
โ โข Need D1 (SQLite at edge) โ
โ โข Want R2 for storage (no egress fees) โ
โ โข Need Workers KV for caching โ
โ โข Budget is critical (generous free tier) โ
โ โข Building complex edge applications โ
โ โ
โ Choose VERCEL EDGE if: โ
โ โข Already using Next.js โ
โ โข Want simplest DX โ
โ โข Need Edge Config for feature flags โ
โ โข Deploying to existing Vercel projects โ
โ โข Prefer TypeScript-first experience โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Practical Examples
A/B Testing at Edge
// Cloudflare Workers - A/B Testing
export default {
async fetch(request, env, ctx) {
// Get cookie or create new bucket
let bucket = request.headers.get('x-ab-bucket')
if (!bucket) {
bucket = Math.random() < 0.5 ? 'a' : 'b'
}
// Fetch variant content
const variant = bucket === 'a'
? await fetch('https://example.com/variant-a.html')
: await fetch('https://example.com/variant-b.html')
// Return with cookie
const response = new Response(variant.body, variant)
response.headers.set('Set-Cookie', `ab-bucket=${bucket}; Path=/; Max-Age=2592000`)
return response
}
}
Request Validation
// Vercel Edge - Request Validation
export const runtime = 'edge'
export default async function handler(request: Request) {
// Validate request before processing
const { searchParams } = new URL(request.url)
const apiKey = request.headers.get('x-api-key')
// Verify API key
if (!apiKey || !await validateKey(apiKey)) {
return new Response('Unauthorized', { status: 401 })
}
// Rate limit check
const ip = request.headers.get('x-forwarded-for') || 'unknown'
if (await isRateLimited(ip)) {
return new Response('Too Many Requests', { status: 429 })
}
// Process request
return new Response('OK')
}
Geolocation Redirect
// Cloudflare Workers - Geo Redirect
export default {
async fetch(request, env, ctx) {
const country = request.headers.get('cf-ipcountry')
const url = new URL(request.url)
// Redirect based on country
if (country === 'JP' && !url.pathname.startsWith('/jp')) {
return Response.redirect('https://example.jp' + url.pathname, 302)
}
if (country === 'DE' && !url.pathname.startsWith('/de')) {
return Response.redirect('https://example.de' + url.pathname, 302)
}
return fetch(request)
}
}
Common Pitfalls
1. Not Using Prepared Statements
Wrong:
// SQL injection vulnerable
const query = `SELECT * FROM users WHERE id = ${userId}`
Correct:
// Parameterized query
const { results } = await env.DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(userId).all()
2. Blocking Operations
Wrong:
// Synchronous file read blocks
const data = fs.readFileSync('/path/to/file')
Correct:
// Async operations only
const data = await readFile('/path/to/file')
Key Takeaways
- Edge = Faster - 5-20ms vs 50-200ms latency
- Cloudflare Workers - Best free tier, D1 database, R2 storage
- Vercel Edge - Best if already using Next.js
- Both are free at small scale - Great for startups
- Use for: auth, redirects, A/B testing, personalization
Comments