I want to show you a line of code. You almost certainly have it somewhere:
export const runtime = 'edge'
Go look. I'll wait.
Every time I open someone's Next.js repo, it's there. Usually on a streaming route, sometimes stamped across an entire API folder. And when I ask who put it there, nobody knows. The answer is always some version of "it's faster, right?" or "we needed it for streaming."
Here's the thing: you weren't wrong. In 2022 that was a solid call. But the ground moved under that line, and now it's quietly costing you — Node APIs you can't reach for, libraries that explode at build time, and that one afternoon you lost to "wait, why isn't crypto here?"
So let me walk you through what changed. Not the changelog version — the version that actually rewires how you think about your backend.
First, let's be fair to Edge
I'm not here to tell you past-you was an idiot. Edge Runtime solved two genuinely painful things:
- One request, one instance. Every concurrent request needed its own container. Traffic spike? Enjoy your wall of cold starts.
- You paid for waiting. Billing was GB-seconds of wall clock. That 900ms your function spent staring at Postgres doing nothing? You paid for it.
Edge dodged both by running a stripped-down V8 isolate instead of a real Node process. Isolates boot in single-digit milliseconds. Beautiful.
The price was Node itself. No fs. No native modules. Half a crypto. A long, miserable tail of npm packages that just don't run.
You took that trade. It was often worth it.
Then Vercel went and fixed the actual problem instead of routing around it. That fix is Fluid Compute, and it's the default now.
The idea that made it click for me
Sit with this one for a second, because everything else falls out of it.
Your backend spends most of its life waiting. Waiting on Postgres. Waiting on Stripe. Waiting four full seconds while an LLM dribbles out tokens one at a time. And during every millisecond of that, the CPU you're paying for is doing absolutely nothing.
Classic serverless shrugs at that. One request owns one instance, start to finish, idle or not.
Fluid looks at the same idle CPU and sees inventory.
A Fluid instance takes multiple concurrent requests. Request 2 shows up while request 1 is blocked on I/O, lands on that same warm instance, and burns CPU that you were throwing in the bin.
Two things follow immediately:
Your cold starts mostly go away. Instances get reused instead of provisioned per request. You get the thing you wanted from Edge — without handing back Node to get it.
Your in-memory state survives. Database pool, cached JWKS, compiled regexes — they persist now, like on a normal server. Flip side, and I'd genuinely watch for this one: module-scope mutable state is now actually shared between concurrent requests. Treat it the way you'd treat state on any long-lived server, because that's what you have now.
If you take one sentence out of this post, take this one: it's a concurrency model change, not a speed trick.
"But we need Edge for streaming"
This is the one I hear most, and it's the expensive one — because it's exactly why Edge ends up on the routes that need Node the most.
Streaming was never an Edge thing. ReadableStream, Server-Sent Events, AI token streaming — all of it runs on the default Node.js runtime. Zero config. Watch:
// app/api/chat/route.ts — no `runtime` export. Node.js. Streams beautifully.
import { streamText } from 'ai'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: 'anthropic/claude-sonnet-5', // via AI Gateway
messages,
})
return result.toUIMessageStreamResponse()
}
Prefer it raw, no framework? Same story:
export async function GET() {
const stream = new ReadableStream({
async start(controller) {
for (const chunk of await getChunks()) {
controller.enqueue(new TextEncoder().encode(`data: ${chunk}\n\n`))
}
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
}
Now flip it around. Your AI route is the worst thing you could put on Edge, not the best. It runs long. It's almost pure I/O wait. It wants SDKs, tracing, a database driver — the whole Node ecosystem.
That's not an edge case for Fluid. That's the exact shape it was built for.
"Edge is cheaper though"
Not anymore, and this is the part I think most people missed: Fluid changed the billing model, not just the runtime.
Vercel Functions now bill on Active CPU time + provisioned memory + invocations. Not GB-seconds of wall clock.
If your function is CPU-bound, this barely moves. If it's I/O-bound — so, most of your functions, and every single AI function you've ever written — the gap between wall clock and active CPU is huge. And you've stopped paying for it.
Then instance reuse compounds it. Three concurrent requests on one warm instance is one block of provisioned memory, not three.
"Middleware has to be Edge"
Two things to untangle here, and they trip people up separately.
Next.js middleware runs full Node.js now. That edge-only constraint is gone. Middleware and Edge Functions are both Vercel Functions under the hood these days.
Routing Middleware is a different product. Framework-agnostic, runs before the cache, works with SvelteKit, Nuxt, Astro, or a pile of static files. It is not Next.js middleware. Keep them separate in your head when you're reading docs, it'll save you an hour.
And while I have you: ISR isn't Next-only either. SvelteKit, Nuxt, and Astro all get it on Vercel.
The limits you're still designing around
This is my favorite part, because I keep finding architecture decisions that are load-bearing on constraints that stopped existing.
If any of these shaped a design of yours, go reopen it.
Two of them trigger a redesign more often than the rest:
5 GB package size. Up from 250 MB. That's a 20× jump, and it means Playwright, Puppeteer, Python data and ML libraries, heavy image processing. If you've got a "we'll need a separate container for this" conversation on the calendar, you might be able to cancel it.
100 MB request bodies. Up from 4.5 MB. Uploads, fat webhook payloads, document intake — straight into a Function. You can stop doing the signed-URL dance you only ever did to dodge a body limit.
And WebSockets work now. So if your reflex is "we'll need Pusher for this one feature," give it a second look first:
import { experimental_upgradeWebSocket } from '@vercel/functions'
export function GET(request: Request) {
const { socket, response } = experimental_upgradeWebSocket(request)
socket.addEventListener('message', (event) => {
socket.send(`echo: ${event.data}`)
})
return response
}
Needs Fluid Compute — which, again, you already have. Plain ws and Socket.IO work too.
Do this Monday morning
1. Find your Edge exports. Run this right now, seriously:
grep -rn "runtime = ['\"]edge['\"]" --include="*.ts" --include="*.tsx" .
For every hit, ask yourself one question: what is this buying me? If the answer is "streaming" or "it felt faster" — delete the line. Test it, obviously. But expect it to just work, with more available to it than it had before.
2. Check your Node version. Node 24 is the LTS default. Node 18 is deprecated. If you're pinned to 18, that's a ticket, write it now.
3. Go find your workarounds. Anything you built to dodge a 4.5 MB body, a 250 MB bundle, or a 60-second timeout deserves a fresh look.
4. Try vercel.ts instead of vercel.json. Typed config, real logic, env access:
// vercel.ts
import { routes, type VercelConfig } from '@vercel/config/v1'
export const config: VercelConfig = {
framework: 'nextjs',
headers: [
routes.cacheControl('/static/(.*)', {
public: true,
maxAge: '1 week',
immutable: true,
}),
],
crons: [{ path: '/api/cleanup', schedule: '0 0 * * *' }],
}
When I'd still use Edge
I'm not telling you it's dead. I still reach for it on genuinely trivial, latency-critical work right at the boundary: a header rewrite, a geo redirect, an A/B bucket assignment, a feature flag lookup. Small, CPU-light, no Node dependencies, runs before anything else touches the request.
That's a narrow band, though. Anything that touches a database, an SDK, a file, or a model? That's Fluid.
Here's the heuristic I'd give you: Edge is a specialist tool for the request boundary. It is not the default for your application code. Somewhere along the way we all flipped that around, and a lot of us are still carrying the flip.
The bit I actually want you to remember
Fluid Compute isn't a Vercel feature to file away. It's a category shift.
We spent years accepting that serverless meant one-request-per-instance, cold starts, and paying for idle time. Then we built elaborate, clever workarounds on top of those assumptions — and got good at maintaining them.
The assumptions changed. The workarounds didn't. A lot of what you're maintaining right now is complexity you're paying for out of habit.
Go delete a line of code. Tell me what breaks.
Written by Jesús García at XpectreLabs. Run that grep and drop what you find in the comments — I'm genuinely curious how many of you have that line sitting on an AI route.

















