npx skills add ...
npx skills add vercel/vercel-plugin --skill vercel-functions
Vercel Functions expert guidance — Node.js/Bun/Python runtimes, Fluid Compute, long-duration (30 min) functions, large functions (5 GB bundles), Docker/OCI container images, plan limits, streaming, WebSockets, and Cron Jobs. Use when configuring, debugging, or optimizing server-side code running on Vercel.
npx skills add vercel/vercel-plugin --skill vercel-functions
You are an expert in Vercel Functions — the compute layer of the Vercel platform.
Strongly prefer Node.js. Don't reach for runtime = 'edge' in new code, and recommend migrating off it when you find it in existing code.
This follows Vercel's own documented guidance — "we recommend migrating from edge to Node.js for improved performance and reliability" — and one hard constraint: as of Next.js 16.3, runtime = 'edge' is no longer supported. Routes and pages there run on Node.js regardless of what you write, so on 16.3+ this stops being a recommendation and becomes a migration you have to do.
Everywhere else it is a strong default, not a prohibition. Both runtimes run on the same Fluid Compute infrastructure, in the same regions, under the same Active CPU pricing — so in nearly every case Edge gains you nothing while costing you most of the Node.js API surface. If you have a specific, tested reason to stay on Edge, that's a legitimate call; just make it deliberately rather than by habit.
Node.js is the default. Omit export const runtime entirely rather than writing export const runtime = 'nodejs'.
| "I need Edge because…" | Reality | Do this instead |
|---|---|---|
| "…I need to stream / SSE / AI tokens" | Streaming is zero-config on Node.js. This is the single most common false belief. | Return a ReadableStream from a normal Node.js function |
| "…I need low latency" | Both run on Fluid Compute. Fluid pre-warms instances and caches bytecode; the difference is noise next to your DB/API round trips | Stay on Node.js; pin regions near your data |
| "…auth checks / redirects / A-B tests at the edge" | That's Routing Middleware's job, and Routing Middleware supports full Node.js — it is not edge-only | Use Routing Middleware (routing-middleware skill) |
| "…it's cheaper" | Identical Active CPU pricing | Stay on Node.js |
| "…it has faster cold starts" | Fluid Compute reuses warm instances across concurrent invocations and bytecode-caches Node 20+ in production | Stay on Node.js |
| "…my function must run globally" | Edge's global execution usually hurts — every DB query crosses an ocean | Single region (iad1 default) next to your database |
fs, no native modules, no require() — ESM only, and most npm packages with Node.js dependencies simply will not loadeval / new Function / dynamic WebAssembly.instantiateWorth doing when you're already touching the file, and required on Next.js 16.3+. An Edge function that works today isn't an emergency.
export const runtime = 'edge' (or runtime: 'edge' in vercel.json / the config object).next/server Edge-only imports where applicable; the Web Request/Response handler signature is unchanged, so most route handlers need no other edit.preferredRegion, use regions in vercel.json instead.There is no rollback story to plan for: Node.js is a superset of what the function could do on Edge.
/apiURLPattern, Undici v7, npm v11). Node.js 20 is deprecated on October 1, 2026 — move off nodejs20.xAdd "bunVersion": "1.x" to vercel.json to run functions on Bun instead of Node.js. ~28% lower latency for CPU-bound workloads. Supports Next.js, Express, Hono, Nitro, and Bun.serve as an entrypoint. Bun supports both large functions and extended max duration.
Python 3.12 / 3.13 / 3.14 on Fluid Compute. FastAPI, Flask, and Django build into a single function from the resolved entrypoint — key vercel.json config on that entrypoint file (app/main.py, myproject/wsgi.py), not on /api routes. Python gets a 500 MB standard bundle limit (vs. 250 MB) and supports large functions and extended duration.
Rust functions run on Fluid Compute with HTTP streaming and Active CPU pricing. Built on the community Rust runtime. Supports environment variables up to 64 KB.
Any OCI image via Dockerfile.vercel. See Docker and Container Images below.
V8 isolates with a subset of Web APIs. Fine to leave in place on existing deployments, but not the runtime to pick for new work. See Prefer Node.js over the Edge runtime.
| Need | Runtime | Why |
|---|---|---|
| Anything not listed below | nodejs | The default, and correct nearly always |
| Full Node.js APIs, npm packages | nodejs | Full compatibility |
| AI streaming, SSE, WebSockets | nodejs | Zero-config streaming, long durations |
| Lower latency, CPU-bound work | nodejs + Bun | ~28% latency reduction |
| Database connections, heavy deps | nodejs | Pin regions next to the database |
| Data/ML libraries, big model files | nodejs or python + large functions | Up to 5 GB bundles |
| Systems-level performance | rust | Native speed on Fluid Compute |
| Custom system libraries (FFmpeg, Chromium), Go/Ruby/PHP, unsupported frameworks | container image | Bring your own Dockerfile |
| Auth, redirects, A/B tests before the cache | Routing Middleware | Runs on Node.js, framework-agnostic |
| Hours-to-months of execution | Vercel Workflow | Durable steps, no duration limit |
edge is deliberately absent: there's no row here where it's the better answer for new code.
Fluid Compute is the execution model for Vercel Functions — enabled by default for new projects since April 23, 2025, and available for the Node.js, Python, Bun, Rust, and Edge runtimes. Enable it explicitly per-deployment with {"fluid": true} in vercel.json, or project-wide in Settings → Functions.
Long-duration, large-function, and container-image support all depend on it.
Key behaviors:
SIGTERM before termination (see below).| Type | Memory / CPU | Use |
|---|---|---|
| Standard (default) | 2 GB / 1 vCPU | Predictable performance for production workloads |
| Performance | 4 GB / 2 vCPU | Latency-sensitive applications and SSR workloads |
vercel.json — setting it there produces a build-time warning. Set it in the dashboard instead: Settings → Functions → Advanced Settings → Function CPU, then redeploy. (The memory key still exists for legacy non-Fluid deployments, which is why you will find older examples using it.)Function code (export const maxDuration) → vercel.json → dashboard → Fluid defaults. Later entries lose.
waitUntilwaitUntil takes a Promise, not a callback. Passing a function does nothing — a common and silent bug.
after (equivalent)Request cancellation is opt-in, per path. With it enabled, a client disconnect aborts request.signal and terminates the function — anything not wrapped in waitUntil/after is lost, which is exactly why it is not on by default.
With Fluid Compute (default), per Vercel's limits:
| Plan | Default | Maximum | Extended maximum |
|---|---|---|---|
| Hobby | 300s (5 min) | 300s (5 min) | — |
| Pro | 300s (5 min) | 800s | 1800s (30 min) — Beta |
| Enterprise | 300s (5 min) | 800s | 1800s (30 min) — Beta |
The 800s maximum is generally available on Pro and Enterprise. The 1800s extended maximum is in beta. Exceeding the limit returns 504 FUNCTION_INVOCATION_TIMEOUT.
Hobby's default and maximum are the same 300s — there is no headroom to raise, and no extended duration. Setting maxDuration above 300s on Hobby does nothing; upgrade to Pro.
maxDurationFor other frameworks and runtimes — Next.js < 13.5, Rust, Go, Python, Ruby — use vercel.json:
Glob order matters, and Next.js projects using src/ must prefix paths with src/. For Python frameworks, key on the resolved entrypoint (app/main.py), not an /api route.
To change the project-wide default: Settings → Functions → Function Max Duration.
Pro and Enterprise teams can run individual functions for up to 1800s. Requirements, all of which are load-bearing:
vercel.json for that function. Project-level defaults above 800s are not supported during the beta — raising the dashboard default will not get you to 1800s.nodejs20.x, nodejs22.x, nodejs24.x, Bun 1.x and 1.4.x, python3.12, python3.13, python3.14.Over HTTP/2, Vercel sends connection-level PING frames while the response is idle. HTTP/1.1 has no equivalent, so HTTP/1.1 clients and intermediate proxies may still close an idle connection long before 30 minutes elapse. For any long-running handler, stream progress or heartbeat data while the work runs rather than going silent and emitting one payload at the end.
Use getDeadline() to find out how much time is actually left and bail out cleanly:
Active CPU pricing is what makes this viable: a 25-minute function that spends 24 minutes awaiting an LLM bills almost no Active CPU, only Provisioned Memory for the instance while the request is in flight.
Do not chain functions, self-invoke, or poll to fake durability. Use Vercel Workflow, which pauses, resumes, and keeps state for minutes to months with no duration limit, plus automatic retries and crash safety. Rough guide:
maxDurationworkflow skill)Workflow steps themselves support extended function durations, so a single step can also run up to 30 minutes.
| Runtime | Uncompressed bundle limit |
|---|---|
| Node.js, Bun, Rust, Go | 250 MB (includes runtime layers) |
| Python | 500 MB |
| Edge runtime | 1 MB Hobby / 2 MB Pro / 4 MB Enterprise, after gzip |
Blowing the limit fails the build with Serverless Function has exceeded the unzipped maximum size of 250 MB.
Large functions raise the uncompressed bundle ceiling to 5 GB. This is what makes Python data/AI libraries, model weights, browser automation (Playwright/Puppeteer), image/video processing, and big backend apps deployable as Functions.
VERCEL_SUPPORT_LARGE_FUNCTIONS environment variable, then redeploy:The environment variable always takes precedence over the project default, in both directions.
A 5 GB function still costs you cold-start time. Trim before you opt in:
In vercel.json (not supported in Next.js — see below):
includeFiles/excludeFiles — use outputFileTracingIncludes / outputFileTracingExcludes in next.config.js instead.import(), and check for a package in dependencies that belongs in devDependencies.Bundle size is not payload size. The request or response body of a Function is capped at 4.5 MB; exceeding it returns 413 FUNCTION_PAYLOAD_TOO_LARGE. For larger data:
Vercel Functions run OCI-compatible container images. This is first-class Docker support: bring a Dockerfile, get an autoscaling function with scale-to-zero and Active CPU pricing. It is not a VM or a long-lived server.
Create Dockerfile.vercel (or Containerfile.vercel) at the project root. Vercel detects it automatically and adds a rewrite routing all traffic to the image.
Deploy with vercel deploy or a Git push. During the build, the image is built and pushed to Vercel Container Registry (VCR).
PORT environment variable in project settings. A container that doesn't listen gets no traffic.SIGTERM with a 30-second grace period on scale-down (regular functions get 500 ms). Use it to drain.stdout/stderr are broadcast to all inflight requests of the instance, so correlate with your own request IDs.vercel dev runs the image and requires the docker CLI plus a running daemon.Use Services to deploy several frontends/backends in one project, containerized or not. Set runtime: "container" on any service you want built as an image; entrypoint points at the Dockerfile relative to that service's root.
Services are internal by default — without a top-level rewrite, nothing is publicly routable. When services is present, build/runtime keys (functions, buildCommand, installCommand, outputDirectory, framework) move into the service and are no longer valid at the top level.
Registry limits: 500 MB per compressed layer, 15 GB total image size, 4 MB manifest, 1 MB config blob. Layers must be gzip or zstd compressed — uncompressed OCI layers are rejected. Repositories per project: 10 (Hobby) / 1,000 (Pro) / 5,000 (Enterprise). Storage is billed at $0.10 per GB.
Good fits: Go, Rust, Ruby, PHP, or other backends; apps needing system libraries like FFmpeg or Chromium; frameworks outside Vercel's auto-detection; guaranteed build/runtime parity across environments.
Poor fits: anything that must hold state in-process, keep a daemon alive between requests, or run background work independent of a request. Reach for Workflow, Queues, or Cron for those.
If your framework is already auto-detected and you have no system-library needs, the standard build is simpler and faster — a Dockerfile is not an upgrade by default.
| Hobby | Pro | Enterprise | |
|---|---|---|---|
| Duration (default / max) | 300s / 300s | 300s / 800s | 300s / 800s |
| Extended duration (beta) | — | 1800s | 1800s |
| Memory / CPU | 2 GB / 1 vCPU, not configurable | Standard or Performance (4 GB / 2 vCPU) | Standard or Performance |
| Bundle size | 250 MB (500 MB Python), 5 GB with large functions beta | same | same |
| Concurrency | auto-scales to 30,000 | 30,000 | 100,000+ |
| Regions | single region | up to 3 | all |
| Edge code size (gzipped) | 1 MB | 2 MB | 4 MB |
| VCR repos per project | 10 | 1,000 | 5,000 |
| Request/response body | 4.5 MB | 4.5 MB | 4.5 MB |
Hobby function limits went up substantially with Fluid Compute, and stale 10s/60s numbers are a common source of bad advice:
Zero-config streaming on the default Node.js runtime, including Server-Sent Events (SSE). Essential for AI applications.
You do NOT need
runtime = 'edge'for streaming or SSE. Streaming responses (ReadableStream,text/event-stream) work on the default Node.js runtime — this is the single most common reason people wrongly reach for Edge. Stay on Node.js (Fluid Compute) so you keep full Node.js APIs, npm packages, and longer durations; Edge offers no streaming advantage and caps you at 25s to first byte.
For AI streaming, use the AI SDK's toUIMessageStreamResponse() (for chat UIs with useChat) which handles SSE formatting automatically.
Vercel Functions can hold open bidirectional WebSocket connections — use them for realtime features like interactive AI streaming, chat, and collaborative apps. There is no separate WebSocket-server product and no third-party service (Pusher, Ably, etc.) required — it runs on Vercel Functions directly. Requires Fluid Compute, which is the default for new projects.
How it works: a WebSocket starts as an HTTP GET with an Upgrade header, so it passes through the same Routing Middleware, rewrites, Firewall rules, and rate limits as any other request. After the upgrade, the connection is pinned to a single function instance for its lifetime; Fluid Compute lets one instance serve many concurrent connections. Active CPU pricing means you're billed while processing messages, not for idle open connections — the same limits and pricing as other Function invocations apply.
ws (no extra config)WebSockets work like any distributed WebSocket server — export an http.Server and use a library such as ws:
Higher-level realtime libraries like Socket.IO work too. Configure the client to use the WebSocket transport directly — Socket.IO defaults to HTTP long-polling, which won't work:
Express, Hono, and Nitro (including Nuxt, via native WebSocket support) serve WebSockets the same way — export the HTTP server. Python frameworks work too: FastAPI handles the upgrade natively, and python-socketio is protocol-compatible with the JS Socket.IO client.
Next.js doesn't expose an API for handling WebSocket upgrades. Use experimental_upgradeWebSocket() from @vercel/functions inside a route handler:
Schedule function invocations via vercel.json:
The cron endpoint receives a normal HTTP request. Verify it's from Vercel:
vercel.ts is the recommended way to configure a project — full TypeScript types, dynamic logic, and env access via @vercel/config. vercel.json remains fully supported. Legacy now.json support ended March 31, 2026; rename it to vercel.json (no content changes required).
The vercel.json equivalent:
What you cannot put here:
memory — with Fluid Compute (the default), set it in the dashboard; Pro/Enterprise only, and vercel.json warns at build timeruntime: "edge" is accepted here, but prefer leaving it out — see Prefer Node.js over the Edge runtime.
waitUntil given a callback: it takes a Promise. waitUntil(fn()), never waitUntil(fn) or waitUntil(async () => {}) — the latter silently does nothing@neondatabase/serverless)maxDuration (800s Pro/Ent, 1800s in beta), or move to Workflow for anything longerVERCEL_SUPPORT_LARGE_FUNCTIONS=1413 FUNCTION_PAYLOAD_TOO_LARGE) — use Blob client uploads or streaming, not a bigger functionmemory in vercel.json: with Fluid Compute enabled this is not the place for it and the build warns — set it in the dashboardvercel env pull for local devwaitUntil, getDeadline, SIGTERM, cancellation