Edge computing brings computation closer to users—running code at CDN edge locations worldwide rather than centralized data centers. This fundamental shift enables new performance and personalization capabilities for web applications.
What Is Edge Computing?#
Traditional Architecture#
User (Tokyo) → CDN (static) → Origin Server (US-East) → Response
└── 200ms latency
Edge Architecture#
User (Tokyo) → Edge Function (Tokyo) → Response
└── 20ms latency
Edge functions run at hundreds of locations globally, processing requests milliseconds from users.
Edge Platforms#
Major Providers#
Cloudflare Workers
- V8 isolates
- 0ms cold start
- 200+ locations
Vercel Edge Functions
- Built on Cloudflare
- Next.js integration
- Edge middleware
AWS Lambda@Edge / CloudFront Functions
- CloudFront integration
- Regional edge caches
- Origin request/response
Deno Deploy
- Deno runtime
- TypeScript native
- Global deployment
Edge Function Basics#
Cloudflare Workers#
1export default {
2 async fetch(request, env, ctx) {
3 const url = new URL(request.url);
4
5 // Personalization based on location
6 const country = request.cf?.country || 'US';
7 const greeting = getGreeting(country);
8
9 // Return response from edge
10 return new Response(JSON.stringify({
11 greeting,
12 location: request.cf?.city,
13 timestamp: new Date().toISOString(),
14 }), {
15 headers: { 'Content-Type': 'application/json' },
16 });
17 },
18};
19
20function getGreeting(country) {
21 const greetings = {
22 US: 'Hello',
23 ES: 'Hola',
24 FR: 'Bonjour',
25 JP: 'こんにちは',
26 };
27 return greetings[country] || 'Hello';
28}Vercel Edge Functions#
1// app/api/greeting/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4export const runtime = 'edge';
5
6export async function GET(request: NextRequest) {
7 const country = request.geo?.country || 'US';
8 const city = request.geo?.city || 'Unknown';
9
10 return NextResponse.json({
11 greeting: `Hello from ${city}, ${country}!`,
12 timestamp: Date.now(),
13 });
14}Common Use Cases#
Geolocation and Personalization#
1export default {
2 async fetch(request) {
3 const { country, city, timezone } = request.cf || {};
4
5 // Currency based on country
6 const currency = getCurrency(country);
7
8 // Language preferences
9 const language = request.headers.get('Accept-Language')?.split(',')[0] || 'en';
10
11 // Modify HTML response
12 const response = await fetch(request);
13 const html = await response.text();
14
15 const personalizedHtml = html
16 .replace('{{CURRENCY}}', currency)
17 .replace('{{LANG}}', language)
18 .replace('{{CITY}}', city);
19
20 return new Response(personalizedHtml, {
21 headers: response.headers,
22 });
23 },
24};A/B Testing#
1export default {
2 async fetch(request) {
3 const url = new URL(request.url);
4
5 // Get or create experiment assignment
6 const cookie = request.headers.get('Cookie') || '';
7 let variant = getCookie(cookie, 'ab_variant');
8
9 if (!variant) {
10 variant = Math.random() < 0.5 ? 'control' : 'treatment';
11 }
12
13 // Modify request to appropriate backend
14 const backendUrl = variant === 'treatment'
15 ? 'https://new-experience.example.com'
16 : 'https://current.example.com';
17
18 const response = await fetch(backendUrl + url.pathname);
19
20 // Set cookie for consistency
21 const newResponse = new Response(response.body, response);
22 newResponse.headers.set('Set-Cookie',
23 `ab_variant=${variant}; Path=/; Max-Age=86400`);
24
25 return newResponse;
26 },
27};Authentication at the Edge#
1import { verify } from '@tsndr/cloudflare-worker-jwt';
2
3export default {
4 async fetch(request, env) {
5 const authHeader = request.headers.get('Authorization');
6
7 if (!authHeader?.startsWith('Bearer ')) {
8 return new Response('Unauthorized', { status: 401 });
9 }
10
11 const token = authHeader.slice(7);
12
13 try {
14 const isValid = await verify(token, env.JWT_SECRET);
15 if (!isValid) {
16 return new Response('Invalid token', { status: 401 });
17 }
18 } catch {
19 return new Response('Invalid token', { status: 401 });
20 }
21
22 // Token valid, forward to origin
23 return fetch(request);
24 },
25};Rate Limiting#
1export default {
2 async fetch(request, env) {
3 const ip = request.headers.get('CF-Connecting-IP');
4 const key = `ratelimit:${ip}`;
5
6 // Use Cloudflare KV or Durable Objects
7 const count = parseInt(await env.RATE_LIMITS.get(key)) || 0;
8
9 if (count >= 100) {
10 return new Response('Rate limit exceeded', {
11 status: 429,
12 headers: { 'Retry-After': '60' },
13 });
14 }
15
16 await env.RATE_LIMITS.put(key, String(count + 1), {
17 expirationTtl: 60,
18 });
19
20 return fetch(request);
21 },
22};Edge Middleware#
Next.js Middleware#
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6 // Get country from geo
7 const country = request.geo?.country || 'US';
8
9 // Redirect to localized version
10 if (country !== 'US' && !request.nextUrl.pathname.startsWith(`/${country.toLowerCase()}`)) {
11 return NextResponse.redirect(
12 new URL(`/${country.toLowerCase()}${request.nextUrl.pathname}`, request.url)
13 );
14 }
15
16 // Add headers
17 const response = NextResponse.next();
18 response.headers.set('X-Country', country);
19
20 return response;
21}
22
23export const config = {
24 matcher: ['/((?!api|_next/static|favicon.ico).*)'],
25};Data at the Edge#
Key-Value Storage#
1// Cloudflare KV
2export default {
3 async fetch(request, env) {
4 // Read
5 const value = await env.MY_KV.get('key');
6
7 // Write
8 await env.MY_KV.put('key', 'value', {
9 expirationTtl: 3600,
10 });
11
12 // Read with metadata
13 const { value, metadata } = await env.MY_KV.getWithMetadata('key');
14
15 return new Response(value);
16 },
17};Durable Objects (Stateful Edge)#
1// Durable Object for stateful operations
2export class Counter {
3 constructor(state, env) {
4 this.state = state;
5 }
6
7 async fetch(request) {
8 let value = await this.state.storage.get('count') || 0;
9 value++;
10 await this.state.storage.put('count', value);
11 return new Response(String(value));
12 }
13}
14
15// Worker that uses the Durable Object
16export default {
17 async fetch(request, env) {
18 const id = env.COUNTER.idFromName('global');
19 const counter = env.COUNTER.get(id);
20 return counter.fetch(request);
21 },
22};Limitations and Trade-offs#
Edge Constraints#
Limited capabilities:
- No filesystem access
- Limited CPU time (50ms - 30s depending on plan)
- Memory limits (128MB - 512MB)
- No persistent connections to databases
- Limited Node.js API compatibility
Solutions:
- Use edge-compatible libraries
- Cache database results in KV
- Use edge-native databases (PlanetScale, Turso)
- Fallback to regional functions for complex operations
When NOT to Use Edge#
Keep at origin:
- Complex database queries
- Long-running operations
- Heavy computation
- Large file processing
- Operations requiring full Node.js API
Use edge for:
- Routing and redirects
- Authentication checks
- Personalization
- A/B testing
- Static asset optimization
- Simple API responses
Hybrid Architecture#
┌─────────────────────────────────────────────┐
│ User │
└─────────────────────┬───────────────────────┘
│
┌─────────────────────▼───────────────────────┐
│ Edge Function │
│ - Auth check │
│ - Geo-personalization │
│ - Route decision │
└─────────────┬───────────────┬───────────────┘
│ │
┌─────────▼─────────┐ ┌───▼────────────────┐
│ Static Assets │ │ Origin Server │
│ (CDN Cache) │ │ (Complex Logic) │
└──────────────────┘ └────────────────────┘
Deployment#
Cloudflare Workers#
1# Install Wrangler CLI
2npm install -g wrangler
3
4# Login
5wrangler login
6
7# Create project
8wrangler init my-worker
9
10# Deploy
11wrangler publishVercel Edge#
# Deploy with Vercel
vercel
# Edge functions in app/api/*/route.ts
# Add: export const runtime = 'edge';Conclusion#
Edge computing fundamentally changes web architecture—moving computation from centralized origins to distributed edge locations milliseconds from users. This enables personalization, security, and performance improvements impossible with traditional architectures.
Start with simple use cases: authentication, redirects, or personalization. As you gain experience, expand to more complex edge logic. The future of web development is increasingly at the edge.