Next.js

Edge Runtime & Edge Functions

18 min Lesson 27 of 40

Edge Runtime & Edge Functions

Edge computing brings your code closer to users by running it on distributed servers worldwide. Next.js provides Edge Runtime for executing code at the edge, offering lower latency and improved performance compared to traditional server-side execution.

Understanding Edge Runtime

Edge Runtime is a lightweight JavaScript runtime based on Web Standards, designed to run on edge networks like Vercel Edge Network, Cloudflare Workers, or Deno Deploy.

Edge vs Node.js Runtime: Edge Runtime uses V8 isolates (not Node.js processes), starts instantly, has limited APIs, but offers global distribution and millisecond response times.

Key Differences Between Edge and Node.js Runtime

/* Node.js Runtime (Default) */ - Full Node.js API access (fs, path, crypto, etc.) - Longer cold start times (~100-500ms) - Regional deployment (single data center) - No memory/CPU limits (depends on hosting) - Full npm package support /* Edge Runtime */ - Web Standard APIs only (fetch, Response, Headers, etc.) - Near-instant cold starts (~0-10ms) - Global deployment (150+ locations) - Memory limit: 1-4MB per request - Limited npm package support

Enabling Edge Runtime

You can configure Edge Runtime at the route level:

App Router Edge Routes

// app/api/edge/route.ts export const runtime = 'edge'; // Enable Edge Runtime export async function GET(request: Request) { return new Response('Hello from the Edge!', { status: 200, headers: { 'Content-Type': 'text/plain', }, }); }

Page Routes with Edge Runtime

// app/dashboard/page.tsx export const runtime = 'edge'; export default function Dashboard() { return <h1>Dashboard Page (Edge Rendered)</h1>; }

Pages Router Edge API Routes

// pages/api/edge-function.ts export const config = { runtime: 'edge', }; export default async function handler(req: Request) { return new Response(JSON.stringify({ message: 'Edge Function' }), { status: 200, headers: { 'Content-Type': 'application/json', }, }); }

Edge API Routes

Create performant API endpoints that run globally:

Basic Edge API Route

// app/api/geolocation/route.ts export const runtime = 'edge'; export async function GET(request: Request) { // Access request geolocation (Vercel Edge) const geo = request.headers.get('x-vercel-ip-country'); const city = request.headers.get('x-vercel-ip-city'); return Response.json({ country: geo, city: city, timestamp: new Date().toISOString(), }); }

Edge API with Query Parameters

// app/api/weather/route.ts export const runtime = 'edge'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const city = searchParams.get('city') || 'London'; // Fetch from external API const response = await fetch( `https://api.weather.com/v1/current?city=${city}`, { headers: { 'Authorization': `Bearer ${process.env.WEATHER_API_KEY}`, }, } ); const data = await response.json(); return Response.json(data, { headers: { 'Cache-Control': 's-maxage=300, stale-while-revalidate', }, }); }

Edge API with POST Request

// app/api/transform/route.ts export const runtime = 'edge'; export async function POST(request: Request) { try { const body = await request.json(); const { text } = body; // Transform text (example: uppercase) const transformed = text.toUpperCase(); return Response.json({ original: text, transformed, }); } catch (error) { return Response.json( { error: 'Invalid request body' }, { status: 400 } ); } }

Edge Middleware

Middleware runs before every request, perfect for authentication, redirects, and request manipulation:

Basic Middleware Setup

// middleware.ts (root of project) import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // Log all requests console.log('Request:', request.url); return NextResponse.next(); } // Specify paths where middleware runs export const config = { matcher: '/api/:path*', };

Authentication Middleware

// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const token = request.cookies.get('auth-token')?.value; // Protect /dashboard routes if (request.nextUrl.pathname.startsWith('/dashboard')) { if (!token) { return NextResponse.redirect(new URL('/login', request.url)); } } return NextResponse.next(); } export const config = { matcher: ['/dashboard/:path*', '/api/protected/:path*'], };

Geolocation-Based Redirects

// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const country = request.geo?.country || 'US'; const { pathname } = request.nextUrl; // Redirect based on country if (pathname === '/') { if (country === 'GB') { return NextResponse.redirect(new URL('/en-gb', request.url)); } else if (country === 'FR') { return NextResponse.redirect(new URL('/fr', request.url)); } } return NextResponse.next(); }

Adding Custom Headers

// middleware.ts import { NextResponse } from 'next/server'; export function middleware(request: NextRequest) { const response = NextResponse.next(); // Add custom headers response.headers.set('x-custom-header', 'my-value'); response.headers.set('x-request-id', crypto.randomUUID()); return response; } export const config = { matcher: '/:path*', };

A/B Testing Middleware

// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // Get or set variant cookie let variant = request.cookies.get('variant')?.value; if (!variant) { // Randomly assign variant A or B variant = Math.random() > 0.5 ? 'A' : 'B'; } const response = NextResponse.next(); // Set variant cookie response.cookies.set('variant', variant, { maxAge: 60 * 60 * 24 * 30, // 30 days }); // Add header for client-side access response.headers.set('x-variant', variant); return response; } export const config = { matcher: '/', };

Edge Runtime Limitations

Understanding what's not available in Edge Runtime:

Not Available in Edge Runtime: Node.js APIs (fs, path, process), native modules, dynamic code evaluation (eval), some npm packages, long-running computations (>30s timeout), large memory usage (>4MB).

Compatible Web APIs

// Available in Edge Runtime - fetch, Request, Response, Headers - URL, URLSearchParams - TextEncoder, TextDecoder - atob, btoa - crypto (Web Crypto API) - console - setTimeout, setInterval // Example: Using Web Crypto API const hash = await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode('hello') ); const hashArray = Array.from(new Uint8Array(hash)); const hashHex = hashArray .map(b => b.toString(16).padStart(2, '0')) .join('');

Checking Package Compatibility

// Some packages work, some don't import jwt from 'jsonwebtoken'; // ❌ Not compatible (uses Node.js crypto) import { jwtVerify } from 'jose'; // ✅ Compatible (Web Crypto based) // Always test packages before deploying export const runtime = 'edge'; export async function GET() { try { // This will work const token = await new jose.SignJWT({ foo: 'bar' }) .setProtectedHeader({ alg: 'HS256' }) .sign(new TextEncoder().encode('secret')); return Response.json({ token }); } catch (error) { return Response.json({ error: error.message }, { status: 500 }); } }

Edge Functions for Image Processing

Transform images on-the-fly at the edge:

// app/api/image/route.ts export const runtime = 'edge'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const imageUrl = searchParams.get('url'); const width = searchParams.get('w'); const quality = searchParams.get('q') || '75'; if (!imageUrl) { return new Response('Missing image URL', { status: 400 }); } // Fetch original image const response = await fetch(imageUrl); const imageBuffer = await response.arrayBuffer(); // Use an edge-compatible image library or service const transformedImage = await transformImage(imageBuffer, { width: width ? parseInt(width) : undefined, quality: parseInt(quality), }); return new Response(transformedImage, { headers: { 'Content-Type': 'image/webp', 'Cache-Control': 'public, max-age=31536000, immutable', }, }); }

Edge Functions for Dynamic OG Images

Generate Open Graph images dynamically:

// app/api/og/route.tsx import { ImageResponse } from 'next/og'; export const runtime = 'edge'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const title = searchParams.get('title') || 'Default Title'; return new ImageResponse( ( <div style={{ fontSize: 60, background: 'linear-gradient(to bottom, #4f46e5, #7c3aed)', width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontWeight: 'bold', }} > {title} </div> ), { width: 1200, height: 630, } ); } // Use in metadata // <meta property="og:image" content="/api/og?title=My Page" />

Performance Monitoring at the Edge

Monitor edge function performance:

// app/api/monitored/route.ts export const runtime = 'edge'; export async function GET(request: Request) { const start = Date.now(); try { // Your edge function logic const data = await fetchSomeData(); const duration = Date.now() - start; return Response.json({ data, performance: { duration: `${duration}ms`, region: process.env.VERCEL_REGION || 'unknown', }, }); } catch (error) { const duration = Date.now() - start; // Log to monitoring service console.error('Edge function error:', { error: error.message, duration, url: request.url, }); return Response.json( { error: 'Internal server error' }, { status: 500 } ); } }

Edge Runtime Best Practices

Edge Runtime Guidelines:
  • Use Edge for latency-sensitive operations (auth, redirects, geolocation)
  • Keep edge functions small and focused (<1MB)
  • Avoid heavy computations or large data processing
  • Use Node.js runtime for complex operations, file system access, or CPU-intensive tasks
  • Test packages thoroughly for Edge compatibility
  • Cache aggressively with appropriate headers
  • Monitor cold start times and execution duration

Debugging Edge Functions

// app/api/debug/route.ts export const runtime = 'edge'; export async function GET(request: Request) { return Response.json({ // Request info url: request.url, method: request.method, headers: Object.fromEntries(request.headers), // Edge info runtime: 'edge', region: process.env.VERCEL_REGION, // Geo info (Vercel) geo: { country: request.headers.get('x-vercel-ip-country'), region: request.headers.get('x-vercel-ip-country-region'), city: request.headers.get('x-vercel-ip-city'), }, }); }

Exercise: Build Edge Functions

  1. Create an edge API route that returns user's geolocation information
  2. Implement authentication middleware that protects /dashboard routes
  3. Build an A/B testing middleware with cookie-based variant tracking
  4. Create an edge function that transforms images (resize, format conversion)
  5. Generate dynamic Open Graph images using next/og
  6. Implement rate limiting middleware at the edge
  7. Create a redirect middleware based on user agent (mobile/desktop)
  8. Monitor and log edge function performance metrics