DevOps
Overview
Edge functions are fast, but they're not magic. A practical guide to deciding when edge runtime actually helps — and when it just adds complexity.
Luca Ferretti
"Edge functions are the future!" they said. And they're not wrong — but they're also not a silver bullet. After deploying edge functions across 20+ production projects, I've learned exactly when they shine and when they're just complexity theater.
Vercel edge network map
Edge functions run on Vercel's global network — not in a single region. When a request hits:
User in Tokyo → Edge Function (Tokyo region) → Response (8ms)
User in London → Edge Function (London region) → Response (12ms)
Without edge: Every request travels to your main region (say, Virginia):
User in Tokyo → Virginia (150ms) → Response (180ms total)
Serverless function (us-east-1)
Edge function (closest region)
The difference is dramatic — but only if your function does very little work.
Latency comparison chart
1. Authentication Checks
// edge function
export const config = { runtime: 'edge' }
export default async function middleware(request: NextRequest) {
const token = request.cookies.get('token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Verify JWT (edge supports Web Crypto API)
const isValid = await verifyJWT(token)
if (!isValid) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
2. Geolocation-based Routing
export default function handler(request: NextRequest) {
const country = request.geo?.country || 'US'
// Redirect to country-specific pricing
if (country === 'CA') {
return NextResponse.redirect('/pricing/ca')
}
if (country === 'GB') {
return NextResponse.redirect('/pricing/uk')
}
return NextResponse.redirect('/pricing/us')
}
3. A/B Testing
export default function handler(request: NextRequest) {
const cookie = request.cookies.get('ab-test')
if (!cookie) {
const variant = Math.random() > 0.5 ? 'A' : 'B'
const response = NextResponse.next()
response.cookies.set('ab-test', variant)
return response
}
// Variant A sees red button, Variant B sees blue
const response = NextResponse.next()
response.headers.set('X-Test-Variant', cookie.value)
return response
}
A/B testing visualization
4. URL Rewrites (Canonical Domains)
export default function handler(request: NextRequest) {
const url = request.nextUrl
const hostname = request.headers.get('host')
// Force www subdomain
if (!hostname?.startsWith('www.')) {
url.host = `www.${hostname}`
return NextResponse.redirect(url, 301)
}
// Localize path
const pathname = url.pathname
const locale = request.geo?.country === 'FR' ? 'fr' : 'en'
if (!pathname.startsWith(`/${locale}`)) {
url.pathname = `/${locale}${pathname}`
return NextResponse.rewrite(url)
}
return NextResponse.next()
}
1. Heavy Computation
Edge functions have strict limits:
2. Database Access
Most database drivers aren't edge-compatible. You'd need:
// This WON'T work in edge functions
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient() // Error: PrismaClient doesn't support edge
// This WILL work (but slower)
const response = await fetch('https://api.supabase.co/rest/v1/users', {
headers: { apikey: process.env.SUPABASE_KEY }
})
3. File System Operations
// WON'T work
import fs from 'fs'
fs.readFileSync('./data.json') // Edge has no file system
// Alternative: Inline data or CDN fetch
const data = await fetch('https://cdn.example.com/data.json')
4. npm Packages with Native Dependencies
Many popular packages assume Node.js APIs that don't exist at the edge.
Use edge for filtering + serverless for processing:
// Edge function: lightweight filtering
export default async function edgeHandler(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const userId = searchParams.get('userId')
if (!userId) {
return new Response('User ID required', { status: 400 })
}
// Pass through to serverless for heavy work
const response = await fetch(`https://your-app.vercel.app/api/process?userId=${userId}`)
return response
}
// Serverless function: heavy processing
export default async function serverlessHandler(request: NextRequest) {
const userId = request.nextUrl.searchParams.get('userId')
// This can use Prisma, heavy computation, etc.
const userData = await prisma.user.findUnique({ where: { id: userId } })
const processedData = await heavyComputation(userData)
return NextResponse.json(processedData)
}
export default function handler(request: NextRequest) {
const response = NextResponse.next()
// Edge cache for 5 minutes
response.headers.set('Cache-Control', 's-maxage=300, stale-while-revalidate')
return response
}
export default function handler(request: NextRequest) {
const userAgent = request.headers.get('user-agent') || ''
if (userAgent.includes('Googlebot')) {
// Return SEO-optimized HTML for crawlers
return new Response(`<html>...</html>`, {
headers: { 'Content-Type': 'text/html' }
})
}
// Return normal response for users
return NextResponse.next()
}
// next.config.js
module.exports = {
experimental: {
runtime: 'experimental-edge' // Next.js 12
},
// Or in Next.js 13+:
// runtime: 'edge' // in specific route files
}
// app/api/hello/route.ts
export const runtime = 'edge'
export async function GET() {
return new Response(JSON.stringify({ message: 'Hello from edge!' }), {
headers: { 'Content-Type': 'application/json' }
})
}
vercel dev
curl http://localhost:3000/api/hello
Edge functions are transformative for specific use cases:
But they're not replacements for serverless functions. The smart architecture uses both:
Edge layer → Lightweight filtering, routing, auth Serverless layer → Database access, heavy computation, file operations
Know which tool to reach for, and you'll build faster apps without fighting edge limitations.
Written by
Luca Ferretti
Let's work together