Edge Functions on Vercel: When and Why to Use Them
Published
11 min read
Apr 2, 2025

DevOps

Edge Functions on Vercel: When and Why to Use Them

VercelEdgeDevOpsPerformance

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.

VercelEdgeDevOpsPerformance
Luca Ferretti

Luca Ferretti

Author
11 min read
Reading Time

Edge Functions on Vercel: When and Why to Use Them

The Hype vs. The Reality

"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 mapVercel edge network map

What Edge Functions Actually Do

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)

The Performance Numbers (Real Tests)

Serverless function (us-east-1)

  • Tokyo latency: 180ms
  • Sydney latency: 220ms
  • London latency: 85ms

Edge function (closest region)

  • Tokyo latency: 12ms
  • Sydney latency: 20ms
  • London latency: 8ms

The difference is dramatic — but only if your function does very little work.

Latency comparison chartLatency comparison chart

When to Reach for Edge Functions

✅ Good Use Cases

1. Authentication Checks

typescript
// 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

typescript
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

typescript
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 visualizationA/B testing visualization

4. URL Rewrites (Canonical Domains)

typescript
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()
}

When to Avoid Edge Functions

❌ Bad Use Cases

1. Heavy Computation

Edge functions have strict limits:

  • 1MB response size
  • 4MB request size
  • 25MB memory limit
  • 10-30 second timeout (much lower than serverless)

2. Database Access

Most database drivers aren't edge-compatible. You'd need:

  • HTTP-based DB (like Supabase REST)
  • Edge-compatible ORM (Prisma Edge requires specific setup)
  • Manual connection pooling (not trivial)
typescript
// 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

typescript
// 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.

The Hybrid Approach That Actually Works

Use edge for filtering + serverless for processing:

typescript
// 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)
}

Edge-Native Patterns

Edge Cache with Custom TTL

typescript
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
}

Conditional Response Based on Headers

typescript
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()
}

The Migration Path

Step 1: Add the Edge Config

javascript
// next.config.js
module.exports = {
  experimental: {
    runtime: 'experimental-edge' // Next.js 12
  },
  // Or in Next.js 13+:
  // runtime: 'edge' // in specific route files
}

Step 2: Create Your First Edge Route

typescript
// 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' }
  })
}

Step 3: Test Locally

bash
vercel dev
curl http://localhost:3000/api/hello

The Bottom Line

Edge functions are transformative for specific use cases:

  • Authentication middleware
  • Geolocation routing
  • A/B testing
  • Request/response transformation

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.

Luca Ferretti

Written by

Luca Ferretti

Let's work together

Start your project.