Vercel's Fluid Compute Cut Our Cold Starts, But Broke Our Budget Alerts
Fluid Compute genuinely fixed our Next.js cold start problem. It also quietly changed how invocations are billed and metered — and our alerting missed it for three weeks.

We migrated a mid-traffic Next.js app to Vercel's Fluid Compute in Q4 and got exactly what the marketing promised on latency: p95 cold starts on our auth-heavy routes dropped from painful to barely noticeable. What we didn't get was a heads-up that our old budget alerts, tuned against the classic serverless invocation model, would go silent while the underlying billing shape shifted underneath us.
This is the write-up we wish we'd read before flipping the switch.
What Fluid Compute actually changes
The short version: classic Vercel Functions ran one invocation per instance, spun a fresh isolate on cold paths, and billed you per invocation plus GB-seconds. Fluid Compute keeps instances warm longer, lets a single instance handle concurrent requests, and shares work like connection pools and imports across those requests. Vercel's own docs frame it as "servers with serverless ergonomics," which is roughly right.
The practical consequences we saw:
- Cold starts stopped clustering around deploys and traffic dips.
- Outbound connection reuse (Postgres, Redis, a couple of internal HTTP APIs) actually worked between requests, instead of every invocation warming its own pool.
- CPU-bound handlers got noisier neighbors, because concurrent requests on one instance now share a CPU budget.
None of that is controversial. The interesting part is what it did to our cost model and our dashboards.
The app in question
So you can calibrate: a B2B SaaS front-end plus a Next.js API layer, roughly 8–12 million route invocations per week, mixed SSR and route handlers, Node runtime (not Edge). Postgres via a pooled connection through PgBouncer, Redis for session cache, a couple of third-party APIs behind a small circuit breaker. Nothing exotic.
The cold start win was real
We don't publish absolute numbers on client workloads, but the shape of the improvement was consistent across three environments:
- p50 route handler latency: essentially unchanged.
- p95: modest improvement, maybe 15–25% on routes that were previously bottlenecked on module init.
- p99 during traffic ramps: this is where Fluid earned its keep. The long tail from fresh isolates on auth, feature-flag, and SSR routes basically collapsed.
The reason is boring and correct: our worst latency wasn't the function code, it was the 200–600ms of module resolution, JIT warmup, and connection setup that happened once per fresh isolate. Fluid amortises that across many requests.
If your app has heavy dependency graphs — Prisma, a big auth SDK, an ORM plus a validation library plus a telemetry SDK — you will feel this. If your handlers are tiny and your dependencies are lean, you will feel it much less and possibly should not bother migrating yet.
Where the billing model quietly shifted
Here's the part that cost us three weeks of quiet overspend.
Classic Functions billing had a mental model most of our team internalised: invocations × average duration × memory. Our budget alerts in Vercel and our mirrored alert in Grafana (fed from the Vercel usage API) were built on invocation count and total GB-hours per day, with thresholds set from a rolling 14-day baseline.
Fluid Compute changes the shape of the meter in ways worth stating plainly:
- Active CPU time is metered separately from wall-clock time on the instance. An instance sitting idle waiting on a database costs far less than one burning CPU.
- Concurrent requests share instance time, so "invocations" and "GB-hours" no longer track each other the way they did.
- Warm instances live longer, which is great for latency but means your baseline "idle" cost is different.
On paper this is better and, in aggregate, our unit economics improved. The problem was the transition week. Our alerts were watching invocation-scaled GB-hours. Fluid made GB-hours drop noticeably for the same traffic. Our thresholds, tuned as absolute floors and ceilings, treated the drop as "nothing to see here" — until a background job regression started spinning CPU on a handful of long-lived instances and we didn't notice for the better part of a sprint, because total GB-hours still looked lower than the old baseline.
The alert we should have shipped on day one
We now track four things instead of two, and we recommend the same to anyone migrating:
- Active CPU seconds per day, with its own baseline.
- Provisioned memory-hours (instance lifetime × memory), separate from CPU.
- Invocations per active CPU second — a proxy for "are we actually benefiting from concurrency?"
- p95 handler duration split by route group, because concurrency-induced CPU contention shows up here first.
A rough sketch of the Terraform we use to push these into our alerting stack via the Vercel API and a Grafana Cloud webhook:
resource "grafana_contact_point" "vercel_budget" {
name = "vercel-fluid-budget"
webhook {
url = var.ops_webhook_url
}
}
resource "grafana_rule_group" "fluid_compute" {
name = "vercel-fluid-compute"
folder_uid = grafana_folder.ops.uid
interval_seconds = 300
rule {
name = "active-cpu-seconds-anomaly"
condition = "C"
for = "15m"
data {
ref_id = "A"
relative_time_range { from = 3600 to = 0 }
datasource_uid = grafana_data_source.prom.uid
model = jsonencode({
expr = "sum(rate(vercel_active_cpu_seconds_total[10m]))"
})
}
data {
ref_id = "C"
datasource_uid = "__expr__"
model = jsonencode({
type = "threshold",
expression = "A",
conditions = [{ evaluator = { type = "gt", params = [1.6] } }]
})
}
}
}
The 1.6 multiplier is versus a slow-moving 7-day median. Nothing clever, just something that would have caught our regression on day two instead of day twenty-one.
Concurrency changed our failure modes
The other thing nobody warned us about: with multiple in-flight requests per instance, one badly behaved handler can starve its neighbours.
We hit this on a route that did synchronous JSON parsing of a large webhook payload. Under classic Functions, it was slow but isolated — its own instance, its own CPU. Under Fluid, three concurrent copies of it on the same instance briefly pushed p95 on unrelated routes into alarming territory because event loop lag spilled across handlers.
Two things helped:
- Moving heavy CPU work to a queue-backed worker (in our case a small Cloud Run service, called from the Vercel handler). If you're weighing that pattern, our team has written about the tradeoffs on our DevOps and cloud services page.
- Setting stricter per-route memory sizing. Fluid lets you configure this per function; we err on the side of smaller memory with lower concurrency for CPU-bound routes, and larger memory with higher concurrency for I/O-bound ones.
A note on observability
OpenTelemetry auto-instrumentation for Node inside Vercel functions is still fiddly. Under Fluid, because instances persist, the SDK actually gets a chance to batch and flush spans properly, which is a genuine improvement over the classic model where we lost tail spans on isolate teardown constantly. If you tried OTel on Vercel a year ago and gave up, it's worth another look. Sentry's Vercel integration also behaves better under Fluid for the same reason.
The tradeoffs we'd flag before you migrate
- Cost is not automatically lower. Aggregate GB-hours often drop, but active CPU billing means CPU-heavy workloads can end up flat or slightly higher. Model your workload before assuming savings.
- Your alert baselines are invalid. Re-baseline everything the day you flip Fluid on. Don't trust two-week-old thresholds.
- Noisy-neighbour effects are real. Audit any handler that does synchronous CPU work over 50ms.
- Long-lived instances change your assumptions about globals. Module-scope caches now actually persist. This is usually good, occasionally a security footgun if you're not careful about per-request isolation of user data.
- Edge Runtime is a separate conversation. Fluid applies to Node functions. If you're already on Edge for latency reasons, this migration isn't for you.
Where we'd start
If you're evaluating Fluid Compute on a real production Next.js app, do these in order:
- Pick one route group with known cold start pain. Measure p95 and p99 for two weeks on classic Functions.
- Enable Fluid on that project only. Re-measure for two weeks. Look at active CPU and memory-hours separately, not just "cost."
- Rebuild your budget alerts against the new meters before you roll it out further. Active CPU seconds and memory-hours as independent series, not a combined dollar figure.
- Audit CPU-heavy handlers for noisy-neighbour risk and either shrink instance memory (to reduce concurrency) or move the work off-platform.
- Only then, roll it project-wide.
The latency win is real and worth having. Just don't let the shape of the new bill catch you the way it caught us.
Want a team like ours?
72Technologies builds production software for the kind of teams who actually read this blog.
Start a projectKeep reading

The DynamoDB On-Demand Bill That Ate Our Margin: A Capacity Mode War Story
We flipped a DynamoDB table to on-demand for 'peace of mind' before a launch. Three weeks later the bill was 6x provisioned. Here's what we learned about capacity modes, autoscaling, and when on-demand actually pays off.

Our AWS NAT Gateway Bill Tripled Overnight: Tracing a Rogue S3 Egress Path
A quiet infra change routed our S3 traffic through the NAT Gateway instead of a VPC endpoint. Here's how we found it, what it cost, and the guardrails we wish we'd had.

GCP Cloud Run vs AWS Lambda for a Bursty API: What We Actually Measured
We ran the same Node API on Cloud Run and Lambda for a client with spiky, unpredictable traffic. The winner wasn't obvious, and the reasons weren't the ones the marketing pages hint at.
