Vercel Edge Functions Guide: Runtime, Limits and Real-World Uses
Edge Functions run at the CDN layer, not a server room. Here's everything you actually need to know about the runtime, cold starts, limits, and when to use them.
What Edge Functions Actually Are
Edge Functions aren't magic. They're just small JavaScript workers that run inside Vercel's CDN network — meaning the code executes at the node geographically closest to your user, not in a single AWS region somewhere in us-east-1. That alone can shave 200–400ms off your response time if your users are spread globally.
The underlying runtime is V8 isolates, the same tech that powers Cloudflare Workers. No Node.js. No filesystem access. No native modules. You get the Web APIs: fetch, Request, Response, URL, TextEncoder, crypto — and that's mostly it. If you're coming from Lambda or traditional serverless, that's a jarring constraint the first time you hit it.
In Next.js, you opt a route into the Edge Runtime by exporting export const runtime = 'edge' from a route handler or middleware file. That's it. No extra infrastructure config, no separate deploy pipeline. Worth noting: this works the same way in both the App Router and the older Pages Router, though the file locations differ.
Honestly, the mental model shift is the hardest part. You're not writing a server function — you're writing a filter that sits in front of your traffic. Think HTTP interceptor, not HTTP server.
The Runtime Constraints You'll Actually Hit
Let's be direct about the walls you'll run into. As of 2026, Vercel enforces a 4MB bundle size limit on Edge Functions (compressed). That sounds like a lot until you try to import a validation library, a JWT package, and a DB client at the same time. You'll blow past it faster than you think.
CPU time is capped at 50ms on the Hobby plan and 1000ms on Pro. Note that's *CPU time*, not wall-clock time — awaiting fetch() doesn't count against it. In practice this means you can do a lot of async I/O, but you can't run a heavy computation loop. No image resizing with sharp, no PDF parsing, no bcrypt.
// This will fail at build time — no Node.js crypto module
import { createHash } from 'crypto'; // ❌ Node built-in
// Use the Web Crypto API instead
const hash = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(input)
); // ✓ Web API, works on edgeMemory limit sits at 128MB. You won't hit that often for typical request-handling logic, but if you're loading large lookup tables into memory between requests — don't. Each isolate is ephemeral. There's no guaranteed warm cache between invocations the way a long-lived Node process would give you.
Quick aside: console.log works fine on edge, and the output shows up in your Vercel function logs just like serverless functions. That part is not broken, and it's a lifesaver during debugging.
Middleware: The Killer Use Case
The single most practical thing you can do with Edge Functions is Next.js Middleware. Drop a middleware.ts at the root of your project, and it runs on *every request* before any page or API route handles it. You can redirect, rewrite, set response headers, or bail early — all from the CDN layer, sub-millisecond.
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};
export function middleware(req: NextRequest) {
const token = req.cookies.get('session')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', req.url));
}
// Optionally attach user info as a header for downstream routes
const res = NextResponse.next();
res.headers.set('x-user-session', token);
return res;
}That auth check above used to require a Lambda cold start on every unauthenticated request. Now it's edge — the user gets bounced to /login from the CDN itself, and your origin never even sees the traffic. That's the real win.
You can also do A/B testing and feature flags this way. Rewrite /pricing to /pricing-v2 for 50% of traffic, set a cookie to keep the experience consistent, and never touch your origin. Vercel's own docs show exactly this pattern, and it works well in practice. That said, keep the logic dead simple — this is not where you put complex business rules.
Look, if you're building a Next.js app and you haven't set up middleware yet, you're leaving a lot of performance on the table. It's genuinely the first thing I reach for when protecting authenticated routes.
Edge API Routes and When They Make Sense
Beyond middleware, you can make any App Router route handler run on the edge. Add export const runtime = 'edge' and your handler gets deployed as an edge function instead of a serverless function. The tradeoff is real: you lose Node.js APIs, but you gain global distribution and near-zero cold starts.
// app/api/hello/route.ts
export const runtime = 'edge';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const name = searchParams.get('name') ?? 'world';
return Response.json({ message: `Hello, ${name}!` }, {
headers: { 'Cache-Control': 's-maxage=60, stale-while-revalidate=300' },
});
}Good candidates for edge API routes: geolocation-aware responses (Vercel injects req.geo on the edge), language detection and redirects, short-lived token generation, and lightweight proxy endpoints that fan out to external APIs. Bad candidates: anything touching a database with a Node.js driver, file system operations, or heavy CPU work.
One pattern that works well in 2026 is pairing edge routes with Vercel Edge Config for ultra-fast feature flag reads. Edge Config reads are ~1ms because the data is colocated at every edge node. Compare that to a round-trip database query and you'll see why this combo makes sense for kill switches and rollout flags.
In practice, most production apps end up with a mix — middleware on edge, data-heavy API routes staying as serverless. Don't force everything onto edge just because it sounds fast. The runtime constraints will bite you.
Geolocation, Headers, and Vercel's Edge Extras
Vercel injects several useful headers on edge automatically. x-vercel-ip-country gives you a two-letter country code. x-vercel-ip-city and x-vercel-ip-latitude/longitude are there too. You don't need a geo-IP database — just read the header.
export const runtime = 'edge';
export async function GET(req: Request) {
const country = req.headers.get('x-vercel-ip-country') ?? 'US';
const isEU = ['DE','FR','IT','ES','NL','PL','SE'].includes(country);
return Response.json({
country,
showGDPRBanner: isEU,
});
}This is genuinely useful for serving the right content per region without any external API calls. Show a GDPR banner for EU visitors, redirect .com traffic to a locale-specific subdomain, or surface region-specific pricing — all from the edge, no origin hit.
Worth noting: these geo headers are only populated on Vercel's infrastructure. In local dev with next dev, they won't be there. Either mock them in dev, or wrap the reads with a fallback value. Don't let your middleware crash locally because x-vercel-ip-country comes back null.
Cold Starts, Caching, and Performance Reality
Cold starts on Edge Functions are essentially zero compared to Lambda. V8 isolates spin up in under 5ms. That's the headline that gets people excited, and it's accurate — but it doesn't mean your edge endpoint will always be fast. If your edge function immediately calls a database in us-east-1, you've just pushed the latency to the database round-trip, which is still region-bound.
The real performance gain happens when you can *fully serve a response from the edge* — either by computing it from request headers/cookies alone, or by fetching from a globally replicated data source like Vercel Edge Config, PlanetScale's global read replicas, or Upstash Redis's global replication.
Caching is your friend here. Edge Functions can return Cache-Control headers, and Vercel's CDN will cache those responses at the edge node. A s-maxage=60 header means the first request computes the response, and the next 1000 requests in that region get it from cache. That's where you see real throughput gains.
// Cache the response for 60s, serve stale for 5min while revalidating
return Response.json(data, {
headers: {
'Cache-Control': 's-maxage=60, stale-while-revalidate=300',
},
});One more thing — if you're working on visual UI components alongside your edge infrastructure, check out how glassmorphism components handle their styles at build time rather than runtime. The principle is similar: push work earlier in the pipeline so runtime paths are lean. It's a good mental model for edge design too.
Debugging, Logging, and Local Development
Local development for edge functions uses next dev which runs them in a Node.js compatibility layer, not a true V8 isolate. That means some APIs that fail on real edge might work locally. The golden rule: test on a preview deployment before assuming your code is edge-compatible. Vercel's build output will tell you if a module isn't edge-safe.
# Deploy a preview and tail the logs
vercel deploy
vercel logs your-preview-url.vercel.app --followThe Vercel dashboard shows Edge Function logs under the Functions tab. Filter by runtime to separate edge from serverless. One thing that tripped me up early on: edge function log retention is shorter than serverless — check your plan's limits, as of 2026 the Pro plan retains 3 days of edge logs by default.
For debugging middleware specifically, NextResponse.next() with modified headers is your main tool. Add an x-debug-* header to trace which branch your middleware took, then inspect it in the response headers in DevTools. It's low-tech but it works every time.
If you're building polished UIs alongside your edge-powered APIs — the kind where visual quality matters as much as performance — the Empire UI component library ships with styles that work well with edge-rendered Next.js apps. No runtime CSS-in-JS, no hydration issues. Worth bookmarking the gradient generator and box shadow generator for your next design pass too.
FAQ
Not with the standard pg or Prisma Node.js drivers — those need Node.js APIs that don't exist on the edge runtime. Use Neon or PlanetScale's HTTP-based drivers, or Prisma's edge-compatible client that talks over HTTP/fetch instead of TCP.
Serverless Functions run in a specific AWS region with full Node.js support, up to 250MB bundle size, and 15-minute execution limits. Edge Functions run globally in V8 isolates with ~4MB bundle limit, 50–1000ms CPU cap, and no Node.js APIs — but near-zero cold starts.
Yes. Next.js Middleware is edge-only by design — you can't change the runtime. It always runs as an Edge Function on Vercel, which is why it has the same Web API constraints and can't use Node.js modules.
Yes, but with tighter limits: 500k invocations per month and a 50ms CPU time cap per invocation. The Pro plan raises the CPU cap to 1000ms and gives you significantly more invocations before overage charges kick in.