Rendering
Edge Rendering
Render at the edge for low latency
Intermediate
edge cdn performance serverless
Definition
Edge rendering executes code at CDN edge locations close to users rather than at a central origin server. This dramatically reduces latency (often under 50ms) by minimizing the distance data travels, enabling dynamic personalization without sacrificing performance.
Edge vs Origin
Traditional Architecture
User in Tokyo ───► Origin in US (200ms latency)
User in London ───► Origin in US (150ms latency)
Everyone connects to one central server
Edge Architecture
User in Tokyo ───► Edge in Tokyo (20ms latency)
User in London ───► Edge in London (15ms latency)
User in NYC ───► Edge in NYC (10ms latency)
100+ edge locations worldwide
Edge Runtime
Characteristics
// Edge functions run in V8 isolates (not Node.js)
// - Cold start: 0ms (always warm)
// - Memory: 128MB-1024MB
// - CPU: Limited (not for heavy computation)
// - Execution time: 30s-5min depending on platform
// - Network: Fetch API only
What Works
// ✅ Works on Edge
export const config = {
runtime: 'edge'
};
export default async function handler(req) {
// Fetch data
const data = await fetch('https://api.example.com/data').then(r => r.json());
// Read headers/cookies
const token = req.headers.get('authorization');
const country = req.geo?.country;
// Return HTML or JSON
return new Response(renderHTML(data), {
headers: { 'Content-Type': 'text/html' }
});
}
What Doesn’t Work
// ❌ Not available on Edge
const fs = require('fs'); // No file system
const db = require('./db'); // No direct DB connections
const sharp = require('sharp'); // No native modules
const crypto = require('crypto'); // Use Web Crypto API instead
Use Cases
1. Geolocation Personalization
// middleware.ts (Next.js)
import { NextResponse } from 'next/server';
export function middleware(request) {
const country = request.geo?.country || 'US';
const currency = country === 'UK' ? 'GBP' : 'USD';
// Rewrite to localized version
return NextResponse.rewrite(
new URL(`/${country}${request.nextUrl.pathname}`, request.url)
);
}
2. A/B Testing
export const config = { runtime: 'edge' };
export default function handler(req) {
// Assign variant at edge (no origin hit)
const variant = Math.random() < 0.5 ? 'a' : 'b';
// Set cookie for consistency
const res = NextResponse.next();
res.cookies.set('ab-test', variant);
return res;
}
3. Authentication at Edge
export const config = { runtime: 'edge' };
export default async function handler(req) {
const token = req.cookies.get('token');
// Verify JWT at edge
const isValid = await verifyToken(token);
if (!isValid) {
return NextResponse.redirect(new URL('/login', req.url));
}
// Add user info to headers for upstream
const requestHeaders = new Headers(req.headers);
requestHeaders.set('x-user-id', getUserIdFromToken(token));
return NextResponse.next({
request: { headers: requestHeaders }
});
}
4. Dynamic OG Images
// Edge-generated social images
export const config = { runtime: 'edge' };
export default async function handler(req) {
const url = new URL(req.url);
const title = url.searchParams.get('title') || 'Default Title';
const author = url.searchParams.get('author') || 'Anonymous';
// Generate SVG on edge using string concatenation
const svg = '<svg width="1200" height="630">' +
'<rect fill="#1a1a1a" width="1200" height="630"/>' +
'<text fill="white" x="50" y="200" font-size="64">' + escapeXml(title) + '</text>' +
'<text fill="#888" x="50" y="500" font-size="32">By ' + escapeXml(author) + '</text>' +
'</svg>';
return new Response(svg, {
headers: {
'Content-Type': 'image/svg+xml',
'Cache-Control': 'public, max-age=86400'
}
});
}
function escapeXml(str) {
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
}
Next.js Edge
Edge API Routes
// pages/api/hello.ts
export const config = {
runtime: 'edge'
};
export default async function handler(req: Request) {
return new Response(JSON.stringify({ message: 'Hello from edge!' }), {
headers: { 'content-type': 'application/json' }
});
}
Edge Middleware
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Check auth token
const token = request.cookies.get('token')?.value;
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*']
};
Vercel Edge Functions
// api/hello.js
export default function handler(request) {
return new Response(`Hello from ${request.geo.city}!`);
}
export const config = {
runtime: 'edge'
};
Available APIs
// Request
request.geo // { city, country, region, latitude, longitude }
request.ip // User IP address
request.nextUrl // Parsed URL
// Response
NextResponse.json() // JSON response
NextResponse.redirect() // Redirect
NextResponse.rewrite() // URL rewrite
NextResponse.next() // Continue to origin
Best Practices
1. Keep It Light
// ✅ Good: Quick decision making
export function middleware(req) {
const country = req.geo.country;
if (country === 'CN') {
return NextResponse.rewrite(new URL('/cn-version', req.url));
}
return NextResponse.next();
}
// ❌ Bad: Heavy computation
export async function handler(req) {
// Don't process images or heavy data at edge
const processed = await heavyImageProcessing(req.body);
}
2. Cache Aggressively
export default async function handler(req) {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 60 } // ISR at edge
});
return new Response(data, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300'
}
});
}
3. Fallback to Origin
export function middleware(req) {
try {
// Try edge logic
return doEdgeLogic(req);
} catch (error) {
// Fallback to origin server
return NextResponse.next();
}
}
Limitations
Compute Constraints
- No Node.js built-ins (fs, path, crypto)
- Limited npm packages (no native dependencies)
- No long-running processes
- Shared nothing architecture (no in-memory state)
Database Connections
// ❌ Can't hold persistent connections
const db = new Database(); // Connection per request
// ✅ Use connectionless protocols
// HTTP APIs, REST, GraphQL
// Or use edge databases like PlanetScale, Fauna
Key Takeaway
Edge rendering brings computation closer to users for ultra-low latency. Ideal for personalization, A/B testing, auth checks, and middleware. Keep edge functions lightweight—use them for routing decisions, not heavy processing. Combine with origin servers for complex business logic and database operations.