Systems Architecture
Vercel
Fluid Compute + Deployment Platform

Vercel Architecture: Fluid Compute, Request Lifecycle, and the Deployment Pipeline

How a git push becomes a globally distributed deployment. The CDN topology, Fluid compute concurrency model, environment isolation, and every decision point where latency or cost accumulates.

Overview

Vercel is a deployment platform built around the idea that the hosting layer should understand your framework. When you push to GitHub, Vercel does not just copy your files to a server. It reads your framework's output format, decides what becomes a CDN-cached static file, what becomes a serverless function, and where in the world each piece lands. That distinction drives everything downstream: latency, cost, scaling behavior, and debugging.

Most developers interact with Vercel through preview URLs and production deploys. The architecture underneath those interactions involves a global CDN with 126 or more Points of Presence across 51 countries, 20 or more compute regions, a build system that turns framework output into deployment artifacts, and Fluid compute, the execution model that replaced classic one-request-per-instance serverless in 2025.

🌐

Global CDN

126+ PoPs across 51 countries. Static assets served from the closest edge node. Zero TTL configuration needed for framework-aware deploys.

Fluid Compute

Default since April 2025. Multiple requests run concurrently on one function instance. Reduces cold starts through bytecode caching and pre-warming.

🔀

Framework-Aware Build

Next.js, SvelteKit, Nuxt, Remix, and more are understood at the build layer. Routing, caching, and rendering strategy read from framework output.

🔒

Environment Isolation

Three environments (Production, Preview, Development) with distinct env var scopes. Every branch gets a preview URL before touching production.

The Core Mental Model

Vercel operates as two layers. The CDN layer handles routing, caching, and static assets at the edge. The compute layer handles functions, SSR, and dynamic routes in specific regions. A request hits the edge first. If the response is cached, it never reaches compute. If it needs computation, the edge proxies to the nearest function region. Knowing which layer handles each request is the foundation for debugging performance.

CDN and Global Network

Vercel's CDN is not a traditional CDN bolted onto a hosting service. It is the routing layer that all traffic passes through, including dynamic responses. The network consists of PoPs (Points of Presence) that serve cached content and route requests to compute regions when computation is required.

USER (Lagos, Nairobi, London, São Paulo) │ ▼ ┌─────────────────────────────────────────────────────┐ │ Vercel CDN Edge (126+ PoPs) │ │ • TLS termination │ │ • Cache hit? → serve immediately │ │ • Routing rules, rewrites, redirects │ │ • Security headers + compression │ └───────────────────────┬─────────────────────────────┘ │ Cache miss / dynamic route ▼ ┌─────────────────────────────────────────────────────┐ │ Vercel Compute Region (20+) │ │ • Vercel Function (Node.js / Python / Go / Bun) │ │ • Fluid compute instance (concurrent requests) │ │ • Connects to your database / API │ └─────────────────────────────────────────────────────┘

PoP vs Compute Region

These are two different tiers. A PoP is a lightweight edge node that terminates TLS, checks caches, applies routing rules, and serves static content. There are 126+ of them, spread thin across the globe. A compute region is a full data center that runs your functions. There are 20+ of them, concentrated in major cloud provider zones (AWS, GCP).

Static assets hit the PoP and return. Dynamic functions execute in a compute region. The edge proxies the request to the nearest region that has your function deployed. This is why pinning a function to iad1 (Northern Virginia) when your database is in eu-central-1 produces ~100ms of unnecessary round-trip on every DB query.

Framework-Aware Routing

Traditional CDNs require manual cache-control header configuration. Vercel reads the framework's build output format, called the Build Output API, and sets cache policies automatically. A Next.js static page gets a long-lived CDN cache. A dynamically rendered route gets no-store. ISR routes get stale-while-revalidate headers matched to the revalidate config.

Every deploy is scoped to a branch. CDN configuration changes are previewed on a branch URL before reaching production. There is no separate CDN config file to maintain.

High Availability

Vercel runs an availability zone failover model. If a function instance in one AZ goes down, traffic shifts to another AZ in the same region. If the entire region goes down, traffic routes to the next closest region. This applies to both Fluid and non-Fluid deployments. The failover is automatic and opaque to application code.

Request Lifecycle

A request from a browser to a Next.js app on Vercel takes a specific path. Understanding each hop explains where latency comes from and where it can be cut.

1

DNS Resolution

Your domain points to Vercel's anycast IP. The browser resolves the nearest PoP in milliseconds.

2

TLS at Edge

TLS handshake completes at the nearest PoP, not at the origin. Reduces TLS overhead for geographically distributed users.

3

Cache Check

Edge checks if a valid cached response exists. A hit returns immediately. Static assets always hit here.

4

Middleware

Edge middleware runs at the PoP if configured. Used for auth, geo-routing, A/B headers. Runs before compute.

5

Function Invoke

Cache miss + dynamic route: edge proxies to compute region. Fluid instance receives the request.

6

Response + Cache

Function returns response. Edge optionally caches it (ISR / stale-while-revalidate). Returns to client.

Where Latency Accumulates

  • Cold start: A function instance that has not been active recently takes time to initialize. Fluid compute reduces this with bytecode caching and pre-warming on production. Still non-zero on Hobby plans.
  • Function-to-database distance: A function pinned to iad1 querying a Neon database in eu-west-2 adds ~80-100ms per query. Pin functions to the same region as your data.
  • Response size: Large HTML responses or uncompressed payloads from the function slow the client-visible response time. Vercel applies Brotli compression at the edge, but oversized payloads still matter.
  • Serialized DB queries: Awaiting queries one by one inside a function. Each round-trip to the database is a blocking pause. Parallelize where possible.

Edge Middleware

Middleware runs at the edge PoP, before the request reaches your function. It has access to the request headers, URL, and geo-location data. Use it for redirects, auth token checks, locale detection, and A/B routing. Because it runs at the edge, it adds minimal latency (typically under 2ms for lightweight logic).

Middleware uses the edge runtime. It cannot use Node.js APIs. It cannot hold a long-lived database connection. Connecting to a database inside middleware puts you in the wrong architectural layer.

Fluid Compute

Classic serverless has a well-known problem: one request per instance. Each incoming request potentially starts a new container, waits for initialization, handles the request, and idles until the instance is recycled. This works for bursty, low-latency workloads. It breaks down for long-running tasks like streaming AI responses, where the instance sits mostly idle while tokens trickle in.

Fluid compute, which became the default for all new Vercel projects on April 23, 2025, changes this. A single function instance can handle multiple concurrent requests. The instance stays alive and warm between requests. Unused CPU capacity during one request's I/O wait is available to another request on the same instance.

Classic Serverless (old model) ─────────────────────────────────────────────────────── Request A → [Instance 1: init → handle → idle → die] Request B → [Instance 2: init → handle → idle → die] Request C → [Instance 3: init → handle → idle → die] ↑ 3 cold starts, 3x resource cost, idle waste Fluid Compute (current default) ─────────────────────────────────────────────────────── Request A ──┐ Request B ──┤→ [Instance 1: warm, concurrent, shared] Request C ──┘ ↑ 1 warm instance, 3 concurrent requests, lower cost

Optimized Concurrency

Fluid compute handles multiple invocations within a single function instance. This is available on Node.js and Python runtimes. Vercel automatically manages how many concurrent requests an instance handles before scaling up to a new instance.

Errors in one concurrent request do not crash the instance or affect other requests. Fluid compute isolates uncaught exceptions per request. An unhandled promise rejection in request A does not abort request B running on the same instance.

Bytecode Caching

One of the larger sources of cold start time in Node.js is parsing and compiling JavaScript. On each cold start, Node.js re-parses the function bundle from source text. Fluid compute applies V8 bytecode optimization automatically. The compiled bytecode is cached across invocations, so subsequent cold starts skip the parse step. For large Next.js app bundles, this reduces cold start time meaningfully.

waitUntil: Background Work After Response

Vercel Functions normally close the instance after return response. Any async work you fire after returning the response is cancelled. The waitUntil API from the @vercel/functions package signals that background work should continue even after the response is sent.

import { waitUntil } from '@vercel/functions'

export async function GET(req) {
  // Send response immediately
  const result = await fetchData()

  // Log to analytics after response, non-blocking
  waitUntil(logToAnalytics(result))

  return Response.json(result)
}

Use this for analytics writes, cache priming, and sending notifications. The user gets the response immediately. The background task completes on the same instance without holding up the round-trip.

Dynamic Scaling

Fluid compute scales by first trying to fill existing warm instances with concurrent requests. Only after available concurrency is exhausted does it spin up a new instance. This means the scaling knee is higher before new cold starts occur. During a traffic spike, existing warm instances absorb more load before new ones appear. During a traffic lull, fewer instances sit idle, reducing cost.

When Fluid Compute Matters Most

Fluid compute has the largest impact on two types of workloads. First, AI inference proxies: requests that stream a slow upstream response (an LLM token stream) hold the instance for seconds. Under classic serverless, those seconds counted as one-request-active-one-request-blocked. Under Fluid compute, other requests run concurrently during the wait. Second, high-traffic API routes that receive bursts: Fluid absorbs concurrent requests on warm instances before triggering cold starts. For simple low-traffic routes, the difference is smaller.

Vercel Functions

Vercel Functions are the compute primitive. They accept an HTTP request, run your code, and return a response. Everything that is not a static file becomes a function on Vercel. In Next.js, that includes API routes, server components with data fetching, route handlers, and middleware.

Runtime Options

  • Node.js (default): Full Node.js API surface. Supports persistent connections (useful for database pooling via Fluid compute). Larger bundle sizes. Duration limits: 300s on Hobby, 800s default on Pro, 1800s extended max on Pro.
  • Python: Supports Fluid compute concurrency. Useful for ML inference wrappers and data-heavy backends. Same duration limits as Node.js.
  • Go: Statically compiled. Fast startup. Single-binary deployment. No Fluid compute concurrency currently (as of August 2026).
  • Bun: Compatible with Node.js APIs. Supports Bun.serve as an entrypoint. Supports large functions and extended max duration on Fluid compute.
  • Edge runtime: Web APIs only. No fs, no crypto, no native modules. Global cold-start near zero. Tight bundle and CPU limits. Suitable for auth checks, geo routing, and lightweight transforms.
Runtime Fluid Concurrency Max Duration (Pro) Node.js APIs Use Case
Node.js Yes 800s / 1800s extended Full General API, SSR, AI streaming
Python Yes 800s / 1800s extended N/A (Python stdlib) ML inference, data processing
Go No 800s None High-throughput APIs, CLIs
Bun Yes 800s / 1800s extended Compatible Fast server-side JS, Hono
Edge Inherent (stateless) ~30s (strict) Web APIs only Auth middleware, geo routing

Route Segment Config

In Next.js App Router, each route file can export config that controls how Vercel runs it. These are read at build time and baked into the deployment artifact.

// app/api/stream/route.ts
export const runtime = 'nodejs'       // 'nodejs' | 'edge'
export const dynamic = 'force-dynamic' // no CDN cache
export const maxDuration = 60          // seconds (≤ plan limit)

// ISR route: regenerate every 60s
export const revalidate = 60

Cron Jobs

Scheduled work is configured in vercel.json as a crons array. Each entry maps a path to a cron expression. Vercel calls that path on schedule using a standard HTTP GET. The path is a public URL, so you must protect it.

{
  "crons": [
    { "path": "/api/daily-sync", "schedule": "0 9 * * 1-5" }
  ]
}

Vercel sends an Authorization: Bearer <CRON_SECRET> header on each scheduled call. Verify it in the handler. Without this check, any caller can trigger your cron endpoint.

Region Pinning

Functions run in the region closest to the user by default. For data-heavy functions that query a specific database, this default is wrong. A user in Tokyo hitting a function that proxies to a Neon database in AWS us-east-1 wastes the geographic proximity advantage entirely.

Pin your function to the same region as your database. In route config: export const preferredRegion = ['iad1']. For Fluid compute projects, set the region in the project dashboard. The function runs farther from some users, but the database round-trip drops from ~150ms to ~2ms.

Build Pipeline

A Vercel deploy starts with a git push. The build pipeline takes your source and produces a build output artifact, then distributes that artifact to the CDN and compute layers.

1

Git Push

Push to any branch. Vercel detects the push via GitHub/GitLab/Bitbucket webhook and queues a build.

2

Framework Detection

Vercel inspects the repo for framework markers (next.config.ts, svelte.config.js, etc.) and selects the matching build preset.

3

Build Execution

Runs your build command (npm run build). Uses cached node_modules and build artifacts from previous deploys when available.

4

Output Parsing

Reads the build output (Build Output API format). Separates static files, edge functions, serverless functions, and routing rules.

5

Artifact Deploy

Static assets push to CDN edge nodes globally. Functions deploy to selected compute regions. Routing config propagates to all PoPs.

6

Live URL

Branch deploy gets a unique preview URL immediately. Production branch also updates the production domain atomically.

Build Cache Behavior

Vercel caches node_modules and framework build artifacts between deploys. On a dependency change, the cache invalidates automatically. On a non-dependency code change, the cached node_modules accelerates the build significantly (from ~60s to ~15s for a mid-size Next.js app). If a deployment behaves unexpectedly after a dependency upgrade, clear the build cache from Settings > Git > Clear Build Cache and redeploy. The cache is project-scoped, not branch-scoped.

Atomic Deployments

When a new production deploy is live, traffic shifts atomically. The old deployment stays accessible via its unique immutable URL. There is no rolling update window where some requests hit the old code and others hit the new code. Both old and new deployments coexist with separate URLs. Rolling back is switching the production alias to the previous immutable deployment URL, which completes in seconds.

Environment Model

Vercel has three environments, and the distinction matters more than it looks. Env vars are scoped per environment. A var set only for Production does not appear in preview deployments. A var set for both Production and Preview does not appear in local development unless explicitly pulled.

Three Environments

  • Production: Builds from the production branch (usually main). These deploys serve your custom domain. Vars scoped here are available only in production builds.
  • Preview: Every branch push and every PR gets a preview URL (e.g. my-app-git-feature-xyz-org.vercel.app). Vars scoped to Preview are available in all non-production deploys.
  • Development: Pulled to your machine via vercel env pull .env.local. Available only locally. Never deployed.

NEXT_PUBLIC_ Is Not Private

Any env var prefixed with NEXT_PUBLIC_ is inlined into the client-side JavaScript bundle at build time. Next.js replaces the variable reference with the literal string value. Anyone who inspects your bundle can read it. Never put tokens, API keys, or secrets in a NEXT_PUBLIC_ var. Use plain vars (without the prefix) for server-only values. They are never bundled into client code.

Env Changes Require a Redeploy

Changing an env var in the Vercel dashboard does not affect the running deployment. The build bakes var values into the artifact at compile time (especially NEXT_PUBLIC_ vars). To pick up the new value, trigger a new deploy. For server-only vars passed via process.env, the behavior depends on whether the value is used at build time or request time. Request-time reads get the var from the running container environment, so server-only vars can be changed with a redeploy. Build-time reads (like metadata generation or static page data) require a full rebuild.

Preview Deployments and Database Branches

The pattern that makes preview deploys genuinely useful: pair each preview deployment with an isolated database branch. Neon and Supabase both support branch-per-preview. Each PR gets its own database state. Migrations and seed data apply to the branch, not the production database. The branch is deleted when the PR merges.

Set the database connection string as a branch-scoped env var in Vercel. When the PR deploy runs, it reads the branch-specific connection string and never touches production data.

Caching Architecture

Vercel caching operates at multiple layers. Understanding which layer holds a cached response determines how to invalidate it, how long it persists, and who benefits from it.

🌍

CDN Edge Cache

Static files and statically rendered pages. Lives at PoP nodes globally. Served without touching compute. Invalidated on new deploy.

🔄

ISR (Incremental Static Regeneration)

Cached responses with a TTL. After expiry, the next request triggers regeneration in the background. Stale content served during regen.

💾

Data Cache (Next.js)

Per-request or tag-based cache for fetch() calls in server components. Persists across deploys. Invalidated with revalidateTag() or revalidatePath().

🖥️

Full Route Cache

Server-rendered output cached at build time for static routes. Reused across requests without re-running server components.

ISR: How It Actually Works

ISR is often described as "regenerates every N seconds." The actual behavior is more precise. When you export revalidate = 60 from a route, Vercel serves the cached response for 60 seconds. After 60 seconds, the next request triggers background regeneration. The requesting user still gets the stale response. The next user after regeneration completes gets the fresh one.

This is stale-while-revalidate. The tradeoff: low latency (cached response always returned immediately) at the cost of up-to-N seconds of stale data. For content that changes frequently and must be current, use dynamic = 'force-dynamic' and accept the function invocation cost on every request.

Image Optimization

Next.js <Image> on Vercel uses Vercel's Image Optimization service automatically. Vercel resizes, converts to WebP or AVIF, and caches optimized variants at the edge. Remote image sources must be declared in next.config.ts via remotePatterns.

Plan limits apply: Hobby allows 1,000 source images per month. Pro allows 5,000. Exceeding the limit incurs per-image overage charges. If you process many images, cache generated variants in object storage (Cloudflare R2, S3) and serve them directly, bypassing Vercel's optimizer on repeated requests.

Cost Reality Check

Vercel pricing has several dimensions that are easy to overlook until a bill arrives. The categories that most often surprise teams are function duration, bandwidth, image optimization, and seat count.

Cost Reality Check

Function duration is the primary lever. You are billed per GB-second (memory x duration). A function allocated 1 GB of memory that runs for 10 seconds per invocation costs 10 GB-seconds. At 100,000 invocations per month, that is 1,000,000 GB-seconds. Fluid compute reduces this by sharing instance cost across concurrent requests. A warm instance handling 5 concurrent requests accumulates 5x the invocations but only 1x the duration cost on the running instance.

Cost Driver When It Accumulates Mitigation
Long-running functions AI streaming, large file processing Fluid compute concurrency; streaming response patterns
No caching on static routes force-dynamic on routes that could be ISR Use revalidate = N for content that tolerates some staleness
Image optimization overages High-volume image-heavy sites on Hobby Pre-process images to WebP/AVIF in CDN; serve from R2
Bandwidth overages Serving large files (PDFs, videos) from functions Use R2 or S3 presigned URLs; never stream large assets through functions
Preview build minutes Many branches, frequent pushes Disable preview builds for non-code branches in project settings
Database connections Serverless functions opening new DB connections per invocation Connection pooler (Neon pooler, PgBouncer) or Fluid compute persistent connections
Fluid Compute and Database Connection Pooling

Classic serverless creates a new database connection on every cold start and drops it when the instance dies. At scale, hundreds of short-lived connections overwhelm Postgres's connection limit. Neon and Supabase both provide connection poolers to absorb this. Fluid compute changes the equation: instances stay warm across requests and can hold persistent connections. With Fluid compute enabled, you can open a connection in module scope and reuse it across concurrent requests on the same instance, reducing total connection count significantly. Still use a pooler for safety, but the pressure is far lower.

Key Takeaways

Principles

  1. Fluid compute is the default; classic serverless is the legacy path. New projects created after April 23, 2025 use Fluid compute automatically. If you are on an older project and have not enabled it, the concurrency and cold-start improvements are sitting unused.
  2. Pin functions to the region where your database lives. Geographic proximity to the user matters for static assets. For data-fetching functions, geographic proximity to the database matters more. A function 10ms from the user but 150ms from the database is slower than a function 40ms from the user and 2ms from the database.
  3. NEXT_PUBLIC_ vars are public. Full stop. The prefix is a signal that the value is safe to ship in the browser bundle. Treat it as such. A token in a NEXT_PUBLIC_ var is exposed to every visitor who opens DevTools.
  4. Env changes need a redeploy. The Vercel dashboard is not a live config system. Values are baked in at build time. Updating a var and expecting it to take effect immediately is a common source of confusing behavior in production incidents.
  5. Atomic deployments and immutable URLs are the rollback mechanism. There is no "undo deploy." The previous deployment still exists at its unique URL. Rollback is switching the production alias to point at that URL. This completes in seconds.
  6. waitUntil is the right place for post-response work. Analytics writes, cache priming, and notifications do not belong inside the response path. Use waitUntil from @vercel/functions to run them after the response is sent without blocking the user.
  7. ISR is stale-while-revalidate, not "updates every N seconds." The revalidation fires after the TTL expires, triggered by the next request, while serving the old content to that requestor. Design data freshness requirements around this actual behavior, not the idealized one.
  8. Cron endpoints are public URLs. Protect them. Always verify the Authorization: Bearer <CRON_SECRET> header in cron handlers. Without this check, any caller with the URL can trigger scheduled jobs on demand.