Update to Vercel
Snapshot Oct 6, 2026 · 18:03 UTC · version 0.54.1
Collection source: downloaded plugin package. These snapshots do not have a confirmed matching collection source. Differences in file lists alone do not establish changes to the package.
Payment or plan references changed
Instruction wording changed from “Serverless Functions, Edge Functions, Fluid Compute, streaming, Cron Jobs, and runtime configuration. ” to “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. ”. 496 additional added or edited lines are in the evidence.
Observed in published text. Live prices and checkout terms have not been verified by this change.
Product description
Serverless Functions, Edge Functions, Fluid Compute, streaming, Cron Jobs, and runtime configuration.
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.
Skill instructions
Serverless Functions, Edge Functions, Fluid Compute, streaming, Cron Jobs, and runtime configuration. Use when configuring, debugging, or optimizing server-side code running on Vercel. sitemap: "https://vercel.com/sitemap/docs.xml" ...
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 ...
Compare saved observations
Download comparison JSONFull technical diff · 2 changed fields
changed /description
"Vercel Functions expert guidance — Serverless Functions, Edge Functions, Fluid Compute, streaming, Cron Jobs, and runtime configuration. Use when configuring, debugging, or optimizing server-side code running on Vercel."
"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."
changed /skill_md_contents
"---\nname: vercel-functions\ndescription: Vercel Functions expert guidance — Serverless Functions, Edge Functions, Fluid Compute, streaming, Cron Jobs, and runtime configuration. Use when configuring, debugging, or optimizing server-side code running on Vercel.\nmetadata:\n priority: 8\n docs:\n - \"https://vercel.com/docs/functions\"\n - \"https://vercel.com/docs/functions/runtimes\"\n - \"https://vercel.com/docs/functions/websockets\"\n sitemap: \"https://vercel.com/sitemap/docs.xml\"\n pathPatterns:\n - 'api/**/*.*'\n - 'pages/api/**'\n - 'src/pages/api/**'\n - 'app/**/route.*'\n - 'src/app/**/route.*'\n - 'apps/*/api/**/*.*'\n - 'apps/*/app/**/route.*'\n - 'apps/*/src/app/**/route.*'\n - 'apps/*/pages/api/**'\n - 'vercel.json'\n - 'apps/*/vercel.json'\n bashPatterns:\n - '\\bvercel\\s+dev\\b'\n - '\\bvercel\\s+logs\\b'\n importPatterns:\n - 'ws'\n - 'socket.io'\n - 'socket.io-client'\n promptSignals:\n phrases:\n - \"websocket\"\n - \"websockets\"\n - \"web socket\"\n - \"socket.io\"\n # Polling is the classic technique people reach for when they think Vercel\n # lacks websockets. We intentionally do NOT trigger on named third-party\n # services (Pusher, PubNub, Ably) — those are deliberate choices, not a\n # signal that someone is working around a missing feature.\n - \"long polling\"\n - \"long-polling\"\n allOf: []\n anyOf:\n - \"realtime\"\n - \"bidirectional\"\n - \"ws server\"\n - \"polling\"\n - \"server-sent events\"\n noneOf: []\n minScore: 6\n---\n\n# Vercel Functions\n\nYou are an expert in Vercel Functions — the compute layer of the Vercel platform.\n\n## Function Types\n\n### Serverless Functions (Node.js)\n- Full Node.js runtime, all npm packages available\n- Default for Next.js API routes, Server Actions, Server Components\n- Cold starts: 800ms–2.5s (with DB connections)\n- Max duration: 10s (Hobby), 300s (Pro default), 800s (Fluid Compute Pro/Enterprise)\n\n```ts\n// app/api/hello/route.ts\nexport async function GET() {\n return Response.json({ message: 'Hello from Node.js' })\n}\n```\n\n### Edge Functions (V8 Isolates)\n- Lightweight V8 runtime, Web Standard APIs only\n- Ultra-low cold starts (<1ms globally)\n- Limited API surface (no full Node.js)\n- Best for: auth checks, redirects, A/B testing, simple transformations\n\n```ts\n// app/api/hello/route.ts\nexport const runtime = 'edge'\n\nexport async function GET() {\n return new Response('Hello from the Edge')\n}\n```\n\n### Bun Runtime (Public Beta)\n\nAdd `\"bunVersion\": \"1.x\"` to `vercel.json` to run Node.js functions on Bun instead. ~28% lower latency for CPU-bound workloads. Supports Next.js, Express, Hono, Nitro.\n\n### Rust Runtime (Public Beta)\n\nRust 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.\n\n### Node.js 24 LTS\n\nNode.js 24 LTS is now GA on Vercel for both builds and functions. Features V8 13.6, global `URLPattern`, Undici v7 for faster `fetch()`, and npm v11.\n\n### Choosing Runtime\n\n| Need | Runtime | Why |\n|------|---------|-----|\n| Full Node.js APIs, npm packages | `nodejs` | Full compatibility |\n| Lower latency, CPU-bound work | `nodejs` + Bun | ~28% latency reduction |\n| Ultra-low latency, simple logic | `edge` | <1ms cold start, global |\n| Database connections, heavy deps | `nodejs` | Edge lacks full Node.js |\n| Auth/redirect at the edge | `edge` | Fastest response |\n| AI streaming | Either | Both support streaming |\n| Systems-level performance | `rust` (beta) | Native speed, Fluid Compute |\n\n## Fluid Compute\n\nFluid Compute is the unified execution model for all Vercel Functions (both Node.js and Edge).\n\nKey benefits:\n- **Optimized concurrency**: Multiple invocations on a single instance — up to 85% cost reduction for high-concurrency workloads\n- **Extended durations**: Default 300s for all plans; up to 800s on Pro/Enterprise\n- **Active CPU pricing**: Charges only while CPU is actively working, not during idle/await time. Enabled by default for all plans. Memory-only periods billed at a significantly lower rate.\n- **Background processing**: `waitUntil` / `after` for post-response tasks\n- **Dynamic scaling**: Automatic during traffic spikes\n- **Bytecode caching**: Reduces cold starts via Rust-based runtime with pre-compiled function code\n- **Multi-region failover**: Default for Enterprise when Fluid is activated\n\n### Instance Sizes\n\n| Size | CPU | Memory |\n|------|-----|--------|\n| Standard (default) | 1 vCPU | 2 GB |\n| Performance | 2 vCPU | 4 GB |\n\nHobby projects use Standard CPU. The Basic CPU instance has been removed.\n\n### Background Processing with `waitUntil`\n\n```ts\n// Continue work after sending response\nimport { waitUntil } from '@vercel/functions'\n\nexport async function POST(req: Request) {\n const data = await req.json()\n\n // Send response immediately\n const response = Response.json({ received: true })\n\n // Continue processing in background\n waitUntil(async () => {\n await processAnalytics(data)\n await sendNotification(data)\n })\n\n return response\n}\n```\n\n### Next.js `after` (equivalent)\n\n```ts\nimport { after } from 'next/server'\n\nexport async function POST(req: Request) {\n const data = await req.json()\n\n after(async () => {\n await logToAnalytics(data)\n })\n\n return Response.json({ ok: true })\n}\n```\n\n## Streaming\n\nZero-config streaming on **both runtimes**, including Server-Sent Events (SSE). Essential for AI applications.\n\n> **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 a common misconception. Stay on Node.js (Fluid Compute) so you keep full Node.js APIs, npm packages, and longer durations; Edge offers no streaming advantage here.\n\n```ts\nexport async function POST(req: Request) {\n const encoder = new TextEncoder()\n const stream = new ReadableStream({\n async start(controller) {\n for (const chunk of data) {\n controller.enqueue(encoder.encode(chunk))\n await new Promise(r => setTimeout(r, 100))\n }\n controller.close()\n },\n })\n\n return new Response(stream, {\n headers: { 'Content-Type': 'text/event-stream' },\n })\n}\n```\n\nFor AI streaming, use the AI SDK's `toUIMessageStreamResponse()` (for chat UIs with `useChat`) which handles SSE formatting automatically.\n\n## WebSockets\n\nVercel 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.\n\n**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.\n\n### `ws` (no extra config)\n\nWebSockets work like any distributed WebSocket server — export an `http.Server` and use a library such as `ws`:\n\n```ts\n// api/ws.ts\nimport http from 'http'\nimport { WebSocketServer } from 'ws'\n\nconst server = http.createServer()\nconst wss = new WebSocketServer({ server })\n\nwss.on('connection', (ws) => {\n ws.on('message', (data) => ws.send(data)) // echo\n})\n\nexport default server\n```\n\n### Socket.IO\n\nHigher-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:\n\n```ts\n// api/socket-io.ts\nimport http from 'http'\nimport { Server } from 'socket.io'\n\nconst server = http.createServer()\nconst io = new Server(server)\n\nio.on('connection', (socket) => {\n socket.on('message', (data) => socket.send(data))\n})\n\nexport default server\n```\n\n```ts\n// client.ts\nimport { io } from 'socket.io-client'\n\nconst socket = io('https://your-domain.com', {\n // Socket.IO appends /socket.io, so the full path becomes /api/socket-io/socket.io\n path: '/api/socket-io/socket.io',\n transports: ['websocket'], // required — Socket.IO defaults to HTTP long-polling\n})\n```\n\nExpress, 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.\n\n### Next.js\n\nNext.js doesn't expose an API for handling WebSocket upgrades. Use `experimental_upgradeWebSocket()` from `@vercel/functions` inside a route handler:\n\n```ts\n// app/api/ws/route.ts\nimport { experimental_upgradeWebSocket, type WebSocketData } from '@vercel/functions'\n\nexport async function GET() {\n return experimental_upgradeWebSocket((ws) => {\n ws.on('message', (data: WebSocketData) => ws.send(data))\n })\n}\n```\n\n### Reconnects and persistent state\n\n- **Connections close when the function reaches its max duration.** Clients must reconnect with backoff, then resubscribe to channels and reload any state they need.\n- **No instance affinity across connections.** A reconnect — or a new deployment — may land on a different instance, so never keep durable state, presence, rooms, or pub/sub coordination in memory. Use an external store such as [Redis from the Marketplace](https://vercel.com/marketplace/redis).\n\n```ts\n// client.ts — reconnect with exponential backoff\nlet socket: WebSocket\nlet delay = 1000\n\nfunction connect() {\n socket = new WebSocket('wss://your-domain.com/api/ws')\n socket.addEventListener('open', () => { delay = 1000 })\n socket.addEventListener('message', (e) => console.log(e.data))\n socket.addEventListener('close', () => {\n setTimeout(connect, delay)\n delay = Math.min(delay * 2, 30000)\n })\n}\n\nconnect()\n```\n\n## Cron Jobs\n\nSchedule function invocations via `vercel.json`:\n\n```json\n{\n \"crons\": [\n {\n \"path\": \"/api/daily-report\",\n \"schedule\": \"0 8 * * *\"\n },\n {\n \"path\": \"/api/cleanup\",\n \"schedule\": \"0 */6 * * *\"\n }\n ]\n}\n```\n\nThe cron endpoint receives a normal HTTP request. Verify it's from Vercel:\n\n```ts\nexport async function GET(req: Request) {\n const authHeader = req.headers.get('authorization')\n if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {\n return new Response('Unauthorized', { status: 401 })\n }\n // Do scheduled work\n return Response.json({ ok: true })\n}\n```\n\n## Configuration via vercel.json\n\n**Deprecation notice**: Support for the legacy `now.json` config file will be removed on **March 31, 2026**. Rename `now.json` to `vercel.json` (no content changes required).\n\n```json\n{\n \"functions\": {\n \"app/api/heavy/**\": {\n \"maxDuration\": 300,\n \"memory\": 1024\n },\n \"app/api/edge/**\": {\n \"runtime\": \"edge\"\n }\n }\n}\n```\n\n## Timeout Limits\n\nAll plans now default to 300s execution time with Fluid Compute.\n\n| Plan | Default | Max |\n|------|---------|-----|\n| Hobby | 300s | 300s |\n| Pro | 300s | 800s |\n| Enterprise | 300s | 800s |\n\n## Common Pitfalls\n\n1. **Cold starts with DB connections**: Use connection pooling (e.g., Neon's `@neondatabase/serverless`)\n2. **Edge limitations**: No `fs`, no native modules, limited `crypto` — use Node.js runtime if needed\n3. **Timeout exceeded**: Use Fluid Compute for long-running tasks, or Workflow DevKit for very long processes\n4. **Bundle size**: Functions support up to 5 GB package size on Fluid Compute (up from 250 MB); request bodies up to 100 MB (up from 4.5 MB)\n5. **Environment variables**: Available in all functions automatically; use `vercel env pull` for local dev\n\n## Function Runtime Diagnostics\n\n### Timeout Diagnostics\n\n```\n504 Gateway Timeout?\n├─ All plans default to 300s with Fluid Compute\n├─ Pro/Enterprise: configurable up to 800s\n├─ Long-running task?\n│ ├─ Under 5 min → Use Fluid Compute with streaming\n│ ├─ Up to 15 min → Use Vercel Functions with `maxDuration` in vercel.json\n│ └─ Hours/days → Use Workflow DevKit (DurableAgent or workflow steps)\n└─ DB query slow? → Add connection pooling, check cold start, use Edge Config\n```\n\n### 500 Error Diagnostics\n\n```\n500 Internal Server Error?\n├─ Check Vercel Runtime Logs (Dashboard → Deployments → Functions tab)\n├─ Missing env vars? → Compare `.env.local` against Vercel dashboard settings\n├─ Import error? → Verify package is in `dependencies`, not `devDependencies`\n└─ Uncaught exception? → Wrap handler in try/catch, use `after()` for error reporting\n```\n\n### Invocation Failure Diagnostics\n\n```\n\"FUNCTION_INVOCATION_FAILED\"?\n├─ Memory exceeded? → Increase `memory` in vercel.json (up to 3008 MB on Pro)\n├─ Crashed during init? → Check top-level await or heavy imports at module scope\n└─ Edge Function crash? → Check for Node.js APIs not available in Edge runtime\n```\n\n### Cold Start Diagnostics\n\n```\nCold start latency > 1s?\n├─ Using Node.js runtime? → Consider Edge Functions for latency-sensitive routes\n├─ Large function bundle? → Audit imports, use dynamic imports, tree-shake\n├─ DB connection in cold start? → Use connection pooling (Neon serverless driver)\n└─ Enable Fluid Compute to reuse warm instances across requests\n```\n\n### Edge Function Timeout Diagnostics\n\n```\n\"EDGE_FUNCTION_INVOCATION_TIMEOUT\"?\n├─ Edge Functions have 25s hard limit (not configurable)\n├─ Move heavy computation to Node.js Serverless Functions\n└─ Use streaming to start response early, process in background with `waitUntil`\n```\n\n## Official Documentation\n\n- [Vercel Functions](https://vercel.com/docs/functions)\n- [Serverless Functions](https://vercel.com/docs/functions)\n- [Edge Functions](https://vercel.com/docs/functions)\n- [Fluid Compute](https://vercel.com/docs/fluid-compute)\n- [Streaming](https://vercel.com/docs/functions/streaming)\n- [WebSockets](https://vercel.com/docs/functions/websockets)\n- [Cron Jobs](https://vercel.com/docs/cron-jobs)\n- [GitHub: Vercel](https://github.com/vercel/vercel)\n""---\nname: vercel-functions\ndescription: 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.\nsummary: \"Vercel Functions run on Fluid Compute with Node.js as the default runtime — strongly prefer it over `runtime = 'edge'` (Vercel recommends migrating off Edge, and Next.js 16.3+ no longer supports it). Duration: 300s default on every plan including Hobby, 800s max on Pro/Enterprise, 1800s (30 min) per-function in the extended beta; beyond that use Vercel Workflow. Bundles: 250 MB standard (500 MB Python), 5 GB via the large functions beta (`VERCEL_SUPPORT_LARGE_FUNCTIONS=1`). Request/response bodies cap at 4.5 MB. Memory is dashboard-only (Standard 2 GB/1 vCPU, Performance 4 GB/2 vCPU; Hobby fixed). Docker works: add `Dockerfile.vercel` to run an OCI image as an autoscaling, stateless, scale-to-zero Function.\"\nmetadata:\n priority: 8\n docs:\n - \"https://vercel.com/docs/functions\"\n - \"https://vercel.com/docs/functions/runtimes\"\n - \"https://vercel.com/docs/functions/limitations\"\n - \"https://vercel.com/docs/functions/configuring-functions/duration\"\n - \"https://vercel.com/docs/functions/container-images\"\n - \"https://vercel.com/docs/fluid-compute\"\n - \"https://vercel.com/docs/functions/websockets\"\n sitemap: \"https://vercel.com/sitemap.xml\"\n pathPatterns:\n - 'api/**/*.*'\n - 'pages/api/**'\n - 'src/pages/api/**'\n - 'app/**/route.*'\n - 'src/app/**/route.*'\n - 'apps/*/api/**/*.*'\n - 'apps/*/app/**/route.*'\n - 'apps/*/src/app/**/route.*'\n - 'apps/*/pages/api/**'\n - 'vercel.json'\n - 'apps/*/vercel.json'\n - 'vercel.ts'\n - 'apps/*/vercel.ts'\n # Vercel builds `Dockerfile.vercel` / `Containerfile.vercel` into a\n # container-image Function, so these are Functions config, not generic Docker.\n - 'Dockerfile.vercel'\n - '*/Dockerfile.vercel'\n - 'Containerfile.vercel'\n - '*/Containerfile.vercel'\n bashPatterns:\n - '\\bvercel\\s+dev\\b'\n - '\\bvercel\\s+logs\\b'\n - '\\bvercel\\s+vcr\\b'\n importPatterns:\n - 'ws'\n - 'socket.io'\n - 'socket.io-client'\n promptSignals:\n phrases:\n - \"websocket\"\n - \"websockets\"\n - \"web socket\"\n - \"socket.io\"\n # Polling is the classic technique people reach for when they think Vercel\n # lacks websockets. We intentionally do NOT trigger on named third-party\n # services (Pusher, PubNub, Ably) — those are deliberate choices, not a\n # signal that someone is working around a missing feature.\n - \"long polling\"\n - \"long-polling\"\n # Duration: people hit the ceiling and ask about it in these words.\n - \"maxduration\"\n - \"max duration\"\n - \"function timeout\"\n - \"function times out\"\n - \"long-running function\"\n - \"long running function\"\n - \"504\"\n # Bundle size: the 250 MB error message is the usual entry point.\n - \"unzipped maximum size\"\n - \"250mb\"\n - \"250 mb\"\n - \"large function\"\n - \"bundle size limit\"\n # Containers: Dockerfile.vercel is a Functions feature, not just packaging.\n - \"dockerfile\"\n - \"container image\"\n - \"container registry\"\n - \"runtime edge\"\n - \"edge runtime\"\n allOf:\n - [\"docker\", \"vercel\"]\n - [\"hobby\", \"limit\"]\n anyOf:\n - \"realtime\"\n - \"bidirectional\"\n - \"ws server\"\n - \"polling\"\n - \"server-sent events\"\n - \"fluid compute\"\n - \"active cpu\"\n noneOf: []\n minScore: 6\nvalidate:\n -\n pattern: export\\s+default\\s+function\n message: 'Use named exports (GET, POST, PUT, DELETE) instead of default export for route handlers'\n severity: error\n # Skip on App Router page / layout / loading / error / not-found / sitemap / template / default files,\n # which require a default export by Next.js convention. Detected via the 'use client' directive,\n # an App Router config export (metadata, dynamic, revalidate, fetchCache, runtime), an `export default\n # function` whose name matches an App Router file (Page / Layout / Loading / etc.), or any JSX\n # element with a capitalised component tag — all signals that the file is a page-style file rather\n # than a route handler. See anthropics/claude-code#54989.\n skipIfFileContains: \"(?:^|\\\\n)\\\\s*['\\\"]use\\\\s+client['\\\"]|export\\\\s+const\\\\s+(?:metadata|dynamic|revalidate|fetchCache|runtime)\\\\b|export\\\\s+default\\\\s+(?:async\\\\s+)?function\\\\s+\\\\w*(?:Page|Layout|Loading|Error|NotFound|Sitemap|Template|Default|sitemap|robots|opengraph|manifest)\\\\b|<[A-Z][A-Za-z0-9]*|\\\\{\\\\s*children\\\\s*[,}:]|MetadataRoute\\\\.|from\\\\s+['\\\"]next/(?:font|image|link|navigation|headers|cookies)['\\\"]\"\n -\n pattern: NextApiRequest|NextApiResponse\n message: 'NextApiRequest/NextApiResponse are Pages Router types — use Web API Request/Response'\n severity: error\n -\n # Vercel's docs recommend migrating off the Edge runtime, and Next.js 16.3+\n # doesn't support it. Surfaced as a recommendation rather than an error:\n # existing Edge functions still work, so this is a nudge, not a blocker.\n # Matches `\"runtime\": \"edge\"` in vercel.json too.\n pattern: 'runtime[''\"]?\\s*[=:]\\s*[''\"]edge[''\"]'\n message: 'Consider dropping `runtime = \"edge\"`. Vercel recommends migrating from Edge to Node.js, and Next.js 16.3+ no longer supports it. Node.js on Fluid Compute runs in the same regions at the same price with full Node.js APIs, longer durations, and larger bundles.'\n severity: recommended\n -\n pattern: 'from\\s+[''\"](openai|@anthropic-ai/sdk|anthropic)[''\"]|new\\s+(OpenAI|Anthropic)\\('\n message: 'Direct AI provider SDK detected in route handler. Use the Vercel AI SDK for streaming, tools, and provider abstraction.'\n severity: recommended\n upgradeToSkill: ai-sdk\n upgradeWhy: 'Replace vendor-locked provider SDKs with @ai-sdk/openai or @ai-sdk/anthropic for unified streaming and tool support.'\n skipIfFileContains: '@ai-sdk/|from\\s+[''\"](ai)[''\"]|import.*from\\s+[''\"](ai)[''\"]|streamText|generateText'\n -\n pattern: 'setTimeout\\s*\\(|setInterval\\s*\\(|await\\s+new\\s+Promise\\s*\\([^)]*setTimeout'\n message: 'Long-running or polling logic detected in a serverless handler. Functions have execution time limits.'\n severity: recommended\n upgradeToSkill: workflow\n upgradeWhy: 'Move delayed/polling logic to Vercel Workflow for durable execution with pause, resume, retries, and crash safety.'\n skipIfFileContains: 'use workflow|use step'\n -\n pattern: 'writeFile(Sync)?\\(|createWriteStream\\(|from\\s+[''\"](multer|formidable)[''\"]|fs\\.writeFile'\n message: 'Local filesystem write detected. Serverless functions have ephemeral, read-only filesystems.'\n severity: error\n upgradeToSkill: vercel-storage\n upgradeWhy: 'Replace local filesystem writes with Vercel Blob, Neon, or Upstash for persistent, platform-native storage.'\n skipIfFileContains: '@vercel/blob|@upstash/|@neondatabase/'\n -\n pattern: 'export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE)\\b'\n message: 'Route handler has no observability instrumentation. Add logging and error tracking for production debugging.'\n severity: warn\n skipIfFileContains: 'console\\.error|logger\\.|captureException|Sentry|@vercel/otel|withTracing'\n -\n pattern: 'from\\s+[''\"\"](lru-cache|node-cache|memory-cache)[''\"\"]|new\\s+(LRUCache|NodeCache|Map)\\(\\s*\\).*cache'\n message: 'In-process memory cache detected in serverless function. Process memory is not shared across invocations.'\n severity: recommended\n upgradeToSkill: runtime-cache\n upgradeWhy: 'Replace in-process caches with Vercel Runtime Cache (getCache from @vercel/functions) for region-aware caching that persists across invocations.'\n skipIfFileContains: 'getCache|from\\s+[''\"\"]\\@vercel/functions[''\"\"]'\n -\n pattern: 'maxRetries\\s*[=:]|retryCount\\s*[=:]|retry\\s*\\(\\s*|for\\s*\\([^)]*retry|while\\s*\\([^)]*retry'\n message: 'Manual retry logic detected. Use Vercel Workflow SDK for automatic retries with durable execution.'\n severity: recommended\n upgradeToSkill: workflow\n upgradeWhy: 'Replace manual retry loops with Workflow SDK steps that provide automatic retries, crash safety, and observability.'\n skipIfFileContains: 'use workflow|use step|from\\s+[''\"\"](workflow)[''\"\"]'\n -\n pattern: 'from\\s+[''\"](express)[''\"\"]|require\\s*\\(\\s*[''\"](express)[''\"\"\\)]'\n message: 'Express.js detected in a Vercel project. Vercel Functions use the Web Request/Response API — Express middleware, req/res, and app.listen() do not work in serverless.'\n severity: recommended\n upgradeToSkill: vercel-functions\n upgradeWhy: 'Replace Express with Next.js route handlers (export async function GET/POST) or Vercel Functions using the Web Request/Response API.'\n skipIfFileContains: 'export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE)|from\\s+[''\"\"](next/server|@vercel/functions)[''\"\"]'\nretrieval:\n aliases:\n - serverless functions\n - api routes\n - edge functions\n - lambda\n - websockets\n - socket.io\n - docker\n - dockerfile\n - container images\n - function timeout\n - max duration\n - bundle size\n - hobby limits\n intents:\n - create serverless function\n - configure function runtime\n - optimize cold starts\n - add api route\n - serve a websocket connection\n - run a function for longer than 5 minutes\n - deploy a dockerfile\n - fix a function that exceeds the bundle size limit\n - check plan limits for functions\n entities:\n - Serverless Functions\n - Edge Functions\n - Fluid Compute\n - Long-duration functions\n - Large functions\n - Container Images\n - Vercel Container Registry\n - streaming\n - WebSockets\n - Cron Jobs\nchainTo:\n -\n pattern: 'from\\s+[''\\\"](openai|@anthropic-ai/sdk|anthropic)[''\"]|new\\s+(OpenAI|Anthropic)\\('\n targetSkill: ai-sdk\n message: 'Direct AI provider SDK in route handler — loading AI SDK guidance for unified streaming and tool support.'\n -\n pattern: 'setTimeout\\s*\\(|setInterval\\s*\\(|await\\s+new\\s+Promise\\s*\\([^)]*setTimeout'\n targetSkill: workflow\n message: 'Long-running or polling logic in serverless handler — loading Workflow SDK for durable execution.'\n -\n pattern: 'writeFile(Sync)?\\(|createWriteStream\\(|from\\s+[''\\\"](multer|formidable)[''\"]|fs\\.writeFile'\n targetSkill: vercel-storage\n message: 'Local filesystem write in serverless function — loading Vercel Storage guidance for platform-native persistence.'\n -\n pattern: 'from\\s+[''\"\"]@vercel/(postgres|kv)[''\"\"]'\n targetSkill: vercel-storage\n message: '@vercel/postgres and @vercel/kv are sunset — loading Vercel Storage guidance for Neon and Upstash migration.'\n -\n pattern: 'generateObject\\s*\\(|streamObject\\s*\\(|toDataStreamResponse|maxSteps\\b|CoreMessage\\b'\n targetSkill: ai-sdk\n message: 'Deprecated AI SDK v5 API detected — loading AI SDK guidance for migration.'\n -\n pattern: 'while\\s*\\(\\s*true\\s*\\)\\s*\\{|for\\s*\\(\\s*;\\s*;\\s*\\)\\s*\\{|setInterval\\s*\\(\\s*async'\n targetSkill: workflow\n message: 'Polling loop in serverless function detected — loading Workflow SDK for durable, crash-safe execution with pause/resume.'\n skipIfFileContains: \"use workflow|use step|from\\\\s+['\\\"]workflow['\\\"]\"\n -\n pattern: \"from\\\\s+['\\\"]express['\\\"]|require\\\\s*\\\\(\\\\s*['\\\"]express['\\\"]\"\n targetSkill: vercel-functions\n message: 'Express.js detected — loading Vercel Functions guidance for Web Request/Response API route handlers that replace Express middleware and routing.'\n skipIfFileContains: \"export\\\\s+(async\\\\s+)?function\\\\s+(GET|POST|PUT|PATCH|DELETE)\"\n -\n pattern: 'from\\s+[''\"\"](lru-cache|node-cache|memory-cache)[''\"\"]|new\\s+(LRUCache|NodeCache|Map)\\(\\s*\\).*cache'\n targetSkill: runtime-cache\n message: 'In-process memory cache in serverless function — loading Runtime Cache guidance for region-aware caching that persists across invocations.'\n skipIfFileContains: 'getCache|from\\s+[''\"\"]\\@vercel/functions[''\"\"]'\n -\n pattern: 'maxRetries\\s*[=:]|retryCount\\s*[=:]|retry\\s*\\(\\s*|for\\s*\\([^)]*retry|while\\s*\\([^)]*retry'\n targetSkill: workflow\n message: 'Manual retry logic in serverless handler — loading Workflow SDK guidance for automatic retries with durable execution.'\n skipIfFileContains: 'use workflow|use step|from\\s+[''\"\"](workflow)[''\"\"]'\n\n -\n pattern: 'import\\s*\\{[^}]*\\bmetric\\b[^}]*\\}\\s*from\\s*[''\"]@vercel/functions[''\"]'\n targetSkill: custom-metrics\n message: '@vercel/functions metric() detected — loading Custom Metrics guidance for naming, attributes, and vc metrics queries.'\n\n---\n\n# Vercel Functions\n\nYou are an expert in Vercel Functions — the compute layer of the Vercel platform.\n\n## Prefer Node.js over the Edge runtime\n\n**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.**\n\nThis 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.\n\nEverywhere 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.\n\n### The default to reach for\n\n```ts\n// app/api/hello/route.ts — no runtime export needed.\nexport async function GET() {\n return Response.json({ message: 'Hello from Node.js on Fluid Compute' })\n}\n```\n\nNode.js is the default. Omit `export const runtime` entirely rather than writing `export const runtime = 'nodejs'`.\n\n### Reasons people reach for Edge — and what to do instead\n\n| \"I need Edge because…\" | Reality | Do this instead |\n|---|---|---|\n| \"…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 |\n| \"…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 |\n| \"…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) |\n| \"…it's cheaper\" | Identical Active CPU pricing | Stay on Node.js |\n| \"…it has faster cold starts\" | Fluid Compute reuses warm instances across concurrent invocations and bytecode-caches Node 20+ in production | Stay on Node.js |\n| \"…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 |\n\n### What Edge actually costs you\n\n- No `fs`, no native modules, no `require()` — ESM only, and most npm packages with Node.js dependencies simply will not load\n- No `eval` / `new Function` / dynamic `WebAssembly.instantiate`\n- **Code size limit after gzip: 1 MB (Hobby), 2 MB (Pro), 4 MB (Enterprise)** — versus 250 MB uncompressed (up to 5 GB) on Node.js\n- Must begin sending a response within **25 seconds** (it may then stream for up to 300s). The 300s/800s/1800s duration limits below apply to the Node.js, Bun, and Python runtimes — **not** to Edge\n- No long-duration or large-function support of any kind\n\n### Migrating an existing Edge function\n\nWorth 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.\n\n1. Remove `export const runtime = 'edge'` (or `runtime: 'edge'` in `vercel.json` / the `config` object).\n2. Replace `next/server` Edge-only imports where applicable; the Web `Request`/`Response` handler signature is unchanged, so most route handlers need no other edit.\n3. If you pinned execution with the Edge-only `preferredRegion`, use `regions` in `vercel.json` instead.\n4. Confirm Fluid Compute is on (default since April 23, 2025) and redeploy.\n\nThere is no rollback story to plan for: Node.js is a superset of what the function could do on Edge.\n\n## Function Types\n\n### Node.js (the default)\n- Full Node.js runtime, all npm packages available\n- Default for Next.js route handlers, Server Actions, Server Components, and any file in `/api`\n- **Node.js 24 LTS is GA** for builds and functions (V8 13.6, global `URLPattern`, Undici v7, npm v11). **Node.js 20 is deprecated on October 1, 2026** — move off `nodejs20.x`\n- Duration: 300s default on every plan; 800s max on Pro/Enterprise; 1800s with the extended-duration beta\n\n### Bun\nAdd `\"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.\n\n### Python\nPython 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.\n\n### Rust\nRust functions run on Fluid Compute with HTTP streaming and Active CPU pricing. Official runtime (Beta) built on the `vercel_runtime` crate. Supports environment variables up to 64 KB.\n\n### Container images (Docker)\nAny OCI image via `Dockerfile.vercel`. See [Docker and Container Images](#docker-and-container-images) below.\n\n### Edge (legacy — not recommended)\nV8 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](#prefer-nodejs-over-the-edge-runtime).\n\n### Choosing a Runtime\n\n| Need | Runtime | Why |\n|------|---------|-----|\n| Anything not listed below | `nodejs` | The default, and correct nearly always |\n| Full Node.js APIs, npm packages | `nodejs` | Full compatibility |\n| AI streaming, SSE, WebSockets | `nodejs` | Zero-config streaming, long durations |\n| Lower latency, CPU-bound work | `nodejs` + Bun | ~28% latency reduction |\n| Database connections, heavy deps | `nodejs` | Pin `regions` next to the database |\n| Data/ML libraries, big model files | `nodejs` or `python` + large functions | Up to 5 GB bundles |\n| Systems-level performance | `rust` | Native speed on Fluid Compute |\n| Custom system libraries (FFmpeg, Chromium), Go/Ruby/PHP, unsupported frameworks | container image | Bring your own Dockerfile |\n| Auth, redirects, A/B tests before the cache | Routing Middleware | Runs on Node.js, framework-agnostic |\n| Hours-to-months of execution | Vercel Workflow | Durable steps, no duration limit |\n\n`edge` is deliberately absent: there's no row here where it's the better answer for new code.\n\n## Fluid Compute\n\nFluid 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.\n\nLong-duration, large-function, and container-image support all depend on it.\n\nKey behaviors:\n- **Optimized concurrency**: multiple invocations share one instance instead of one microVM per request. Vercel prioritizes idle existing resources before allocating new ones. Available on the Node.js and Python runtimes.\n- **Active CPU pricing**: you are billed for CPU time your code actually consumes, plus provisioned memory while requests are in flight, plus invocations. Waiting on I/O (AI models, DB queries) does not accrue Active CPU — which is what makes 30-minute functions affordable.\n- **Automatic cold start optimization**: function pre-warming plus **bytecode caching** on Node.js 20+. Bytecode caching applies to **production only** — not dev or preview, so don't benchmark cold starts in a preview deployment.\n- **Error isolation**: an uncaught exception or unhandled rejection is logged and in-flight requests are allowed to finish; one broken request will not crash its neighbors on the same instance.\n- **Cross-AZ and cross-region failover**: fails over to another availability zone in-region first, then to the next closest region.\n- **Graceful shutdown**: `SIGTERM` before termination (see below).\n\n### Instance Sizes (memory / CPU)\n\n| Type | Memory / CPU | Use |\n|------|--------------|-----|\n| Standard (default) | 2 GB / 1 vCPU | Predictable performance for production workloads |\n| Performance | 4 GB / 2 vCPU | Latency-sensitive applications and SSR workloads |\n\n- **With Fluid Compute enabled, memory cannot be set in `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.)\n- **Pro/Enterprise only.** Hobby always runs Standard (2 GB / 1 vCPU) and cannot configure it. The Basic instance has been removed.\n- More memory also means more CPU, which can *reduce* Active CPU billing for CPU-bound work by finishing sooner — but it raises Provisioned Memory cost while requests are in flight.\n- Projects created before 2019-11-08 may still sit on legacy sizes (1024 MB / 0.6 vCPU on Hobby, 3008 MB / 1.67 vCPU on Pro) until you pick a size in the dashboard.\n\n### Settings precedence\n\nFunction code (`export const maxDuration`) → `vercel.json` → dashboard → Fluid defaults. Later entries lose.\n\n### Background Processing with `waitUntil`\n\n`waitUntil` takes a **Promise**, not a callback. Passing a function does nothing — a common and silent bug.\n\n```ts\nimport { waitUntil } from '@vercel/functions'\n\nexport async function POST(req: Request) {\n const data = await req.json()\n\n // Correct: invoke the async work and hand over the promise.\n waitUntil(processAnalytics(data))\n\n // For several tasks, combine them:\n waitUntil(Promise.all([sendNotification(data), updateCache(data)]))\n\n return Response.json({ received: true })\n}\n```\n\n### Next.js `after` (equivalent)\n\n```ts\nimport { after } from 'next/server'\n\nexport async function POST(req: Request) {\n const data = await req.json()\n\n after(async () => {\n await logToAnalytics(data)\n })\n\n return Response.json({ ok: true })\n}\n```\n\n### Graceful shutdown and request cancellation\n\n```ts\n// Runs on scale-down. 500 ms to clean up (30 s for container images).\nprocess.on('SIGTERM', () => {\n // flush buffers, close pools\n})\n```\n\nRequest 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.\n\n```json filename=\"vercel.json\"\n{\n \"functions\": {\n \"api/*\": { \"supportsCancellation\": true }\n }\n}\n```\n\n```ts\nexport async function GET(request: Request) {\n // Pass the signal through so upstream work stops too.\n const res = await fetch('https://upstream.example.com', { signal: request.signal })\n return new Response(res.body, { status: res.status })\n}\n```\n\n## Duration and Long-Duration Functions\n\n### Duration limits\n\nWith Fluid Compute (default), per [Vercel's limits](https://vercel.com/docs/functions/limitations#max-duration):\n\n| Plan | Default | Maximum | Extended maximum |\n|------|---------|---------|------------------|\n| Hobby | 300s (5 min) | 300s (5 min) | — |\n| Pro | 300s (5 min) | 800s | 1800s (30 min) — Beta |\n| Enterprise | 300s (5 min) | 800s | 1800s (30 min) — Beta |\n\nThe 800s maximum is **generally available** on Pro and Enterprise. The 1800s extended maximum is **in beta**. Exceeding the limit returns `504 FUNCTION_INVOCATION_TIMEOUT`.\n\n**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.\n\n### Setting `maxDuration`\n\n```ts\n// app/api/report/route.ts — Next.js App Router (and Node.js, SvelteKit, Astro,\n// Nuxt, Remix via their own config). Value is in seconds.\nexport const maxDuration = 800\n\nexport async function POST(request: Request) {\n return Response.json({ ok: true })\n}\n```\n\nFor other frameworks and runtimes — Next.js < 13.5, Rust, Go, Python, Ruby — use `vercel.json`:\n\n```json\n{\n \"$schema\": \"https://openapi.vercel.sh/vercel.json\",\n \"functions\": {\n \"api/long-task.py\": { \"maxDuration\": 1800 }\n }\n}\n```\n\nGlob 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.\n\nTo change the project-wide default: Settings → Functions → **Function Max Duration**.\n\n### Extended max duration (30 minutes) — Beta\n\nPro and Enterprise teams can run individual functions for up to **1800s**. Requirements, all of which are load-bearing:\n\n- **Per-function configuration only.** Durations above 800s must be set in code or in `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.\n- **Supported runtimes only**: `nodejs20.x`, `nodejs22.x`, `nodejs24.x`, Bun `1.x` and `1.4.x`, `python3.12`, `python3.13`, `python3.14`.\n- **Fluid Compute must be enabled** (default for new projects).\n- **Secure Compute and Static IPs do not support durations above 800s** during the beta. If the project uses either, you are capped at 800s.\n\n```ts\n// app/api/long-task/route.ts\nexport const maxDuration = 1800 // 30 minutes\n\nexport async function POST(request: Request) {\n await doTheLongThing()\n return Response.json({ ok: true })\n}\n```\n\n### Keeping a long request alive\n\nOver 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.\n\nUse `getDeadline()` to find out how much time is actually left and bail out cleanly:\n\n```ts\nimport { getDeadline } from '@vercel/functions'\n\nconst deadline = getDeadline() // Date | undefined (undefined outside the Vercel Functions runtime)\nconst msRemaining = deadline ? deadline.getTime() - Date.now() : Infinity\n```\n\n### Cost of long functions\n\nActive 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.\n\n### When 30 minutes is not enough\n\nDo 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:\n\n- ≤ 300s → any plan, no configuration needed\n- 300–800s → Pro/Enterprise, set `maxDuration`\n- 800–1800s → Pro/Enterprise, extended-duration beta, per-function config\n- Beyond 30 min, or needs to survive a crash/deploy → Vercel Workflow (`workflow` skill)\n\nWorkflow steps themselves support extended function durations, so a single step can also run up to 30 minutes.\n\n## Large Functions (bundle size)\n\n### Standard limits\n\n| Runtime | Uncompressed bundle limit |\n|---------|---------------------------|\n| Node.js, Bun, Rust, Go | 250 MB (includes runtime layers) |\n| Python | 500 MB |\n| Edge runtime | 1 MB Hobby / 2 MB Pro / 4 MB Enterprise, **after gzip** |\n\nBlowing the limit fails the build with `Serverless Function has exceeded the unzipped maximum size of 250 MB`.\n\n### Large functions — Beta\n\nLarge 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.\n\n- **Runtimes**: Node.js, Bun, Python.\n- **Requires Fluid Compute with Active CPU** enabled (default for new projects).\n- **New projects are eligible by default.** Existing projects opt in with the `VERCEL_SUPPORT_LARGE_FUNCTIONS` environment variable, then redeploy:\n\n```bash\nvercel env add VERCEL_SUPPORT_LARGE_FUNCTIONS # value: 1 (use 0 to disable)\n```\n\nThe environment variable always takes precedence over the project default, in both directions.\n\n- **Only functions that exceed the standard limit use the large-function path** — everything under 250 MB keeps the normal, faster path, so enabling it is not a global performance trade.\n- **Not supported with Secure Compute or Static IPs.**\n\n### Shrinking a bundle first\n\nA 5 GB function still costs you cold-start time. Trim before you opt in:\n\nIn `vercel.json` (not supported in Next.js — see below):\n\n```json filename=\"vercel.json\"\n{\n \"functions\": {\n \"api/**/*.py\": {\n \"excludeFiles\": \"{tests/**,__tests__/**,**/*.test.py,fixtures/**,testdata/**}\"\n }\n }\n}\n```\n\n- Next.js ignores `includeFiles`/`excludeFiles` — use `outputFileTracingIncludes` / `outputFileTracingExcludes` in `next.config.js` instead.\n- Audit heavy imports, prefer dynamic `import()`, and check for a package in `dependencies` that belongs in `devDependencies`.\n\n### Request and response payloads\n\nBundle 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:\n\n- **Uploads** → Vercel Blob **client uploads**, which send the file browser → Blob directly, bypassing the function\n- **Large responses** → stream them; streamed responses are not subject to the limit\n- Otherwise, chunk across multiple requests\n\n## Docker and Container Images\n\nVercel 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.\n\n### Quick start\n\nCreate `Dockerfile.vercel` (or `Containerfile.vercel`) at the project root. Vercel detects it automatically and adds a rewrite routing all traffic to the image.\n\n```docker\n# Dockerfile.vercel\nFROM node:26-alpine\n\nRUN npm i -g srvx\nWORKDIR /app\nCOPY server.ts .\n\n# srvx listens on $PORT by default\nCMD [\"srvx\", \"--prod\"]\n```\n\n```ts\n// server.ts\nexport default {\n fetch(req: Request) {\n return Response.json({ ip: req.headers.get('x-forwarded-for') })\n },\n}\n```\n\nDeploy with `vercel deploy` or a Git push. During the build, the image is built and pushed to [Vercel Container Registry (VCR)](https://vercel.com/docs/container-registry).\n\n### The rules that actually bite\n\n- **Serve HTTP on port 80**, or override with the `PORT` environment variable in project settings. A container that doesn't listen gets no traffic.\n- **Containers must be stateless.** Each instance takes a request, returns a response, and keeps nothing between calls — that is what allows autoscaling and scale-to-zero. Persist to a Marketplace database, Redis, or Blob; never to the container filesystem.\n- **Scale to zero** after 5 minutes without traffic in production, 30 seconds in preview. Cold starts are real; do not assume a warm process.\n- **`SIGTERM` with a 30-second grace period** on scale-down (regular functions get 500 ms). Use it to drain.\n- **Logs are not per-request.** `stdout`/`stderr` are broadcast to all inflight requests of the instance, so correlate with your own request IDs.\n- **Same Function limits and Active CPU pricing** apply for size, memory, and duration.\n- **Secure Compute and Static IPs are not supported** with custom container images. If you need either, deploy that part without a container.\n- **Local dev**: `vercel dev` runs the image and requires the `docker` CLI plus a running daemon.\n\n### Multiple services in one project\n\nUse [Services](https://vercel.com/docs/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`.\n\n```json filename=\"vercel.json\"\n{\n \"services\": {\n \"frontend\": { \"runtime\": \"container\", \"root\": \"frontend/\", \"entrypoint\": \"Dockerfile.vercel\" },\n \"backend\": { \"runtime\": \"container\", \"root\": \"backend/\", \"entrypoint\": \"Dockerfile.vercel\" }\n },\n \"rewrites\": [\n { \"source\": \"/api/(.*)\", \"destination\": { \"service\": \"backend\" } },\n { \"source\": \"/(.*)\", \"destination\": { \"service\": \"frontend\" } }\n ]\n}\n```\n\nServices 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.\n\n### Vercel Container Registry (VCR)\n\n```bash\nvercel vcr login docker # authenticate Docker with a short-lived OIDC token\nvercel vcr image ls my-app # list images\nvercel vcr image inspect my-app <id>\nvercel vcr image rm my-app <id>\n```\n\nRegistry limits: 2 GB 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.\n\n### When to reach for a container\n\nGood 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.\n\nPoor 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.\n\nIf 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.\n\n## Plan Limits at a Glance\n\n| | Hobby | Pro | Enterprise |\n|---|---|---|---|\n| Duration (default / max) | 300s / **300s** | 300s / 800s | 300s / 800s |\n| Extended duration (beta) | — | 1800s | 1800s |\n| Memory / CPU | 2 GB / 1 vCPU, not configurable | Standard or Performance (4 GB / 2 vCPU) | Standard or Performance |\n| Bundle size | 250 MB (500 MB Python), 5 GB with large functions beta | same | same |\n| Concurrency | auto-scales to 30,000 | 30,000 | 100,000+ |\n| Regions | single region | up to 3 | all |\n| Edge code size (gzipped) | 1 MB | 2 MB | 4 MB |\n| VCR repos per project | 10 | 1,000 | 5,000 |\n| Request/response body | 4.5 MB | 4.5 MB | 4.5 MB |\n\n### What changed for Hobby\n\nHobby function limits went **up substantially** with Fluid Compute, and stale 10s/60s numbers are a common source of bad advice:\n\n- **Duration: 60s → 300s for both the default and the maximum** — a 5× increase. Hobby functions can run a full five minutes.\n- **CPU: the Basic instance was removed; Hobby now runs Standard**, 1 vCPU / 2 GB (up from 1 vCPU / 1.7 GB), managed by Vercel with a minimum of 1 vCPU.\n- Hobby still cannot configure memory/CPU, use the extended 30-minute duration, or run in multiple regions — those remain Pro/Enterprise.\n\n## Streaming\n\nZero-config streaming on the default Node.js runtime, including Server-Sent Events (SSE). Essential for AI applications.\n\n> **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.\n\n```ts\nexport async function POST(req: Request) {\n const encoder = new TextEncoder()\n const stream = new ReadableStream({\n async start(controller) {\n for (const chunk of data) {\n controller.enqueue(encoder.encode(chunk))\n await new Promise(r => setTimeout(r, 100))\n }\n controller.close()\n },\n })\n\n return new Response(stream, {\n headers: { 'Content-Type': 'text/event-stream' },\n })\n}\n```\n\nFor AI streaming, use the AI SDK's `toUIMessageStreamResponse()` (for chat UIs with `useChat`) which handles SSE formatting automatically.\n\n## WebSockets\n\nVercel 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.\n\n**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.\n\n### `ws` (no extra config)\n\nWebSockets work like any distributed WebSocket server — export an `http.Server` and use a library such as `ws`:\n\n```ts\n// api/ws.ts\nimport http from 'http'\nimport { WebSocketServer } from 'ws'\n\nconst server = http.createServer()\nconst wss = new WebSocketServer({ server })\n\nwss.on('connection', (ws) => {\n ws.on('message', (data) => ws.send(data)) // echo\n})\n\nexport default server\n```\n\n### Socket.IO\n\nHigher-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:\n\n```ts\n// api/socket-io.ts\nimport http from 'http'\nimport { Server } from 'socket.io'\n\nconst server = http.createServer()\nconst io = new Server(server)\n\nio.on('connection', (socket) => {\n socket.on('message', (data) => socket.send(data))\n})\n\nexport default server\n```\n\n```ts\n// client.ts\nimport { io } from 'socket.io-client'\n\nconst socket = io('https://your-domain.com', {\n // Socket.IO appends /socket.io, so the full path becomes /api/socket-io/socket.io\n path: '/api/socket-io/socket.io',\n transports: ['websocket'], // required — Socket.IO defaults to HTTP long-polling\n})\n```\n\nExpress, 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.\n\n### Next.js\n\nNext.js doesn't expose an API for handling WebSocket upgrades. Use `experimental_upgradeWebSocket()` from `@vercel/functions` inside a route handler:\n\n```ts\n// app/api/ws/route.ts\nimport { experimental_upgradeWebSocket, type WebSocketData } from '@vercel/functions'\n\nexport async function GET() {\n return experimental_upgradeWebSocket((ws) => {\n ws.on('message', (data: WebSocketData) => ws.send(data))\n })\n}\n```\n\n### Reconnects and persistent state\n\n- **Connections close when the function reaches its max duration.** Clients must reconnect with backoff, then resubscribe to channels and reload any state they need.\n- **No instance affinity across connections.** A reconnect — or a new deployment — may land on a different instance, so never keep durable state, presence, rooms, or pub/sub coordination in memory. Use an external store such as [Redis from the Marketplace](https://vercel.com/marketplace/redis).\n\n```ts\n// client.ts — reconnect with exponential backoff\nlet socket: WebSocket\nlet delay = 1000\n\nfunction connect() {\n socket = new WebSocket('wss://your-domain.com/api/ws')\n socket.addEventListener('open', () => { delay = 1000 })\n socket.addEventListener('message', (e) => console.log(e.data))\n socket.addEventListener('close', () => {\n setTimeout(connect, delay)\n delay = Math.min(delay * 2, 30000)\n })\n}\n\nconnect()\n```\n\n## Cron Jobs\n\nSchedule function invocations via `vercel.json`:\n\n```json filename=\"vercel.json\"\n{\n \"crons\": [\n {\n \"path\": \"/api/daily-report\",\n \"schedule\": \"0 8 * * *\"\n },\n {\n \"path\": \"/api/cleanup\",\n \"schedule\": \"0 */6 * * *\"\n }\n ]\n}\n```\n\nThe cron endpoint receives a normal HTTP request. Verify it's from Vercel:\n\n```ts\nexport async function GET(req: Request) {\n const authHeader = req.headers.get('authorization')\n if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {\n return new Response('Unauthorized', { status: 401 })\n }\n // Do scheduled work\n return Response.json({ ok: true })\n}\n```\n\n## Configuration\n\n`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).\n\n```ts\n// vercel.ts\nimport type { VercelConfig } from '@vercel/config/v1'\n\nexport const config: VercelConfig = {\n functions: {\n 'app/api/heavy/**': { maxDuration: 800 },\n 'app/api/report/**': { maxDuration: 1800 }, // Pro/Ent extended-duration beta\n },\n crons: [{ path: '/api/cleanup', schedule: '0 0 * * *' }],\n}\n```\n\nThe `vercel.json` equivalent:\n\n```json\n{\n \"$schema\": \"https://openapi.vercel.sh/vercel.json\",\n \"functions\": {\n \"app/api/heavy/**\": { \"maxDuration\": 800 },\n \"api/upload.js\": { \"supportsCancellation\": true }\n }\n}\n```\n\nWhat you **cannot** put here:\n- `memory` — with Fluid Compute (the default), set it in the dashboard; Pro/Enterprise only, and `vercel.json` warns at build time\n- A project-wide default above 800s — extended durations are per-function only\n\n`runtime: \"edge\"` is accepted here, but prefer leaving it out — see [Prefer Node.js over the Edge runtime](#prefer-nodejs-over-the-edge-runtime).\n\n## Common Pitfalls\n\n1. **`waitUntil` given a callback**: it takes a Promise. `waitUntil(fn())`, never `waitUntil(fn)` or `waitUntil(async () => {})` — the latter silently does nothing\n2. **Cold starts with DB connections**: use connection pooling (e.g. Neon's `@neondatabase/serverless`)\n3. **Reaching for the Edge runtime**: prefer Node.js — see [Prefer Node.js over the Edge runtime](#prefer-nodejs-over-the-edge-runtime)\n4. **Timeout exceeded**: raise `maxDuration` (800s Pro/Ent, 1800s in beta), or move to Workflow for anything longer\n5. **Bundle size**: standard limit is 250 MB uncompressed (500 MB Python). 5 GB needs the large functions beta, which existing projects must opt into with `VERCEL_SUPPORT_LARGE_FUNCTIONS=1`\n6. **Payload size**: request and response bodies cap at **4.5 MB** (`413 FUNCTION_PAYLOAD_TOO_LARGE`) — use Blob client uploads or streaming, not a bigger function\n7. **In-memory state**: Fluid shares instances across invocations and scales to zero — never keep sessions, rooms, or caches in process memory\n8. **Setting `memory` in `vercel.json`**: with Fluid Compute enabled this is not the place for it and the build warns — set it in the dashboard\n9. **Environment variables**: available in all functions automatically; use `vercel env pull` for local dev\n\n## Function Runtime Diagnostics\n\n### Timeout Diagnostics\n\n```\n504 FUNCTION_INVOCATION_TIMEOUT?\n├─ All plans default to 300s with Fluid Compute\n├─ How long does the work actually need?\n│ ├─ ≤ 300s → Already allowed on every plan; the timeout is a bug, not a limit\n│ ├─ 300–800s → Pro/Enterprise: set `maxDuration` in code or vercel.json\n│ ├─ 800–1800s → Pro/Enterprise extended-duration beta (30 min)\n│ │ ├─ Must be set PER FUNCTION — project defaults above 800s are ignored\n│ │ ├─ Runtimes: nodejs20/22/24.x, Bun 1.x/1.4.x, python3.12/3.13/3.14\n│ │ └─ Blocked if the project uses Secure Compute or Static IPs\n│ └─ > 30 min, or must survive crashes/deploys → Vercel Workflow\n├─ On Hobby? → 300s is both default AND max; no extension exists, upgrade to Pro\n├─ Client disconnected before the function finished?\n│ └─ HTTP/1.1 drops idle connections → stream heartbeat/progress data\n└─ DB query slow? → Add connection pooling, check cold start, use Global Config\n```\n\n### 500 Error Diagnostics\n\n```\n500 Internal Server Error?\n├─ Check Vercel Runtime Logs (Dashboard → Deployments → Functions tab)\n├─ Missing env vars? → Compare `.env.local` against Vercel dashboard settings\n├─ Import error? → Verify package is in `dependencies`, not `devDependencies`\n└─ Uncaught exception? → Wrap handler in try/catch, use `after()` for error reporting\n```\n\n### Invocation Failure Diagnostics\n\n```\n\"FUNCTION_INVOCATION_FAILED\"?\n├─ Memory exceeded (OOM)?\n│ ├─ Pro/Enterprise → switch to Performance (4 GB / 2 vCPU) in Settings → Functions\n│ │ └─ With Fluid compute, set it there, not in vercel.json (which warns at build)\n│ └─ Hobby → fixed at 2 GB / 1 vCPU; reduce per-request memory or upgrade\n├─ Crashed during init? → Check top-level await or heavy imports at module scope\n├─ Build failed with \"exceeded the unzipped maximum size of 250 MB\"?\n│ ├─ Trim with excludeFiles / outputFileTracingExcludes first\n│ └─ Then large functions beta: VERCEL_SUPPORT_LARGE_FUNCTIONS=1 (5 GB, Node/Bun/Python)\n├─ 413 FUNCTION_PAYLOAD_TOO_LARGE? → 4.5 MB body cap; use Blob client uploads or streaming\n└─ Container image? → Is it listening on port 80 (or $PORT)? Is it holding state between requests?\n```\n\n### Cold Start Diagnostics\n\n```\nCold start latency > 1s?\n├─ Moving to the Edge runtime is not the fix — Vercel recommends migrating off it\n├─ Fluid Compute enabled? → Reuses warm instances across concurrent invocations\n├─ Measuring in preview? → Bytecode caching is production-only; re-measure in prod\n├─ Large function bundle? → Audit imports, use dynamic imports, tree-shake\n├─ DB connection in cold start? → Use connection pooling (Neon serverless driver)\n└─ Container image? → Scales to zero after 5 min idle (30 s in preview); expect cold starts\n```\n\n### Edge Function Timeout Diagnostics\n\n```\n\"EDGE_FUNCTION_INVOCATION_TIMEOUT\"?\n├─ Edge must START the response within 25s (then may stream up to 300s)\n├─ `maxDuration` does NOT apply to the Edge runtime — there is no way to raise this\n├─ Recommended fix: drop `runtime = 'edge'` and run on Node.js\n│ └─ Node.js gives you 300s by default, 800s on Pro/Ent, 1800s in the beta\n└─ On Next.js 16.3+, `runtime = 'edge'` is unsupported — migration is required there\n```\n\n## Official Documentation\n\n- [Vercel Functions](https://vercel.com/docs/functions)\n- [Functions limits](https://vercel.com/docs/functions/limitations) — duration, memory, bundle size, large functions\n- [Configuring max duration](https://vercel.com/docs/functions/configuring-functions/duration) — including the extended 30-minute beta\n- [Configuring memory / CPU](https://vercel.com/docs/functions/configuring-functions/memory)\n- [Functions API reference](https://vercel.com/docs/functions/functions-api-reference) — `waitUntil`, `getDeadline`, SIGTERM, cancellation\n- [Fluid Compute](https://vercel.com/docs/fluid-compute)\n- [Container Images](https://vercel.com/docs/functions/container-images) — Dockerfile on Vercel\n- [Vercel Container Registry](https://vercel.com/docs/container-registry) and its [limits and pricing](https://vercel.com/docs/container-registry/limits-and-pricing)\n- [Services](https://vercel.com/docs/services) — multiple backends/frontends in one project\n- [Streaming](https://vercel.com/docs/functions/streaming-functions)\n- [WebSockets](https://vercel.com/docs/functions/websockets)\n- [Cron Jobs](https://vercel.com/docs/cron-jobs)\n- [Vercel Workflow](https://vercel.com/docs/workflows) — for anything beyond 30 minutes\n- [Edge Runtime](https://vercel.com/docs/functions/runtimes/edge) — legacy; Vercel recommends migrating to Node.js\n- [GitHub: Vercel](https://github.com/vercel/vercel)\n"SKILL.md line diff
--- before +++ after @@ -1,13 +1,18 @@ --- name: vercel-functions -description: Vercel Functions expert guidance — Serverless Functions, Edge Functions, Fluid Compute, streaming, Cron Jobs, and runtime configuration. Use when configuring, debugging, or optimizing server-side code running on Vercel. +description: 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. +summary: "Vercel Functions run on Fluid Compute with Node.js as the default runtime — strongly prefer it over `runtime = 'edge'` (Vercel recommends migrating off Edge, and Next.js 16.3+ no longer supports it). Duration: 300s default on every plan including Hobby, 800s max on Pro/Enterprise, 1800s (30 min) per-function in the extended beta; beyond that use Vercel Workflow. Bundles: 250 MB standard (500 MB Python), 5 GB via the large functions beta (`VERCEL_SUPPORT_LARGE_FUNCTIONS=1`). Request/response bodies cap at 4.5 MB. Memory is dashboard-only (Standard 2 GB/1 vCPU, Performance 4 GB/2 vCPU; Hobby fixed). Docker works: add `Dockerfile.vercel` to run an OCI image as an autoscaling, stateless, scale-to-zero Function." metadata: priority: 8 docs: - "https://vercel.com/docs/functions" - "https://vercel.com/docs/functions/runtimes" + - "https://vercel.com/docs/functions/limitations" + - "https://vercel.com/docs/functions/configuring-functions/duration" + - "https://vercel.com/docs/functions/container-images" + - "https://vercel.com/docs/fluid-compute" - "https://vercel.com/docs/functions/websockets" - sitemap: "https://vercel.com/sitemap/docs.xml" + sitemap: "https://vercel.com/sitemap.xml" pathPatterns: - 'api/**/*.*' - 'pages/api/**' @@ -20,9 +25,18 @@ - 'apps/*/pages/api/**' - 'vercel.json' - 'apps/*/vercel.json' + - 'vercel.ts' + - 'apps/*/vercel.ts' + # Vercel builds `Dockerfile.vercel` / `Containerfile.vercel` into a + # container-image Function, so these are Functions config, not generic Docker. + - 'Dockerfile.vercel' + - '*/Dockerfile.vercel' + - 'Containerfile.vercel' + - '*/Containerfile.vercel' bashPatterns: - '\bvercel\s+dev\b' - '\bvercel\s+logs\b' + - '\bvercel\s+vcr\b' importPatterns: - 'ws' - 'socket.io' @@ -39,116 +53,335 @@ # signal that someone is working around a missing feature. - "long polling" - "long-polling" - allOf: [] + # Duration: people hit the ceiling and ask about it in these words. + - "maxduration" + - "max duration" + - "function timeout" + - "function times out" + - "long-running function" + - "long running function" + - "504" + # Bundle size: the 250 MB error message is the usual entry point. + - "unzipped maximum size" + - "250mb" + - "250 mb" + - "large function" + - "bundle size limit" + # Containers: Dockerfile.vercel is a Functions feature, not just packaging. + - "dockerfile" + - "container image" + - "container registry" + - "runtime edge" + - "edge runtime" + allOf: + - ["docker", "vercel"] + - ["hobby", "limit"] anyOf: - "realtime" - "bidirectional" - "ws server" - "polling" - "server-sent events" + - "fluid compute" + - "active cpu" noneOf: [] minScore: 6 +validate: + - + pattern: export\s+default\s+function + message: 'Use named exports (GET, POST, PUT, DELETE) instead of default export for route handlers' + severity: error + # Skip on App Router page / layout / loading / error / not-found / sitemap / template / default files, + # which require a default export by Next.js convention. Detected via the 'use client' directive, + # an App Router config export (metadata, dynamic, revalidate, fetchCache, runtime), an `export default + # function` whose name matches an App Router file (Page / Layout / Loading / etc.), or any JSX + # element with a capitalised component tag — all signals that the file is a page-style file rather + # than a route handler. See anthropics/claude-code#54989. + skipIfFileContains: "(?:^|\\n)\\s*['\"]use\\s+client['\"]|export\\s+const\\s+(?:metadata|dynamic|revalidate|fetchCache|runtime)\\b|export\\s+default\\s+(?:async\\s+)?function\\s+\\w*(?:Page|Layout|Loading|Error|NotFound|Sitemap|Template|Default|sitemap|robots|opengraph|manifest)\\b|<[A-Z][A-Za-z0-9]*|\\{\\s*children\\s*[,}:]|MetadataRoute\\.|from\\s+['\"]next/(?:font|image|link|navigation|headers|cookies)['\"]" + - + pattern: NextApiRequest|NextApiResponse + message: 'NextApiRequest/NextApiResponse are Pages Router types — use Web API Request/Response' + severity: error + - + # Vercel's docs recommend migrating off the Edge runtime, and Next.js 16.3+ + # doesn't support it. Surfaced as a recommendation rather than an error: + # existing Edge functions still work, so this is a nudge, not a blocker. + # Matches `"runtime": "edge"` in vercel.json too. + pattern: 'runtime[''"]?\s*[=:]\s*[''"]edge[''"]' + message: 'Consider dropping `runtime = "edge"`. Vercel recommends migrating from Edge to Node.js, and Next.js 16.3+ no longer supports it. Node.js on Fluid Compute runs in the same regions at the same price with full Node.js APIs, longer durations, and larger bundles.' + severity: recommended + - + pattern: 'from\s+[''"](openai|@anthropic-ai/sdk|anthropic)[''"]|new\s+(OpenAI|Anthropic)\(' + message: 'Direct AI provider SDK detected in route handler. Use the Vercel AI SDK for streaming, tools, and provider abstraction.' + severity: recommended + upgradeToSkill: ai-sdk + upgradeWhy: 'Replace vendor-locked provider SDKs with @ai-sdk/openai or @ai-sdk/anthropic for unified streaming and tool support.' + skipIfFileContains: '@ai-sdk/|from\s+[''"](ai)[''"]|import.*from\s+[''"](ai)[''"]|streamText|generateText' + - + pattern: 'setTimeout\s*\(|setInterval\s*\(|await\s+new\s+Promise\s*\([^)]*setTimeout' + message: 'Long-running or polling logic detected in a serverless handler. Functions have execution time limits.' + severity: recommended + upgradeToSkill: workflow + upgradeWhy: 'Move delayed/polling logic to Vercel Workflow for durable execution with pause, resume, retries, and crash safety.' + skipIfFileContains: 'use workflow|use step' + - + pattern: 'writeFile(Sync)?\(|createWriteStream\(|from\s+[''"](multer|formidable)[''"]|fs\.writeFile' + message: 'Local filesystem write detected. Serverless functions have ephemeral, read-only filesystems.' + severity: error + upgradeToSkill: vercel-storage + upgradeWhy: 'Replace local filesystem writes with Vercel Blob, Neon, or Upstash for persistent, platform-native storage.' + skipIfFileContains: '@vercel/blob|@upstash/|@neondatabase/' + - + pattern: 'export\s+(async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)\b' + message: 'Route handler has no observability instrumentation. Add logging and error tracking for production debugging.' + severity: warn + skipIfFileContains: 'console\.error|logger\.|captureException|Sentry|@vercel/otel|withTracing' + - + pattern: 'from\s+[''""](lru-cache|node-cache|memory-cache)[''""]|new\s+(LRUCache|NodeCache|Map)\(\s*\).*cache' + message: 'In-process memory cache detected in serverless function. Process memory is not shared across invocations.' + severity: recommended + upgradeToSkill: runtime-cache + upgradeWhy: 'Replace in-process caches with Vercel Runtime Cache (getCache from @vercel/functions) for region-aware caching that persists across invocations.' + skipIfFileContains: 'getCache|from\s+[''""]\@vercel/functions[''""]' + - + pattern: 'maxRetries\s*[=:]|retryCount\s*[=:]|retry\s*\(\s*|for\s*\([^)]*retry|while\s*\([^)]*retry' + message: 'Manual retry logic detected. Use Vercel Workflow SDK for automatic retries with durable execution.' + severity: recommended + upgradeToSkill: workflow + upgradeWhy: 'Replace manual retry loops with Workflow SDK steps that provide automatic retries, crash safety, and observability.' + skipIfFileContains: 'use workflow|use step|from\s+[''""](workflow)[''""]' + - + pattern: 'from\s+[''"](express)[''""]|require\s*\(\s*[''"](express)[''""\)]' + message: 'Express.js detected in a Vercel project. Vercel Functions use the Web Request/Response API — Express middleware, req/res, and app.listen() do not work in serverless.' + severity: recommended + upgradeToSkill: vercel-functions + upgradeWhy: 'Replace Express with Next.js route handlers (export async function GET/POST) or Vercel Functions using the Web Request/Response API.' + skipIfFileContains: 'export\s+(async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)|from\s+[''""](next/server|@vercel/functions)[''""]' +retrieval: + aliases: + - serverless functions + - api routes + - edge functions + - lambda + - websockets + - socket.io + - docker + - dockerfile + - container images + - function timeout + - max duration + - bundle size + - hobby limits + intents: + - create serverless function + - configure function runtime + - optimize cold starts + - add api route + - serve a websocket connection + - run a function for longer than 5 minutes + - deploy a dockerfile + - fix a function that exceeds the bundle size limit + - check plan limits for functions + entities: + - Serverless Functions + - Edge Functions + - Fluid Compute + - Long-duration functions + - Large functions + - Container Images + - Vercel Container Registry + - streaming + - WebSockets + - Cron Jobs +chainTo: + - + pattern: 'from\s+[''\"](openai|@anthropic-ai/sdk|anthropic)[''"]|new\s+(OpenAI|Anthropic)\(' + targetSkill: ai-sdk + message: 'Direct AI provider SDK in route handler — loading AI SDK guidance for unified streaming and tool support.' + - + pattern: 'setTimeout\s*\(|setInterval\s*\(|await\s+new\s+Promise\s*\([^)]*setTimeout' + targetSkill: workflow + message: 'Long-running or polling logic in serverless handler — loading Workflow SDK for durable execution.' + - + pattern: 'writeFile(Sync)?\(|createWriteStream\(|from\s+[''\"](multer|formidable)[''"]|fs\.writeFile' + targetSkill: vercel-storage + message: 'Local filesystem write in serverless function — loading Vercel Storage guidance for platform-native persistence.' + - + pattern: 'from\s+[''""]@vercel/(postgres|kv)[''""]' + targetSkill: vercel-storage + message: '@vercel/postgres and @vercel/kv are sunset — loading Vercel Storage guidance for Neon and Upstash migration.' + - + pattern: 'generateObject\s*\(|streamObject\s*\(|toDataStreamResponse|maxSteps\b|CoreMessage\b' + targetSkill: ai-sdk + message: 'Deprecated AI SDK v5 API detected — loading AI SDK guidance for migration.' + - + pattern: 'while\s*\(\s*true\s*\)\s*\{|for\s*\(\s*;\s*;\s*\)\s*\{|setInterval\s*\(\s*async' + targetSkill: workflow + message: 'Polling loop in serverless function detected — loading Workflow SDK for durable, crash-safe execution with pause/resume.' + skipIfFileContains: "use workflow|use step|from\\s+['\"]workflow['\"]" + - + pattern: "from\\s+['\"]express['\"]|require\\s*\\(\\s*['\"]express['\"]" + targetSkill: vercel-functions + message: 'Express.js detected — loading Vercel Functions guidance for Web Request/Response API route handlers that replace Express middleware and routing.' + skipIfFileContains: "export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE)" + - + pattern: 'from\s+[''""](lru-cache|node-cache|memory-cache)[''""]|new\s+(LRUCache|NodeCache|Map)\(\s*\).*cache' + targetSkill: runtime-cache + message: 'In-process memory cache in serverless function — loading Runtime Cache guidance for region-aware caching that persists across invocations.' + skipIfFileContains: 'getCache|from\s+[''""]\@vercel/functions[''""]' + - + pattern: 'maxRetries\s*[=:]|retryCount\s*[=:]|retry\s*\(\s*|for\s*\([^)]*retry|while\s*\([^)]*retry' + targetSkill: workflow + message: 'Manual retry logic in serverless handler — loading Workflow SDK guidance for automatic retries with durable execution.' + skipIfFileContains: 'use workflow|use step|from\s+[''""](workflow)[''""]' + + - + pattern: 'import\s*\{[^}]*\bmetric\b[^}]*\}\s*from\s*[''"]@vercel/functions[''"]' + targetSkill: custom-metrics + message: '@vercel/functions metric() detected — loading Custom Metrics guidance for naming, attributes, and vc metrics queries.' + --- # Vercel Functions You are an expert in Vercel Functions — the compute layer of the Vercel platform. -## Function Types +## Prefer Node.js over the Edge runtime -### Serverless Functions (Node.js) -- Full Node.js runtime, all npm packages available -- Default for Next.js API routes, Server Actions, Server Components -- Cold starts: 800ms–2.5s (with DB connections) -- Max duration: 10s (Hobby), 300s (Pro default), 800s (Fluid Compute Pro/Enterprise) +**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. + +### The default to reach for ```ts -// app/api/hello/route.ts +// app/api/hello/route.ts — no runtime export needed. export async function GET() { - return Response.json({ message: 'Hello from Node.js' }) + return Response.json({ message: 'Hello from Node.js on Fluid Compute' }) } ``` -### Edge Functions (V8 Isolates) -- Lightweight V8 runtime, Web Standard APIs only -- Ultra-low cold starts (<1ms globally) -- Limited API surface (no full Node.js) -- Best for: auth checks, redirects, A/B testing, simple transformations +Node.js is the default. Omit `export const runtime` entirely rather than writing `export const runtime = 'nodejs'`. -```ts -// app/api/hello/route.ts -export const runtime = 'edge' +### Reasons people reach for Edge — and what to do instead -export async function GET() { - return new Response('Hello from the Edge') -} -``` +| "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 | -### Bun Runtime (Public Beta) +### What Edge actually costs you -Add `"bunVersion": "1.x"` to `vercel.json` to run Node.js functions on Bun instead. ~28% lower latency for CPU-bound workloads. Supports Next.js, Express, Hono, Nitro. +- No `fs`, no native modules, no `require()` — ESM only, and most npm packages with Node.js dependencies simply will not load +- No `eval` / `new Function` / dynamic `WebAssembly.instantiate` +- **Code size limit after gzip: 1 MB (Hobby), 2 MB (Pro), 4 MB (Enterprise)** — versus 250 MB uncompressed (up to 5 GB) on Node.js +- Must begin sending a response within **25 seconds** (it may then stream for up to 300s). The 300s/800s/1800s duration limits below apply to the Node.js, Bun, and Python runtimes — **not** to Edge +- No long-duration or large-function support of any kind -### Rust Runtime (Public Beta) +### Migrating an existing Edge function -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. +Worth 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. -### Node.js 24 LTS +1. Remove `export const runtime = 'edge'` (or `runtime: 'edge'` in `vercel.json` / the `config` object). +2. Replace `next/server` Edge-only imports where applicable; the Web `Request`/`Response` handler signature is unchanged, so most route handlers need no other edit. +3. If you pinned execution with the Edge-only `preferredRegion`, use `regions` in `vercel.json` instead. +4. Confirm Fluid Compute is on (default since April 23, 2025) and redeploy. -Node.js 24 LTS is now GA on Vercel for both builds and functions. Features V8 13.6, global `URLPattern`, Undici v7 for faster `fetch()`, and npm v11. +There is no rollback story to plan for: Node.js is a superset of what the function could do on Edge. -### Choosing Runtime +## Function Types + +### Node.js (the default) +- Full Node.js runtime, all npm packages available +- Default for Next.js route handlers, Server Actions, Server Components, and any file in `/api` +- **Node.js 24 LTS is GA** for builds and functions (V8 13.6, global `URLPattern`, Undici v7, npm v11). **Node.js 20 is deprecated on October 1, 2026** — move off `nodejs20.x` +- Duration: 300s default on every plan; 800s max on Pro/Enterprise; 1800s with the extended-duration beta + +### Bun +Add `"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 +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 +Rust functions run on Fluid Compute with HTTP streaming and Active CPU pricing. Official runtime (Beta) built on the `vercel_runtime` crate. Supports environment variables up to 64 KB. + +### Container images (Docker) +Any OCI image via `Dockerfile.vercel`. See [Docker and Container Images](#docker-and-container-images) below. + +### Edge (legacy — not recommended) +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](#prefer-nodejs-over-the-edge-runtime). + +### Choosing a 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 | -| Ultra-low latency, simple logic | `edge` | <1ms cold start, global | -| Database connections, heavy deps | `nodejs` | Edge lacks full Node.js | -| Auth/redirect at the edge | `edge` | Fastest response | -| AI streaming | Either | Both support streaming | -| Systems-level performance | `rust` (beta) | Native speed, Fluid Compute | +| 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 -Fluid Compute is the unified execution model for all Vercel Functions (both Node.js and Edge). +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. -Key benefits: -- **Optimized concurrency**: Multiple invocations on a single instance — up to 85% cost reduction for high-concurrency workloads -- **Extended durations**: Default 300s for all plans; up to 800s on Pro/Enterprise -- **Active CPU pricing**: Charges only while CPU is actively working, not during idle/await time. Enabled by default for all plans. Memory-only periods billed at a significantly lower rate. -- **Background processing**: `waitUntil` / `after` for post-response tasks -- **Dynamic scaling**: Automatic during traffic spikes -- **Bytecode caching**: Reduces cold starts via Rust-based runtime with pre-compiled function code -- **Multi-region failover**: Default for Enterprise when Fluid is activated - -### Instance Sizes - -| Size | CPU | Memory | -|------|-----|--------| -| Standard (default) | 1 vCPU | 2 GB | -| Performance | 2 vCPU | 4 GB | +Long-duration, large-function, and container-image support all depend on it. -Hobby projects use Standard CPU. The Basic CPU instance has been removed. +Key behaviors: +- **Optimized concurrency**: multiple invocations share one instance instead of one microVM per request. Vercel prioritizes idle existing resources before allocating new ones. Available on the Node.js and Python runtimes. +- **Active CPU pricing**: you are billed for CPU time your code actually consumes, plus provisioned memory while requests are in flight, plus invocations. Waiting on I/O (AI models, DB queries) does not accrue Active CPU — which is what makes 30-minute functions affordable. +- **Automatic cold start optimization**: function pre-warming plus **bytecode caching** on Node.js 20+. Bytecode caching applies to **production only** — not dev or preview, so don't benchmark cold starts in a preview deployment. +- **Error isolation**: an uncaught exception or unhandled rejection is logged and in-flight requests are allowed to finish; one broken request will not crash its neighbors on the same instance. +- **Cross-AZ and cross-region failover**: fails over to another availability zone in-region first, then to the next closest region. +- **Graceful shutdown**: `SIGTERM` before termination (see below). + +### Instance Sizes (memory / CPU) + +| 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 | + +- **With Fluid Compute enabled, memory cannot be set in `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.) +- **Pro/Enterprise only.** Hobby always runs Standard (2 GB / 1 vCPU) and cannot configure it. The Basic instance has been removed. +- More memory also means more CPU, which can *reduce* Active CPU billing for CPU-bound work by finishing sooner — but it raises Provisioned Memory cost while requests are in flight. +- Projects created before 2019-11-08 may still sit on legacy sizes (1024 MB / 0.6 vCPU on Hobby, 3008 MB / 1.67 vCPU on Pro) until you pick a size in the dashboard. + +### Settings precedence + +Function code (`export const maxDuration`) → `vercel.json` → dashboard → Fluid defaults. Later entries lose. ### Background Processing with `waitUntil` +`waitUntil` takes a **Promise**, not a callback. Passing a function does nothing — a common and silent bug. + ```ts -// Continue work after sending response import { waitUntil } from '@vercel/functions' export async function POST(req: Request) { const data = await req.json() - // Send response immediately - const response = Response.json({ received: true }) + // Correct: invoke the async work and hand over the promise. + waitUntil(processAnalytics(data)) - // Continue processing in background - waitUntil(async () => { - await processAnalytics(data) - await sendNotification(data) - }) + // For several tasks, combine them: + waitUntil(Promise.all([sendNotification(data), updateCache(data)])) - return response + return Response.json({ received: true }) } ``` @@ -168,11 +401,286 @@ } ``` +### Graceful shutdown and request cancellation + +```ts +// Runs on scale-down. 500 ms to clean up (30 s for container images). +process.on('SIGTERM', () => { + // flush buffers, close pools +}) +``` + +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. + +```json filename="vercel.json" +{ + "functions": { + "api/*": { "supportsCancellation": true } + } +} +``` + +```ts +export async function GET(request: Request) { + // Pass the signal through so upstream work stops too. + const res = await fetch('https://upstream.example.com', { signal: request.signal }) + return new Response(res.body, { status: res.status }) +} +``` + +## Duration and Long-Duration Functions + +### Duration limits + +With Fluid Compute (default), per [Vercel's limits](https://vercel.com/docs/functions/limitations#max-duration): + +| 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. + +### Setting `maxDuration` + +```ts +// app/api/report/route.ts — Next.js App Router (and Node.js, SvelteKit, Astro, +// Nuxt, Remix via their own config). Value is in seconds. +export const maxDuration = 800 + +export async function POST(request: Request) { + return Response.json({ ok: true }) +} +``` + +For other frameworks and runtimes — Next.js < 13.5, Rust, Go, Python, Ruby — use `vercel.json`: + +```json +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "functions": { + "api/long-task.py": { "maxDuration": 1800 } + } +} +``` + +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**. + +### Extended max duration (30 minutes) — Beta + +Pro and Enterprise teams can run individual functions for up to **1800s**. Requirements, all of which are load-bearing: + +- **Per-function configuration only.** Durations above 800s must be set in code or in `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. +- **Supported runtimes only**: `nodejs20.x`, `nodejs22.x`, `nodejs24.x`, Bun `1.x` and `1.4.x`, `python3.12`, `python3.13`, `python3.14`. +- **Fluid Compute must be enabled** (default for new projects). +- **Secure Compute and Static IPs do not support durations above 800s** during the beta. If the project uses either, you are capped at 800s. + +```ts +// app/api/long-task/route.ts +export const maxDuration = 1800 // 30 minutes + +export async function POST(request: Request) { + await doTheLongThing() + return Response.json({ ok: true }) +} +``` + +### Keeping a long request alive + +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: + +```ts +import { getDeadline } from '@vercel/functions' + +const deadline = getDeadline() // Date | undefined (undefined outside the Vercel Functions runtime) +const msRemaining = deadline ? deadline.getTime() - Date.now() : Infinity +``` + +### Cost of long functions + +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. + +### When 30 minutes is not enough + +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: + +- ≤ 300s → any plan, no configuration needed +- 300–800s → Pro/Enterprise, set `maxDuration` +- 800–1800s → Pro/Enterprise, extended-duration beta, per-function config +- Beyond 30 min, or needs to survive a crash/deploy → Vercel Workflow (`workflow` skill) + +Workflow steps themselves support extended function durations, so a single step can also run up to 30 minutes. + +## Large Functions (bundle size) + +### Standard limits + +| 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 — Beta + +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. + +- **Runtimes**: Node.js, Bun, Python. +- **Requires Fluid Compute with Active CPU** enabled (default for new projects). +- **New projects are eligible by default.** Existing projects opt in with the `VERCEL_SUPPORT_LARGE_FUNCTIONS` environment variable, then redeploy: + +```bash +vercel env add VERCEL_SUPPORT_LARGE_FUNCTIONS # value: 1 (use 0 to disable) +``` + +The environment variable always takes precedence over the project default, in both directions. + +- **Only functions that exceed the standard limit use the large-function path** — everything under 250 MB keeps the normal, faster path, so enabling it is not a global performance trade. +- **Not supported with Secure Compute or Static IPs.** + +### Shrinking a bundle first + +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): + +```json filename="vercel.json" +{ + "functions": { + "api/**/*.py": { + "excludeFiles": "{tests/**,__tests__/**,**/*.test.py,fixtures/**,testdata/**}" + } + } +} +``` + +- Next.js ignores `includeFiles`/`excludeFiles` — use `outputFileTracingIncludes` / `outputFileTracingExcludes` in `next.config.js` instead. +- Audit heavy imports, prefer dynamic `import()`, and check for a package in `dependencies` that belongs in `devDependencies`. + +### Request and response payloads + +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: + +- **Uploads** → Vercel Blob **client uploads**, which send the file browser → Blob directly, bypassing the function +- **Large responses** → stream them; streamed responses are not subject to the limit +- Otherwise, chunk across multiple requests + +## Docker and Container Images + +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. + +### Quick start + +Create `Dockerfile.vercel` (or `Containerfile.vercel`) at the project root. Vercel detects it automatically and adds a rewrite routing all traffic to the image. + +```docker +# Dockerfile.vercel +FROM node:26-alpine + +RUN npm i -g srvx +WORKDIR /app +COPY server.ts . + +# srvx listens on $PORT by default +CMD ["srvx", "--prod"] +``` + +```ts +// server.ts +export default { + fetch(req: Request) { + return Response.json({ ip: req.headers.get('x-forwarded-for') }) + }, +} +``` + +Deploy with `vercel deploy` or a Git push. During the build, the image is built and pushed to [Vercel Container Registry (VCR)](https://vercel.com/docs/container-registry). + +### The rules that actually bite + +- **Serve HTTP on port 80**, or override with the `PORT` environment variable in project settings. A container that doesn't listen gets no traffic. +- **Containers must be stateless.** Each instance takes a request, returns a response, and keeps nothing between calls — that is what allows autoscaling and scale-to-zero. Persist to a Marketplace database, Redis, or Blob; never to the container filesystem. +- **Scale to zero** after 5 minutes without traffic in production, 30 seconds in preview. Cold starts are real; do not assume a warm process. +- **`SIGTERM` with a 30-second grace period** on scale-down (regular functions get 500 ms). Use it to drain. +- **Logs are not per-request.** `stdout`/`stderr` are broadcast to all inflight requests of the instance, so correlate with your own request IDs. +- **Same Function limits and Active CPU pricing** apply for size, memory, and duration. +- **Secure Compute and Static IPs are not supported** with custom container images. If you need either, deploy that part without a container. +- **Local dev**: `vercel dev` runs the image and requires the `docker` CLI plus a running daemon. + +### Multiple services in one project + +Use [Services](https://vercel.com/docs/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`. + +```json filename="vercel.json" +{ + "services": { + "frontend": { "runtime": "container", "root": "frontend/", "entrypoint": "Dockerfile.vercel" }, + "backend": { "runtime": "container", "root": "backend/", "entrypoint": "Dockerfile.vercel" } + }, + "rewrites": [ + { "source": "/api/(.*)", "destination": { "service": "backend" } }, + { "source": "/(.*)", "destination": { "service": "frontend" } } + ] +} +``` + +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. + +### Vercel Container Registry (VCR) + +```bash +vercel vcr login docker # authenticate Docker with a short-lived OIDC token +vercel vcr image ls my-app # list images +vercel vcr image inspect my-app <id> +vercel vcr image rm my-app <id> +``` + +Registry limits: 2 GB 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. + +### When to reach for a container + +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. + +## Plan Limits at a Glance + +| | 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 | + +### What changed for Hobby + +Hobby function limits went **up substantially** with Fluid Compute, and stale 10s/60s numbers are a common source of bad advice: + +- **Duration: 60s → 300s for both the default and the maximum** — a 5× increase. Hobby functions can run a full five minutes. +- **CPU: the Basic instance was removed; Hobby now runs Standard**, 1 vCPU / 2 GB (up from 1 vCPU / 1.7 GB), managed by Vercel with a minimum of 1 vCPU. +- Hobby still cannot configure memory/CPU, use the extended 30-minute duration, or run in multiple regions — those remain Pro/Enterprise. + ## Streaming -Zero-config streaming on **both runtimes**, including Server-Sent Events (SSE). Essential for AI applications. +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 a common misconception. Stay on Node.js (Fluid Compute) so you keep full Node.js APIs, npm packages, and longer durations; Edge offers no streaming advantage here. +> **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. ```ts export async function POST(req: Request) { @@ -294,7 +802,7 @@ Schedule function invocations via `vercel.json`: -```json +```json filename="vercel.json" { "crons": [ { @@ -322,55 +830,72 @@ } ``` -## Configuration via vercel.json +## Configuration -**Deprecation notice**: Support for the legacy `now.json` config file will be removed on **March 31, 2026**. Rename `now.json` to `vercel.json` (no content changes required). +`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). + +```ts +// vercel.ts +import type { VercelConfig } from '@vercel/config/v1' + +export const config: VercelConfig = { + functions: { + 'app/api/heavy/**': { maxDuration: 800 }, + 'app/api/report/**': { maxDuration: 1800 }, // Pro/Ent extended-duration beta + }, + crons: [{ path: '/api/cleanup', schedule: '0 0 * * *' }], +} +``` + +The `vercel.json` equivalent: ```json { + "$schema": "https://openapi.vercel.sh/vercel.json", "functions": { - "app/api/heavy/**": { - "maxDuration": 300, - "memory": 1024 - }, - "app/api/edge/**": { - "runtime": "edge" - } + "app/api/heavy/**": { "maxDuration": 800 }, + "api/upload.js": { "supportsCancellation": true } } } ``` -## Timeout Limits +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 time +- A project-wide default above 800s — extended durations are per-function only -All plans now default to 300s execution time with Fluid Compute. - -| Plan | Default | Max | -|------|---------|-----| -| Hobby | 300s | 300s | -| Pro | 300s | 800s | -| Enterprise | 300s | 800s | +`runtime: "edge"` is accepted here, but prefer leaving it out — see [Prefer Node.js over the Edge runtime](#prefer-nodejs-over-the-edge-runtime). ## Common Pitfalls -1. **Cold starts with DB connections**: Use connection pooling (e.g., Neon's `@neondatabase/serverless`) -2. **Edge limitations**: No `fs`, no native modules, limited `crypto` — use Node.js runtime if needed -3. **Timeout exceeded**: Use Fluid Compute for long-running tasks, or Workflow DevKit for very long processes -4. **Bundle size**: Functions support up to 5 GB package size on Fluid Compute (up from 250 MB); request bodies up to 100 MB (up from 4.5 MB) -5. **Environment variables**: Available in all functions automatically; use `vercel env pull` for local dev +1. **`waitUntil` given a callback**: it takes a Promise. `waitUntil(fn())`, never `waitUntil(fn)` or `waitUntil(async () => {})` — the latter silently does nothing +2. **Cold starts with DB connections**: use connection pooling (e.g. Neon's `@neondatabase/serverless`) +3. **Reaching for the Edge runtime**: prefer Node.js — see [Prefer Node.js over the Edge runtime](#prefer-nodejs-over-the-edge-runtime) +4. **Timeout exceeded**: raise `maxDuration` (800s Pro/Ent, 1800s in beta), or move to Workflow for anything longer +5. **Bundle size**: standard limit is 250 MB uncompressed (500 MB Python). 5 GB needs the large functions beta, which existing projects must opt into with `VERCEL_SUPPORT_LARGE_FUNCTIONS=1` +6. **Payload size**: request and response bodies cap at **4.5 MB** (`413 FUNCTION_PAYLOAD_TOO_LARGE`) — use Blob client uploads or streaming, not a bigger function +7. **In-memory state**: Fluid shares instances across invocations and scales to zero — never keep sessions, rooms, or caches in process memory +8. **Setting `memory` in `vercel.json`**: with Fluid Compute enabled this is not the place for it and the build warns — set it in the dashboard +9. **Environment variables**: available in all functions automatically; use `vercel env pull` for local dev ## Function Runtime Diagnostics ### Timeout Diagnostics ``` -504 Gateway Timeout? +504 FUNCTION_INVOCATION_TIMEOUT? ├─ All plans default to 300s with Fluid Compute -├─ Pro/Enterprise: configurable up to 800s -├─ Long-running task? -│ ├─ Under 5 min → Use Fluid Compute with streaming -│ ├─ Up to 15 min → Use Vercel Functions with `maxDuration` in vercel.json -│ └─ Hours/days → Use Workflow DevKit (DurableAgent or workflow steps) -└─ DB query slow? → Add connection pooling, check cold start, use Edge Config +├─ How long does the work actually need? +│ ├─ ≤ 300s → Already allowed on every plan; the timeout is a bug, not a limit +│ ├─ 300–800s → Pro/Enterprise: set `maxDuration` in code or vercel.json +│ ├─ 800–1800s → Pro/Enterprise extended-duration beta (30 min) +│ │ ├─ Must be set PER FUNCTION — project defaults above 800s are ignored +│ │ ├─ Runtimes: nodejs20/22/24.x, Bun 1.x/1.4.x, python3.12/3.13/3.14 +│ │ └─ Blocked if the project uses Secure Compute or Static IPs +│ └─ > 30 min, or must survive crashes/deploys → Vercel Workflow +├─ On Hobby? → 300s is both default AND max; no extension exists, upgrade to Pro +├─ Client disconnected before the function finished? +│ └─ HTTP/1.1 drops idle connections → stream heartbeat/progress data +└─ DB query slow? → Add connection pooling, check cold start, use Global Config ``` ### 500 Error Diagnostics @@ -387,37 +912,55 @@ ``` "FUNCTION_INVOCATION_FAILED"? -├─ Memory exceeded? → Increase `memory` in vercel.json (up to 3008 MB on Pro) +├─ Memory exceeded (OOM)? +│ ├─ Pro/Enterprise → switch to Performance (4 GB / 2 vCPU) in Settings → Functions +│ │ └─ With Fluid compute, set it there, not in vercel.json (which warns at build) +│ └─ Hobby → fixed at 2 GB / 1 vCPU; reduce per-request memory or upgrade ├─ Crashed during init? → Check top-level await or heavy imports at module scope -└─ Edge Function crash? → Check for Node.js APIs not available in Edge runtime +├─ Build failed with "exceeded the unzipped maximum size of 250 MB"? +│ ├─ Trim with excludeFiles / outputFileTracingExcludes first +│ └─ Then large functions beta: VERCEL_SUPPORT_LARGE_FUNCTIONS=1 (5 GB, Node/Bun/Python) +├─ 413 FUNCTION_PAYLOAD_TOO_LARGE? → 4.5 MB body cap; use Blob client uploads or streaming +└─ Container image? → Is it listening on port 80 (or $PORT)? Is it holding state between requests? ``` ### Cold Start Diagnostics ``` Cold start latency > 1s? -├─ Using Node.js runtime? → Consider Edge Functions for latency-sensitive routes +├─ Moving to the Edge runtime is not the fix — Vercel recommends migrating off it +├─ Fluid Compute enabled? → Reuses warm instances across concurrent invocations +├─ Measuring in preview? → Bytecode caching is production-only; re-measure in prod ├─ Large function bundle? → Audit imports, use dynamic imports, tree-shake ├─ DB connection in cold start? → Use connection pooling (Neon serverless driver) -└─ Enable Fluid Compute to reuse warm instances across requests +└─ Container image? → Scales to zero after 5 min idle (30 s in preview); expect cold starts ``` ### Edge Function Timeout Diagnostics ``` "EDGE_FUNCTION_INVOCATION_TIMEOUT"? -├─ Edge Functions have 25s hard limit (not configurable) -├─ Move heavy computation to Node.js Serverless Functions -└─ Use streaming to start response early, process in background with `waitUntil` +├─ Edge must START the response within 25s (then may stream up to 300s) +├─ `maxDuration` does NOT apply to the Edge runtime — there is no way to raise this +├─ Recommended fix: drop `runtime = 'edge'` and run on Node.js +│ └─ Node.js gives you 300s by default, 800s on Pro/Ent, 1800s in the beta +└─ On Next.js 16.3+, `runtime = 'edge'` is unsupported — migration is required there ``` ## Official Documentation - [Vercel Functions](https://vercel.com/docs/functions) -- [Serverless Functions](https://vercel.com/docs/functions) -- [Edge Functions](https://vercel.com/docs/functions) +- [Functions limits](https://vercel.com/docs/functions/limitations) — duration, memory, bundle size, large functions +- [Configuring max duration](https://vercel.com/docs/functions/configuring-functions/duration) — including the extended 30-minute beta +- [Configuring memory / CPU](https://vercel.com/docs/functions/configuring-functions/memory) +- [Functions API reference](https://vercel.com/docs/functions/functions-api-reference) — `waitUntil`, `getDeadline`, SIGTERM, cancellation - [Fluid Compute](https://vercel.com/docs/fluid-compute) -- [Streaming](https://vercel.com/docs/functions/streaming) +- [Container Images](https://vercel.com/docs/functions/container-images) — Dockerfile on Vercel +- [Vercel Container Registry](https://vercel.com/docs/container-registry) and its [limits and pricing](https://vercel.com/docs/container-registry/limits-and-pricing) +- [Services](https://vercel.com/docs/services) — multiple backends/frontends in one project +- [Streaming](https://vercel.com/docs/functions/streaming-functions) - [WebSockets](https://vercel.com/docs/functions/websockets) - [Cron Jobs](https://vercel.com/docs/cron-jobs) +- [Vercel Workflow](https://vercel.com/docs/workflows) — for anything beyond 30 minutes +- [Edge Runtime](https://vercel.com/docs/functions/runtimes/edge) — legacy; Vercel recommends migrating to Node.js - [GitHub: Vercel](https://github.com/vercel/vercel)
Full snapshot data
{
"description": "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.",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 182
}
],
"name": "vercel-functions",
"skill_md_contents": "---\nname: vercel-functions\ndescription: 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.\nsummary: \"Vercel Functions run on Fluid Compute with Node.js as the default runtime — strongly prefer it over `runtime = 'edge'` (Vercel recommends migrating off Edge, and Next.js 16.3+ no longer supports it). Duration: 300s default on every plan including Hobby, 800s max on Pro/Enterprise, 1800s (30 min) per-function in the extended beta; beyond that use Vercel Workflow. Bundles: 250 MB standard (500 MB Python), 5 GB via the large functions beta (`VERCEL_SUPPORT_LARGE_FUNCTIONS=1`). Request/response bodies cap at 4.5 MB. Memory is dashboard-only (Standard 2 GB/1 vCPU, Performance 4 GB/2 vCPU; Hobby fixed). Docker works: add `Dockerfile.vercel` to run an OCI image as an autoscaling, stateless, scale-to-zero Function.\"\nmetadata:\n priority: 8\n docs:\n - \"https://vercel.com/docs/functions\"\n - \"https://vercel.com/docs/functions/runtimes\"\n - \"https://vercel.com/docs/functions/limitations\"\n - \"https://vercel.com/docs/functions/configuring-functions/duration\"\n - \"https://vercel.com/docs/functions/container-images\"\n - \"https://vercel.com/docs/fluid-compute\"\n - \"https://vercel.com/docs/functions/websockets\"\n sitemap: \"https://vercel.com/sitemap.xml\"\n pathPatterns:\n - 'api/**/*.*'\n - 'pages/api/**'\n - 'src/pages/api/**'\n - 'app/**/route.*'\n - 'src/app/**/route.*'\n - 'apps/*/api/**/*.*'\n - 'apps/*/app/**/route.*'\n - 'apps/*/src/app/**/route.*'\n - 'apps/*/pages/api/**'\n - 'vercel.json'\n - 'apps/*/vercel.json'\n - 'vercel.ts'\n - 'apps/*/vercel.ts'\n # Vercel builds `Dockerfile.vercel` / `Containerfile.vercel` into a\n # container-image Function, so these are Functions config, not generic Docker.\n - 'Dockerfile.vercel'\n - '*/Dockerfile.vercel'\n - 'Containerfile.vercel'\n - '*/Containerfile.vercel'\n bashPatterns:\n - '\\bvercel\\s+dev\\b'\n - '\\bvercel\\s+logs\\b'\n - '\\bvercel\\s+vcr\\b'\n importPatterns:\n - 'ws'\n - 'socket.io'\n - 'socket.io-client'\n promptSignals:\n phrases:\n - \"websocket\"\n - \"websockets\"\n - \"web socket\"\n - \"socket.io\"\n # Polling is the classic technique people reach for when they think Vercel\n # lacks websockets. We intentionally do NOT trigger on named third-party\n # services (Pusher, PubNub, Ably) — those are deliberate choices, not a\n # signal that someone is working around a missing feature.\n - \"long polling\"\n - \"long-polling\"\n # Duration: people hit the ceiling and ask about it in these words.\n - \"maxduration\"\n - \"max duration\"\n - \"function timeout\"\n - \"function times out\"\n - \"long-running function\"\n - \"long running function\"\n - \"504\"\n # Bundle size: the 250 MB error message is the usual entry point.\n - \"unzipped maximum size\"\n - \"250mb\"\n - \"250 mb\"\n - \"large function\"\n - \"bundle size limit\"\n # Containers: Dockerfile.vercel is a Functions feature, not just packaging.\n - \"dockerfile\"\n - \"container image\"\n - \"container registry\"\n - \"runtime edge\"\n - \"edge runtime\"\n allOf:\n - [\"docker\", \"vercel\"]\n - [\"hobby\", \"limit\"]\n anyOf:\n - \"realtime\"\n - \"bidirectional\"\n - \"ws server\"\n - \"polling\"\n - \"server-sent events\"\n - \"fluid compute\"\n - \"active cpu\"\n noneOf: []\n minScore: 6\nvalidate:\n -\n pattern: export\\s+default\\s+function\n message: 'Use named exports (GET, POST, PUT, DELETE) instead of default export for route handlers'\n severity: error\n # Skip on App Router page / layout / loading / error / not-found / sitemap / template / default files,\n # which require a default export by Next.js convention. Detected via the 'use client' directive,\n # an App Router config export (metadata, dynamic, revalidate, fetchCache, runtime), an `export default\n # function` whose name matches an App Router file (Page / Layout / Loading / etc.), or any JSX\n # element with a capitalised component tag — all signals that the file is a page-style file rather\n # than a route handler. See anthropics/claude-code#54989.\n skipIfFileContains: \"(?:^|\\\\n)\\\\s*['\\\"]use\\\\s+client['\\\"]|export\\\\s+const\\\\s+(?:metadata|dynamic|revalidate|fetchCache|runtime)\\\\b|export\\\\s+default\\\\s+(?:async\\\\s+)?function\\\\s+\\\\w*(?:Page|Layout|Loading|Error|NotFound|Sitemap|Template|Default|sitemap|robots|opengraph|manifest)\\\\b|<[A-Z][A-Za-z0-9]*|\\\\{\\\\s*children\\\\s*[,}:]|MetadataRoute\\\\.|from\\\\s+['\\\"]next/(?:font|image|link|navigation|headers|cookies)['\\\"]\"\n -\n pattern: NextApiRequest|NextApiResponse\n message: 'NextApiRequest/NextApiResponse are Pages Router types — use Web API Request/Response'\n severity: error\n -\n # Vercel's docs recommend migrating off the Edge runtime, and Next.js 16.3+\n # doesn't support it. Surfaced as a recommendation rather than an error:\n # existing Edge functions still work, so this is a nudge, not a blocker.\n # Matches `\"runtime\": \"edge\"` in vercel.json too.\n pattern: 'runtime[''\"]?\\s*[=:]\\s*[''\"]edge[''\"]'\n message: 'Consider dropping `runtime = \"edge\"`. Vercel recommends migrating from Edge to Node.js, and Next.js 16.3+ no longer supports it. Node.js on Fluid Compute runs in the same regions at the same price with full Node.js APIs, longer durations, and larger bundles.'\n severity: recommended\n -\n pattern: 'from\\s+[''\"](openai|@anthropic-ai/sdk|anthropic)[''\"]|new\\s+(OpenAI|Anthropic)\\('\n message: 'Direct AI provider SDK detected in route handler. Use the Vercel AI SDK for streaming, tools, and provider abstraction.'\n severity: recommended\n upgradeToSkill: ai-sdk\n upgradeWhy: 'Replace vendor-locked provider SDKs with @ai-sdk/openai or @ai-sdk/anthropic for unified streaming and tool support.'\n skipIfFileContains: '@ai-sdk/|from\\s+[''\"](ai)[''\"]|import.*from\\s+[''\"](ai)[''\"]|streamText|generateText'\n -\n pattern: 'setTimeout\\s*\\(|setInterval\\s*\\(|await\\s+new\\s+Promise\\s*\\([^)]*setTimeout'\n message: 'Long-running or polling logic detected in a serverless handler. Functions have execution time limits.'\n severity: recommended\n upgradeToSkill: workflow\n upgradeWhy: 'Move delayed/polling logic to Vercel Workflow for durable execution with pause, resume, retries, and crash safety.'\n skipIfFileContains: 'use workflow|use step'\n -\n pattern: 'writeFile(Sync)?\\(|createWriteStream\\(|from\\s+[''\"](multer|formidable)[''\"]|fs\\.writeFile'\n message: 'Local filesystem write detected. Serverless functions have ephemeral, read-only filesystems.'\n severity: error\n upgradeToSkill: vercel-storage\n upgradeWhy: 'Replace local filesystem writes with Vercel Blob, Neon, or Upstash for persistent, platform-native storage.'\n skipIfFileContains: '@vercel/blob|@upstash/|@neondatabase/'\n -\n pattern: 'export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE)\\b'\n message: 'Route handler has no observability instrumentation. Add logging and error tracking for production debugging.'\n severity: warn\n skipIfFileContains: 'console\\.error|logger\\.|captureException|Sentry|@vercel/otel|withTracing'\n -\n pattern: 'from\\s+[''\"\"](lru-cache|node-cache|memory-cache)[''\"\"]|new\\s+(LRUCache|NodeCache|Map)\\(\\s*\\).*cache'\n message: 'In-process memory cache detected in serverless function. Process memory is not shared across invocations.'\n severity: recommended\n upgradeToSkill: runtime-cache\n upgradeWhy: 'Replace in-process caches with Vercel Runtime Cache (getCache from @vercel/functions) for region-aware caching that persists across invocations.'\n skipIfFileContains: 'getCache|from\\s+[''\"\"]\\@vercel/functions[''\"\"]'\n -\n pattern: 'maxRetries\\s*[=:]|retryCount\\s*[=:]|retry\\s*\\(\\s*|for\\s*\\([^)]*retry|while\\s*\\([^)]*retry'\n message: 'Manual retry logic detected. Use Vercel Workflow SDK for automatic retries with durable execution.'\n severity: recommended\n upgradeToSkill: workflow\n upgradeWhy: 'Replace manual retry loops with Workflow SDK steps that provide automatic retries, crash safety, and observability.'\n skipIfFileContains: 'use workflow|use step|from\\s+[''\"\"](workflow)[''\"\"]'\n -\n pattern: 'from\\s+[''\"](express)[''\"\"]|require\\s*\\(\\s*[''\"](express)[''\"\"\\)]'\n message: 'Express.js detected in a Vercel project. Vercel Functions use the Web Request/Response API — Express middleware, req/res, and app.listen() do not work in serverless.'\n severity: recommended\n upgradeToSkill: vercel-functions\n upgradeWhy: 'Replace Express with Next.js route handlers (export async function GET/POST) or Vercel Functions using the Web Request/Response API.'\n skipIfFileContains: 'export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE)|from\\s+[''\"\"](next/server|@vercel/functions)[''\"\"]'\nretrieval:\n aliases:\n - serverless functions\n - api routes\n - edge functions\n - lambda\n - websockets\n - socket.io\n - docker\n - dockerfile\n - container images\n - function timeout\n - max duration\n - bundle size\n - hobby limits\n intents:\n - create serverless function\n - configure function runtime\n - optimize cold starts\n - add api route\n - serve a websocket connection\n - run a function for longer than 5 minutes\n - deploy a dockerfile\n - fix a function that exceeds the bundle size limit\n - check plan limits for functions\n entities:\n - Serverless Functions\n - Edge Functions\n - Fluid Compute\n - Long-duration functions\n - Large functions\n - Container Images\n - Vercel Container Registry\n - streaming\n - WebSockets\n - Cron Jobs\nchainTo:\n -\n pattern: 'from\\s+[''\\\"](openai|@anthropic-ai/sdk|anthropic)[''\"]|new\\s+(OpenAI|Anthropic)\\('\n targetSkill: ai-sdk\n message: 'Direct AI provider SDK in route handler — loading AI SDK guidance for unified streaming and tool support.'\n -\n pattern: 'setTimeout\\s*\\(|setInterval\\s*\\(|await\\s+new\\s+Promise\\s*\\([^)]*setTimeout'\n targetSkill: workflow\n message: 'Long-running or polling logic in serverless handler — loading Workflow SDK for durable execution.'\n -\n pattern: 'writeFile(Sync)?\\(|createWriteStream\\(|from\\s+[''\\\"](multer|formidable)[''\"]|fs\\.writeFile'\n targetSkill: vercel-storage\n message: 'Local filesystem write in serverless function — loading Vercel Storage guidance for platform-native persistence.'\n -\n pattern: 'from\\s+[''\"\"]@vercel/(postgres|kv)[''\"\"]'\n targetSkill: vercel-storage\n message: '@vercel/postgres and @vercel/kv are sunset — loading Vercel Storage guidance for Neon and Upstash migration.'\n -\n pattern: 'generateObject\\s*\\(|streamObject\\s*\\(|toDataStreamResponse|maxSteps\\b|CoreMessage\\b'\n targetSkill: ai-sdk\n message: 'Deprecated AI SDK v5 API detected — loading AI SDK guidance for migration.'\n -\n pattern: 'while\\s*\\(\\s*true\\s*\\)\\s*\\{|for\\s*\\(\\s*;\\s*;\\s*\\)\\s*\\{|setInterval\\s*\\(\\s*async'\n targetSkill: workflow\n message: 'Polling loop in serverless function detected — loading Workflow SDK for durable, crash-safe execution with pause/resume.'\n skipIfFileContains: \"use workflow|use step|from\\\\s+['\\\"]workflow['\\\"]\"\n -\n pattern: \"from\\\\s+['\\\"]express['\\\"]|require\\\\s*\\\\(\\\\s*['\\\"]express['\\\"]\"\n targetSkill: vercel-functions\n message: 'Express.js detected — loading Vercel Functions guidance for Web Request/Response API route handlers that replace Express middleware and routing.'\n skipIfFileContains: \"export\\\\s+(async\\\\s+)?function\\\\s+(GET|POST|PUT|PATCH|DELETE)\"\n -\n pattern: 'from\\s+[''\"\"](lru-cache|node-cache|memory-cache)[''\"\"]|new\\s+(LRUCache|NodeCache|Map)\\(\\s*\\).*cache'\n targetSkill: runtime-cache\n message: 'In-process memory cache in serverless function — loading Runtime Cache guidance for region-aware caching that persists across invocations.'\n skipIfFileContains: 'getCache|from\\s+[''\"\"]\\@vercel/functions[''\"\"]'\n -\n pattern: 'maxRetries\\s*[=:]|retryCount\\s*[=:]|retry\\s*\\(\\s*|for\\s*\\([^)]*retry|while\\s*\\([^)]*retry'\n targetSkill: workflow\n message: 'Manual retry logic in serverless handler — loading Workflow SDK guidance for automatic retries with durable execution.'\n skipIfFileContains: 'use workflow|use step|from\\s+[''\"\"](workflow)[''\"\"]'\n\n -\n pattern: 'import\\s*\\{[^}]*\\bmetric\\b[^}]*\\}\\s*from\\s*[''\"]@vercel/functions[''\"]'\n targetSkill: custom-metrics\n message: '@vercel/functions metric() detected — loading Custom Metrics guidance for naming, attributes, and vc metrics queries.'\n\n---\n\n# Vercel Functions\n\nYou are an expert in Vercel Functions — the compute layer of the Vercel platform.\n\n## Prefer Node.js over the Edge runtime\n\n**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.**\n\nThis 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.\n\nEverywhere 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.\n\n### The default to reach for\n\n```ts\n// app/api/hello/route.ts — no runtime export needed.\nexport async function GET() {\n return Response.json({ message: 'Hello from Node.js on Fluid Compute' })\n}\n```\n\nNode.js is the default. Omit `export const runtime` entirely rather than writing `export const runtime = 'nodejs'`.\n\n### Reasons people reach for Edge — and what to do instead\n\n| \"I need Edge because…\" | Reality | Do this instead |\n|---|---|---|\n| \"…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 |\n| \"…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 |\n| \"…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) |\n| \"…it's cheaper\" | Identical Active CPU pricing | Stay on Node.js |\n| \"…it has faster cold starts\" | Fluid Compute reuses warm instances across concurrent invocations and bytecode-caches Node 20+ in production | Stay on Node.js |\n| \"…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 |\n\n### What Edge actually costs you\n\n- No `fs`, no native modules, no `require()` — ESM only, and most npm packages with Node.js dependencies simply will not load\n- No `eval` / `new Function` / dynamic `WebAssembly.instantiate`\n- **Code size limit after gzip: 1 MB (Hobby), 2 MB (Pro), 4 MB (Enterprise)** — versus 250 MB uncompressed (up to 5 GB) on Node.js\n- Must begin sending a response within **25 seconds** (it may then stream for up to 300s). The 300s/800s/1800s duration limits below apply to the Node.js, Bun, and Python runtimes — **not** to Edge\n- No long-duration or large-function support of any kind\n\n### Migrating an existing Edge function\n\nWorth 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.\n\n1. Remove `export const runtime = 'edge'` (or `runtime: 'edge'` in `vercel.json` / the `config` object).\n2. Replace `next/server` Edge-only imports where applicable; the Web `Request`/`Response` handler signature is unchanged, so most route handlers need no other edit.\n3. If you pinned execution with the Edge-only `preferredRegion`, use `regions` in `vercel.json` instead.\n4. Confirm Fluid Compute is on (default since April 23, 2025) and redeploy.\n\nThere is no rollback story to plan for: Node.js is a superset of what the function could do on Edge.\n\n## Function Types\n\n### Node.js (the default)\n- Full Node.js runtime, all npm packages available\n- Default for Next.js route handlers, Server Actions, Server Components, and any file in `/api`\n- **Node.js 24 LTS is GA** for builds and functions (V8 13.6, global `URLPattern`, Undici v7, npm v11). **Node.js 20 is deprecated on October 1, 2026** — move off `nodejs20.x`\n- Duration: 300s default on every plan; 800s max on Pro/Enterprise; 1800s with the extended-duration beta\n\n### Bun\nAdd `\"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.\n\n### Python\nPython 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.\n\n### Rust\nRust functions run on Fluid Compute with HTTP streaming and Active CPU pricing. Official runtime (Beta) built on the `vercel_runtime` crate. Supports environment variables up to 64 KB.\n\n### Container images (Docker)\nAny OCI image via `Dockerfile.vercel`. See [Docker and Container Images](#docker-and-container-images) below.\n\n### Edge (legacy — not recommended)\nV8 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](#prefer-nodejs-over-the-edge-runtime).\n\n### Choosing a Runtime\n\n| Need | Runtime | Why |\n|------|---------|-----|\n| Anything not listed below | `nodejs` | The default, and correct nearly always |\n| Full Node.js APIs, npm packages | `nodejs` | Full compatibility |\n| AI streaming, SSE, WebSockets | `nodejs` | Zero-config streaming, long durations |\n| Lower latency, CPU-bound work | `nodejs` + Bun | ~28% latency reduction |\n| Database connections, heavy deps | `nodejs` | Pin `regions` next to the database |\n| Data/ML libraries, big model files | `nodejs` or `python` + large functions | Up to 5 GB bundles |\n| Systems-level performance | `rust` | Native speed on Fluid Compute |\n| Custom system libraries (FFmpeg, Chromium), Go/Ruby/PHP, unsupported frameworks | container image | Bring your own Dockerfile |\n| Auth, redirects, A/B tests before the cache | Routing Middleware | Runs on Node.js, framework-agnostic |\n| Hours-to-months of execution | Vercel Workflow | Durable steps, no duration limit |\n\n`edge` is deliberately absent: there's no row here where it's the better answer for new code.\n\n## Fluid Compute\n\nFluid 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.\n\nLong-duration, large-function, and container-image support all depend on it.\n\nKey behaviors:\n- **Optimized concurrency**: multiple invocations share one instance instead of one microVM per request. Vercel prioritizes idle existing resources before allocating new ones. Available on the Node.js and Python runtimes.\n- **Active CPU pricing**: you are billed for CPU time your code actually consumes, plus provisioned memory while requests are in flight, plus invocations. Waiting on I/O (AI models, DB queries) does not accrue Active CPU — which is what makes 30-minute functions affordable.\n- **Automatic cold start optimization**: function pre-warming plus **bytecode caching** on Node.js 20+. Bytecode caching applies to **production only** — not dev or preview, so don't benchmark cold starts in a preview deployment.\n- **Error isolation**: an uncaught exception or unhandled rejection is logged and in-flight requests are allowed to finish; one broken request will not crash its neighbors on the same instance.\n- **Cross-AZ and cross-region failover**: fails over to another availability zone in-region first, then to the next closest region.\n- **Graceful shutdown**: `SIGTERM` before termination (see below).\n\n### Instance Sizes (memory / CPU)\n\n| Type | Memory / CPU | Use |\n|------|--------------|-----|\n| Standard (default) | 2 GB / 1 vCPU | Predictable performance for production workloads |\n| Performance | 4 GB / 2 vCPU | Latency-sensitive applications and SSR workloads |\n\n- **With Fluid Compute enabled, memory cannot be set in `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.)\n- **Pro/Enterprise only.** Hobby always runs Standard (2 GB / 1 vCPU) and cannot configure it. The Basic instance has been removed.\n- More memory also means more CPU, which can *reduce* Active CPU billing for CPU-bound work by finishing sooner — but it raises Provisioned Memory cost while requests are in flight.\n- Projects created before 2019-11-08 may still sit on legacy sizes (1024 MB / 0.6 vCPU on Hobby, 3008 MB / 1.67 vCPU on Pro) until you pick a size in the dashboard.\n\n### Settings precedence\n\nFunction code (`export const maxDuration`) → `vercel.json` → dashboard → Fluid defaults. Later entries lose.\n\n### Background Processing with `waitUntil`\n\n`waitUntil` takes a **Promise**, not a callback. Passing a function does nothing — a common and silent bug.\n\n```ts\nimport { waitUntil } from '@vercel/functions'\n\nexport async function POST(req: Request) {\n const data = await req.json()\n\n // Correct: invoke the async work and hand over the promise.\n waitUntil(processAnalytics(data))\n\n // For several tasks, combine them:\n waitUntil(Promise.all([sendNotification(data), updateCache(data)]))\n\n return Response.json({ received: true })\n}\n```\n\n### Next.js `after` (equivalent)\n\n```ts\nimport { after } from 'next/server'\n\nexport async function POST(req: Request) {\n const data = await req.json()\n\n after(async () => {\n await logToAnalytics(data)\n })\n\n return Response.json({ ok: true })\n}\n```\n\n### Graceful shutdown and request cancellation\n\n```ts\n// Runs on scale-down. 500 ms to clean up (30 s for container images).\nprocess.on('SIGTERM', () => {\n // flush buffers, close pools\n})\n```\n\nRequest 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.\n\n```json filename=\"vercel.json\"\n{\n \"functions\": {\n \"api/*\": { \"supportsCancellation\": true }\n }\n}\n```\n\n```ts\nexport async function GET(request: Request) {\n // Pass the signal through so upstream work stops too.\n const res = await fetch('https://upstream.example.com', { signal: request.signal })\n return new Response(res.body, { status: res.status })\n}\n```\n\n## Duration and Long-Duration Functions\n\n### Duration limits\n\nWith Fluid Compute (default), per [Vercel's limits](https://vercel.com/docs/functions/limitations#max-duration):\n\n| Plan | Default | Maximum | Extended maximum |\n|------|---------|---------|------------------|\n| Hobby | 300s (5 min) | 300s (5 min) | — |\n| Pro | 300s (5 min) | 800s | 1800s (30 min) — Beta |\n| Enterprise | 300s (5 min) | 800s | 1800s (30 min) — Beta |\n\nThe 800s maximum is **generally available** on Pro and Enterprise. The 1800s extended maximum is **in beta**. Exceeding the limit returns `504 FUNCTION_INVOCATION_TIMEOUT`.\n\n**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.\n\n### Setting `maxDuration`\n\n```ts\n// app/api/report/route.ts — Next.js App Router (and Node.js, SvelteKit, Astro,\n// Nuxt, Remix via their own config). Value is in seconds.\nexport const maxDuration = 800\n\nexport async function POST(request: Request) {\n return Response.json({ ok: true })\n}\n```\n\nFor other frameworks and runtimes — Next.js < 13.5, Rust, Go, Python, Ruby — use `vercel.json`:\n\n```json\n{\n \"$schema\": \"https://openapi.vercel.sh/vercel.json\",\n \"functions\": {\n \"api/long-task.py\": { \"maxDuration\": 1800 }\n }\n}\n```\n\nGlob 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.\n\nTo change the project-wide default: Settings → Functions → **Function Max Duration**.\n\n### Extended max duration (30 minutes) — Beta\n\nPro and Enterprise teams can run individual functions for up to **1800s**. Requirements, all of which are load-bearing:\n\n- **Per-function configuration only.** Durations above 800s must be set in code or in `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.\n- **Supported runtimes only**: `nodejs20.x`, `nodejs22.x`, `nodejs24.x`, Bun `1.x` and `1.4.x`, `python3.12`, `python3.13`, `python3.14`.\n- **Fluid Compute must be enabled** (default for new projects).\n- **Secure Compute and Static IPs do not support durations above 800s** during the beta. If the project uses either, you are capped at 800s.\n\n```ts\n// app/api/long-task/route.ts\nexport const maxDuration = 1800 // 30 minutes\n\nexport async function POST(request: Request) {\n await doTheLongThing()\n return Response.json({ ok: true })\n}\n```\n\n### Keeping a long request alive\n\nOver 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.\n\nUse `getDeadline()` to find out how much time is actually left and bail out cleanly:\n\n```ts\nimport { getDeadline } from '@vercel/functions'\n\nconst deadline = getDeadline() // Date | undefined (undefined outside the Vercel Functions runtime)\nconst msRemaining = deadline ? deadline.getTime() - Date.now() : Infinity\n```\n\n### Cost of long functions\n\nActive 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.\n\n### When 30 minutes is not enough\n\nDo 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:\n\n- ≤ 300s → any plan, no configuration needed\n- 300–800s → Pro/Enterprise, set `maxDuration`\n- 800–1800s → Pro/Enterprise, extended-duration beta, per-function config\n- Beyond 30 min, or needs to survive a crash/deploy → Vercel Workflow (`workflow` skill)\n\nWorkflow steps themselves support extended function durations, so a single step can also run up to 30 minutes.\n\n## Large Functions (bundle size)\n\n### Standard limits\n\n| Runtime | Uncompressed bundle limit |\n|---------|---------------------------|\n| Node.js, Bun, Rust, Go | 250 MB (includes runtime layers) |\n| Python | 500 MB |\n| Edge runtime | 1 MB Hobby / 2 MB Pro / 4 MB Enterprise, **after gzip** |\n\nBlowing the limit fails the build with `Serverless Function has exceeded the unzipped maximum size of 250 MB`.\n\n### Large functions — Beta\n\nLarge 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.\n\n- **Runtimes**: Node.js, Bun, Python.\n- **Requires Fluid Compute with Active CPU** enabled (default for new projects).\n- **New projects are eligible by default.** Existing projects opt in with the `VERCEL_SUPPORT_LARGE_FUNCTIONS` environment variable, then redeploy:\n\n```bash\nvercel env add VERCEL_SUPPORT_LARGE_FUNCTIONS # value: 1 (use 0 to disable)\n```\n\nThe environment variable always takes precedence over the project default, in both directions.\n\n- **Only functions that exceed the standard limit use the large-function path** — everything under 250 MB keeps the normal, faster path, so enabling it is not a global performance trade.\n- **Not supported with Secure Compute or Static IPs.**\n\n### Shrinking a bundle first\n\nA 5 GB function still costs you cold-start time. Trim before you opt in:\n\nIn `vercel.json` (not supported in Next.js — see below):\n\n```json filename=\"vercel.json\"\n{\n \"functions\": {\n \"api/**/*.py\": {\n \"excludeFiles\": \"{tests/**,__tests__/**,**/*.test.py,fixtures/**,testdata/**}\"\n }\n }\n}\n```\n\n- Next.js ignores `includeFiles`/`excludeFiles` — use `outputFileTracingIncludes` / `outputFileTracingExcludes` in `next.config.js` instead.\n- Audit heavy imports, prefer dynamic `import()`, and check for a package in `dependencies` that belongs in `devDependencies`.\n\n### Request and response payloads\n\nBundle 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:\n\n- **Uploads** → Vercel Blob **client uploads**, which send the file browser → Blob directly, bypassing the function\n- **Large responses** → stream them; streamed responses are not subject to the limit\n- Otherwise, chunk across multiple requests\n\n## Docker and Container Images\n\nVercel 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.\n\n### Quick start\n\nCreate `Dockerfile.vercel` (or `Containerfile.vercel`) at the project root. Vercel detects it automatically and adds a rewrite routing all traffic to the image.\n\n```docker\n# Dockerfile.vercel\nFROM node:26-alpine\n\nRUN npm i -g srvx\nWORKDIR /app\nCOPY server.ts .\n\n# srvx listens on $PORT by default\nCMD [\"srvx\", \"--prod\"]\n```\n\n```ts\n// server.ts\nexport default {\n fetch(req: Request) {\n return Response.json({ ip: req.headers.get('x-forwarded-for') })\n },\n}\n```\n\nDeploy with `vercel deploy` or a Git push. During the build, the image is built and pushed to [Vercel Container Registry (VCR)](https://vercel.com/docs/container-registry).\n\n### The rules that actually bite\n\n- **Serve HTTP on port 80**, or override with the `PORT` environment variable in project settings. A container that doesn't listen gets no traffic.\n- **Containers must be stateless.** Each instance takes a request, returns a response, and keeps nothing between calls — that is what allows autoscaling and scale-to-zero. Persist to a Marketplace database, Redis, or Blob; never to the container filesystem.\n- **Scale to zero** after 5 minutes without traffic in production, 30 seconds in preview. Cold starts are real; do not assume a warm process.\n- **`SIGTERM` with a 30-second grace period** on scale-down (regular functions get 500 ms). Use it to drain.\n- **Logs are not per-request.** `stdout`/`stderr` are broadcast to all inflight requests of the instance, so correlate with your own request IDs.\n- **Same Function limits and Active CPU pricing** apply for size, memory, and duration.\n- **Secure Compute and Static IPs are not supported** with custom container images. If you need either, deploy that part without a container.\n- **Local dev**: `vercel dev` runs the image and requires the `docker` CLI plus a running daemon.\n\n### Multiple services in one project\n\nUse [Services](https://vercel.com/docs/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`.\n\n```json filename=\"vercel.json\"\n{\n \"services\": {\n \"frontend\": { \"runtime\": \"container\", \"root\": \"frontend/\", \"entrypoint\": \"Dockerfile.vercel\" },\n \"backend\": { \"runtime\": \"container\", \"root\": \"backend/\", \"entrypoint\": \"Dockerfile.vercel\" }\n },\n \"rewrites\": [\n { \"source\": \"/api/(.*)\", \"destination\": { \"service\": \"backend\" } },\n { \"source\": \"/(.*)\", \"destination\": { \"service\": \"frontend\" } }\n ]\n}\n```\n\nServices 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.\n\n### Vercel Container Registry (VCR)\n\n```bash\nvercel vcr login docker # authenticate Docker with a short-lived OIDC token\nvercel vcr image ls my-app # list images\nvercel vcr image inspect my-app <id>\nvercel vcr image rm my-app <id>\n```\n\nRegistry limits: 2 GB 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.\n\n### When to reach for a container\n\nGood 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.\n\nPoor 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.\n\nIf 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.\n\n## Plan Limits at a Glance\n\n| | Hobby | Pro | Enterprise |\n|---|---|---|---|\n| Duration (default / max) | 300s / **300s** | 300s / 800s | 300s / 800s |\n| Extended duration (beta) | — | 1800s | 1800s |\n| Memory / CPU | 2 GB / 1 vCPU, not configurable | Standard or Performance (4 GB / 2 vCPU) | Standard or Performance |\n| Bundle size | 250 MB (500 MB Python), 5 GB with large functions beta | same | same |\n| Concurrency | auto-scales to 30,000 | 30,000 | 100,000+ |\n| Regions | single region | up to 3 | all |\n| Edge code size (gzipped) | 1 MB | 2 MB | 4 MB |\n| VCR repos per project | 10 | 1,000 | 5,000 |\n| Request/response body | 4.5 MB | 4.5 MB | 4.5 MB |\n\n### What changed for Hobby\n\nHobby function limits went **up substantially** with Fluid Compute, and stale 10s/60s numbers are a common source of bad advice:\n\n- **Duration: 60s → 300s for both the default and the maximum** — a 5× increase. Hobby functions can run a full five minutes.\n- **CPU: the Basic instance was removed; Hobby now runs Standard**, 1 vCPU / 2 GB (up from 1 vCPU / 1.7 GB), managed by Vercel with a minimum of 1 vCPU.\n- Hobby still cannot configure memory/CPU, use the extended 30-minute duration, or run in multiple regions — those remain Pro/Enterprise.\n\n## Streaming\n\nZero-config streaming on the default Node.js runtime, including Server-Sent Events (SSE). Essential for AI applications.\n\n> **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.\n\n```ts\nexport async function POST(req: Request) {\n const encoder = new TextEncoder()\n const stream = new ReadableStream({\n async start(controller) {\n for (const chunk of data) {\n controller.enqueue(encoder.encode(chunk))\n await new Promise(r => setTimeout(r, 100))\n }\n controller.close()\n },\n })\n\n return new Response(stream, {\n headers: { 'Content-Type': 'text/event-stream' },\n })\n}\n```\n\nFor AI streaming, use the AI SDK's `toUIMessageStreamResponse()` (for chat UIs with `useChat`) which handles SSE formatting automatically.\n\n## WebSockets\n\nVercel 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.\n\n**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.\n\n### `ws` (no extra config)\n\nWebSockets work like any distributed WebSocket server — export an `http.Server` and use a library such as `ws`:\n\n```ts\n// api/ws.ts\nimport http from 'http'\nimport { WebSocketServer } from 'ws'\n\nconst server = http.createServer()\nconst wss = new WebSocketServer({ server })\n\nwss.on('connection', (ws) => {\n ws.on('message', (data) => ws.send(data)) // echo\n})\n\nexport default server\n```\n\n### Socket.IO\n\nHigher-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:\n\n```ts\n// api/socket-io.ts\nimport http from 'http'\nimport { Server } from 'socket.io'\n\nconst server = http.createServer()\nconst io = new Server(server)\n\nio.on('connection', (socket) => {\n socket.on('message', (data) => socket.send(data))\n})\n\nexport default server\n```\n\n```ts\n// client.ts\nimport { io } from 'socket.io-client'\n\nconst socket = io('https://your-domain.com', {\n // Socket.IO appends /socket.io, so the full path becomes /api/socket-io/socket.io\n path: '/api/socket-io/socket.io',\n transports: ['websocket'], // required — Socket.IO defaults to HTTP long-polling\n})\n```\n\nExpress, 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.\n\n### Next.js\n\nNext.js doesn't expose an API for handling WebSocket upgrades. Use `experimental_upgradeWebSocket()` from `@vercel/functions` inside a route handler:\n\n```ts\n// app/api/ws/route.ts\nimport { experimental_upgradeWebSocket, type WebSocketData } from '@vercel/functions'\n\nexport async function GET() {\n return experimental_upgradeWebSocket((ws) => {\n ws.on('message', (data: WebSocketData) => ws.send(data))\n })\n}\n```\n\n### Reconnects and persistent state\n\n- **Connections close when the function reaches its max duration.** Clients must reconnect with backoff, then resubscribe to channels and reload any state they need.\n- **No instance affinity across connections.** A reconnect — or a new deployment — may land on a different instance, so never keep durable state, presence, rooms, or pub/sub coordination in memory. Use an external store such as [Redis from the Marketplace](https://vercel.com/marketplace/redis).\n\n```ts\n// client.ts — reconnect with exponential backoff\nlet socket: WebSocket\nlet delay = 1000\n\nfunction connect() {\n socket = new WebSocket('wss://your-domain.com/api/ws')\n socket.addEventListener('open', () => { delay = 1000 })\n socket.addEventListener('message', (e) => console.log(e.data))\n socket.addEventListener('close', () => {\n setTimeout(connect, delay)\n delay = Math.min(delay * 2, 30000)\n })\n}\n\nconnect()\n```\n\n## Cron Jobs\n\nSchedule function invocations via `vercel.json`:\n\n```json filename=\"vercel.json\"\n{\n \"crons\": [\n {\n \"path\": \"/api/daily-report\",\n \"schedule\": \"0 8 * * *\"\n },\n {\n \"path\": \"/api/cleanup\",\n \"schedule\": \"0 */6 * * *\"\n }\n ]\n}\n```\n\nThe cron endpoint receives a normal HTTP request. Verify it's from Vercel:\n\n```ts\nexport async function GET(req: Request) {\n const authHeader = req.headers.get('authorization')\n if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {\n return new Response('Unauthorized', { status: 401 })\n }\n // Do scheduled work\n return Response.json({ ok: true })\n}\n```\n\n## Configuration\n\n`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).\n\n```ts\n// vercel.ts\nimport type { VercelConfig } from '@vercel/config/v1'\n\nexport const config: VercelConfig = {\n functions: {\n 'app/api/heavy/**': { maxDuration: 800 },\n 'app/api/report/**': { maxDuration: 1800 }, // Pro/Ent extended-duration beta\n },\n crons: [{ path: '/api/cleanup', schedule: '0 0 * * *' }],\n}\n```\n\nThe `vercel.json` equivalent:\n\n```json\n{\n \"$schema\": \"https://openapi.vercel.sh/vercel.json\",\n \"functions\": {\n \"app/api/heavy/**\": { \"maxDuration\": 800 },\n \"api/upload.js\": { \"supportsCancellation\": true }\n }\n}\n```\n\nWhat you **cannot** put here:\n- `memory` — with Fluid Compute (the default), set it in the dashboard; Pro/Enterprise only, and `vercel.json` warns at build time\n- A project-wide default above 800s — extended durations are per-function only\n\n`runtime: \"edge\"` is accepted here, but prefer leaving it out — see [Prefer Node.js over the Edge runtime](#prefer-nodejs-over-the-edge-runtime).\n\n## Common Pitfalls\n\n1. **`waitUntil` given a callback**: it takes a Promise. `waitUntil(fn())`, never `waitUntil(fn)` or `waitUntil(async () => {})` — the latter silently does nothing\n2. **Cold starts with DB connections**: use connection pooling (e.g. Neon's `@neondatabase/serverless`)\n3. **Reaching for the Edge runtime**: prefer Node.js — see [Prefer Node.js over the Edge runtime](#prefer-nodejs-over-the-edge-runtime)\n4. **Timeout exceeded**: raise `maxDuration` (800s Pro/Ent, 1800s in beta), or move to Workflow for anything longer\n5. **Bundle size**: standard limit is 250 MB uncompressed (500 MB Python). 5 GB needs the large functions beta, which existing projects must opt into with `VERCEL_SUPPORT_LARGE_FUNCTIONS=1`\n6. **Payload size**: request and response bodies cap at **4.5 MB** (`413 FUNCTION_PAYLOAD_TOO_LARGE`) — use Blob client uploads or streaming, not a bigger function\n7. **In-memory state**: Fluid shares instances across invocations and scales to zero — never keep sessions, rooms, or caches in process memory\n8. **Setting `memory` in `vercel.json`**: with Fluid Compute enabled this is not the place for it and the build warns — set it in the dashboard\n9. **Environment variables**: available in all functions automatically; use `vercel env pull` for local dev\n\n## Function Runtime Diagnostics\n\n### Timeout Diagnostics\n\n```\n504 FUNCTION_INVOCATION_TIMEOUT?\n├─ All plans default to 300s with Fluid Compute\n├─ How long does the work actually need?\n│ ├─ ≤ 300s → Already allowed on every plan; the timeout is a bug, not a limit\n│ ├─ 300–800s → Pro/Enterprise: set `maxDuration` in code or vercel.json\n│ ├─ 800–1800s → Pro/Enterprise extended-duration beta (30 min)\n│ │ ├─ Must be set PER FUNCTION — project defaults above 800s are ignored\n│ │ ├─ Runtimes: nodejs20/22/24.x, Bun 1.x/1.4.x, python3.12/3.13/3.14\n│ │ └─ Blocked if the project uses Secure Compute or Static IPs\n│ └─ > 30 min, or must survive crashes/deploys → Vercel Workflow\n├─ On Hobby? → 300s is both default AND max; no extension exists, upgrade to Pro\n├─ Client disconnected before the function finished?\n│ └─ HTTP/1.1 drops idle connections → stream heartbeat/progress data\n└─ DB query slow? → Add connection pooling, check cold start, use Global Config\n```\n\n### 500 Error Diagnostics\n\n```\n500 Internal Server Error?\n├─ Check Vercel Runtime Logs (Dashboard → Deployments → Functions tab)\n├─ Missing env vars? → Compare `.env.local` against Vercel dashboard settings\n├─ Import error? → Verify package is in `dependencies`, not `devDependencies`\n└─ Uncaught exception? → Wrap handler in try/catch, use `after()` for error reporting\n```\n\n### Invocation Failure Diagnostics\n\n```\n\"FUNCTION_INVOCATION_FAILED\"?\n├─ Memory exceeded (OOM)?\n│ ├─ Pro/Enterprise → switch to Performance (4 GB / 2 vCPU) in Settings → Functions\n│ │ └─ With Fluid compute, set it there, not in vercel.json (which warns at build)\n│ └─ Hobby → fixed at 2 GB / 1 vCPU; reduce per-request memory or upgrade\n├─ Crashed during init? → Check top-level await or heavy imports at module scope\n├─ Build failed with \"exceeded the unzipped maximum size of 250 MB\"?\n│ ├─ Trim with excludeFiles / outputFileTracingExcludes first\n│ └─ Then large functions beta: VERCEL_SUPPORT_LARGE_FUNCTIONS=1 (5 GB, Node/Bun/Python)\n├─ 413 FUNCTION_PAYLOAD_TOO_LARGE? → 4.5 MB body cap; use Blob client uploads or streaming\n└─ Container image? → Is it listening on port 80 (or $PORT)? Is it holding state between requests?\n```\n\n### Cold Start Diagnostics\n\n```\nCold start latency > 1s?\n├─ Moving to the Edge runtime is not the fix — Vercel recommends migrating off it\n├─ Fluid Compute enabled? → Reuses warm instances across concurrent invocations\n├─ Measuring in preview? → Bytecode caching is production-only; re-measure in prod\n├─ Large function bundle? → Audit imports, use dynamic imports, tree-shake\n├─ DB connection in cold start? → Use connection pooling (Neon serverless driver)\n└─ Container image? → Scales to zero after 5 min idle (30 s in preview); expect cold starts\n```\n\n### Edge Function Timeout Diagnostics\n\n```\n\"EDGE_FUNCTION_INVOCATION_TIMEOUT\"?\n├─ Edge must START the response within 25s (then may stream up to 300s)\n├─ `maxDuration` does NOT apply to the Edge runtime — there is no way to raise this\n├─ Recommended fix: drop `runtime = 'edge'` and run on Node.js\n│ └─ Node.js gives you 300s by default, 800s on Pro/Ent, 1800s in the beta\n└─ On Next.js 16.3+, `runtime = 'edge'` is unsupported — migration is required there\n```\n\n## Official Documentation\n\n- [Vercel Functions](https://vercel.com/docs/functions)\n- [Functions limits](https://vercel.com/docs/functions/limitations) — duration, memory, bundle size, large functions\n- [Configuring max duration](https://vercel.com/docs/functions/configuring-functions/duration) — including the extended 30-minute beta\n- [Configuring memory / CPU](https://vercel.com/docs/functions/configuring-functions/memory)\n- [Functions API reference](https://vercel.com/docs/functions/functions-api-reference) — `waitUntil`, `getDeadline`, SIGTERM, cancellation\n- [Fluid Compute](https://vercel.com/docs/fluid-compute)\n- [Container Images](https://vercel.com/docs/functions/container-images) — Dockerfile on Vercel\n- [Vercel Container Registry](https://vercel.com/docs/container-registry) and its [limits and pricing](https://vercel.com/docs/container-registry/limits-and-pricing)\n- [Services](https://vercel.com/docs/services) — multiple backends/frontends in one project\n- [Streaming](https://vercel.com/docs/functions/streaming-functions)\n- [WebSockets](https://vercel.com/docs/functions/websockets)\n- [Cron Jobs](https://vercel.com/docs/cron-jobs)\n- [Vercel Workflow](https://vercel.com/docs/workflows) — for anything beyond 30 minutes\n- [Edge Runtime](https://vercel.com/docs/functions/runtimes/edge) — legacy; Vercel recommends migrating to Node.js\n- [GitHub: Vercel](https://github.com/vercel/vercel)\n"
}SHA-256 of public snapshot: 43313075f379edfe1fd9c7541e36103a5b66f49f01b4e51f9df56e17812c0cf0