Edge Functions vs. Serverless Functions: two different runtimes, not one feature
Cloud Computing's own serverless lesson covered AWS Lambda's general model: code that runs on demand, without a server to manage. Vercel actually offers two distinct flavors of this idea, with genuinely different runtime constraints, and picking the wrong one for a given task causes real, confusing failures.
Intermediate
2 min read
Serverless Functions: a full Node.js (or other) runtime, in one region
export default function handler(req, res) { const data = fs.readFileSync("./some-file.json"); // full Node.js APIs available res.json({ data });}
A Serverless Function on Vercel runs in a real Node.js (or Python, Go, Ruby) runtime, in one specific AWS region, with access to the full standard library and npm ecosystem — file system access, native modules, anything a normal Node process can do. This is Cloud Computing's own serverless concept applied directly: no server to manage, scales automatically, billed per invocation/duration, but running in a single region means users far from that region pay real latency to reach it.
Edge Functions: a much smaller, globally-distributed runtime
export const config = { runtime: "edge" };export default function handler(req) { // NO fs, NO native Node APIs — a restricted Web-standard runtime return new Response(JSON.stringify({ hello: "world" }));}
An Edge Function runs on the V8 isolate runtime Vercel's Edge Network uses (the same underlying technology as Cloudflare Workers) — not Node.js at all. It starts in milliseconds (genuinely faster cold starts than a Node-based Serverless Function), runs physically close to whichever user made the request, but loses access to Node's full API surface entirely: no filesystem, no native modules, only Web-standard APIs (fetch, Request, Response) and whatever subset of npm packages happens to be "Edge-compatible" (pure JavaScript, no native bindings).
Why this distinction causes real, confusing deployment failures
A Serverless Function that works fine locally can fail specifically when deployed as an Edge Function if it imports a Node-only package (one using fs, crypto's Node-specific APIs, or any native binding) — the failure often only surfaces at deploy or runtime, not at the moment the edge runtime is selected, because nothing locally enforces Edge-compatibility during normal development. Recognizing "this function needs full Node APIs" before reaching for the Edge runtime, rather than discovering it from a deployment error, is the practical skill this distinction exists to teach.
When each one is actually the right choice
Need
Right choice
Full npm ecosystem, native modules, heavier compute
Serverless Function
Low-latency, simple logic (auth checks, redirects, A/B test routing)
Edge Function
Database access via a traditional driver with persistent connections
Serverless Function (Edge Functions' short-lived, distributed nature fits poorly with connection pooling — this is exactly why Supabase's own domain covers connection pooling as a real, separate concern)
Reading/writing a request's geolocation or headers for quick routing decisions