infra-platform-vercel
Vercel deployment platform — project configuration, serverless/edge functions, Routing Middleware, cron jobs, environment variables, monorepo setup
Install
npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/infra-platform-vercel/skills/infra-platform-vercel
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
git clone https://github.com/agents-inc/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Vercel Platform Patterns
Quick Guide: Configure deployments with
vercel.json(static) orvercel.ts(programmatic, build-time). Functions default to Node.js runtime iniad1region. Useexport const runtime = 'edge'for edge functions (V8 isolates, global deployment). Routing Middleware runs before the cache globally. Secure cron jobs withCRON_SECRET. Enable Fluid compute for better concurrency and cost. Always place functions near your data source.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST always include "$schema": "https://openapi.vercel.sh/vercel.json" in vercel.json for IDE validation)
(You MUST place functions in a region close to your data source -- the default iad1 may add latency if your database is elsewhere)
(You MUST verify CRON_SECRET in cron job handlers -- Vercel cron endpoints are publicly accessible URLs)
(You MUST design cron jobs to be idempotent -- Vercel may deliver the same cron event more than once)
(You MUST NOT store secrets in vercel.json or source code -- use Environment Variables in the Vercel dashboard)
</critical_requirements>
Examples
- Core Configuration & Functions -- vercel.json schema, functions config, runtime selection, regions, environment variables, Fluid compute
- Routing & Middleware -- Routing Middleware, headers, redirects, rewrites, conditional routing, geo-routing
- Cron Jobs & Scheduling -- cron configuration, CRON_SECRET verification, idempotent handlers
- Monorepo & Advanced -- monorepo setup, vercel.ts programmatic config, ignoreCommand, image optimization
- Quick Reference -- vercel.json property reference, plan limits, region IDs, CLI commands
Auto-detection: Vercel, vercel.json, vercel.ts, @vercel/config, Vercel Functions, Vercel deploy, VERCEL_URL, VERCEL_ENV, VERCEL_REGION, Routing Middleware, middleware.ts, Edge Runtime, export const runtime, Fluid compute, vercel cron, cron jobs vercel, vercel.json crons, vercel dev, vercel build, vercel deploy, vercel env, vercel link, vercel pull, .vercelignore, vercel monorepo, vercel regions, vercel headers, vercel redirects, vercel rewrites
When to use:
- Configuring Vercel project settings via
vercel.jsonorvercel.ts - Deploying serverless functions (Node.js or Edge runtime)
- Setting up Routing Middleware for auth, geo-routing, or A/B testing
- Configuring cron jobs for scheduled tasks
- Managing environment variables across preview/production
- Setting up monorepo deployments with per-app configuration
- Configuring headers, redirects, rewrites, and URL routing
- Choosing function regions and memory/duration limits
When NOT to use:
- Long-running background jobs exceeding plan limits (use a dedicated job runner)
- Workloads requiring persistent WebSocket connections (Vercel functions are request/response)
- Applications needing custom server runtimes beyond Node.js/Edge/Bun/Python/Go/Ruby
Key patterns covered:
vercel.json/vercel.tsproject configuration with IDE schema validation- Serverless function configuration (runtime, memory, maxDuration, regions)
- Edge Runtime vs Node.js runtime tradeoffs
- Routing Middleware (runs before cache, global edge execution)
- Cron jobs with
CRON_SECRETauthentication - Headers, redirects, rewrites with conditional matching (
has/missing) - Monorepo setup with root directory and ignored build steps
- Fluid compute for improved concurrency and cost efficiency
- Environment variables (
VERCEL_ENV,VERCEL_URL,VERCEL_REGION)
<decision_framework>
Decision Framework
Choosing a Runtime
What does your function need?
|
+-- Full Node.js APIs (fs, child_process, native modules)?
| --> Node.js runtime
|
+-- Global low-latency, minimal dependencies?
| --> Edge runtime (but consider Node.js + multi-region)
|
+-- Heavy computation or large dependencies?
| --> Node.js runtime (250 MB limit vs 1-4 MB Edge)
|
+-- Not sure?
--> Node.js (default, recommended by Vercel for most cases)
Choosing Where to Put Logic
Does it need to run before every request (auth, redirects)?
|
+-- YES --> Routing Middleware (middleware.ts)
|
+-- NO --> Is it a scheduled task?
|
+-- YES --> Cron job (vercel.json crons + api route)
|
+-- NO --> Is it an API endpoint?
|
+-- YES --> Vercel Function (api/ directory)
|
+-- NO --> Is it a static routing rule?
|
+-- YES --> vercel.json (headers/redirects/rewrites)
+-- NO --> Framework-specific solution
Static Config vs Programmatic Config
Is your configuration static and predictable?
|
+-- YES --> vercel.json
|
+-- NO --> Do you need env vars, API calls, or conditional logic at build time?
|
+-- YES --> vercel.ts (with @vercel/config)
+-- NO --> vercel.json
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- Storing secrets in
vercel.jsonor committing.envfiles -- use the Vercel dashboard Environment Variables orvercel envCLI - Missing
CRON_SECRETverification in cron handlers -- cron endpoints are publicly accessible URLs anyone can call - Using Edge runtime when you need Node.js APIs (fs, native modules, large packages) -- will fail at runtime with missing API errors
- Setting
maxDurationabove your plan limit -- deployment will fail - Not setting
regionswhen your database is outsideiad1-- every function call makes a cross-region database round trip
Medium Priority Issues:
- Missing
$schemain vercel.json -- loses IDE autocompletion and validation that catches config errors before deployment - Using
permanent: trueredirects during development -- browsers cache 308s aggressively, hard to undo - Not using
ignoreCommandin monorepos -- every commit triggers builds for all apps, wasting build minutes - Hardcoding
VERCEL_URLwithout protocol --VERCEL_URLdoes not includehttps://, must be prepended
Common Mistakes:
- Assuming cron jobs run on Preview deployments -- they only run on Production
- Using
statusCodeandpermanenttogether in redirects -- they are mutually exclusive - Not making cron handlers idempotent -- Vercel may deliver events more than once
- Expecting Edge functions to have file system access -- Edge runtime has no
fsmodule - Using
buildsproperty in vercel.json -- it is legacy, usefunctionsinstead
Gotchas & Edge Cases:
- Edge Runtime:
eval(),new Function(), and dynamicWebAssembly.instantiateare disabled for security - Edge Runtime: Must begin sending response within 25 seconds (can stream up to 300s after)
- Routing Middleware: 50ms average CPU time limit on Edge, 4 MB max request body, 14 KB max URL length
cleanUrls: truecauses 404s in localvercel devbut works in productionhas/missingconditions on redirects/headers don't work locally withvercel devVERCEL_URLdiffers between Production (custom domain) and Preview (generated.vercel.appURL)- Fluid compute reuses function instances -- module-level state persists across requests (can be useful for caching, but be aware of stale data)
- Function
memorycannot be set in vercel.json when Fluid compute is enabled -- use the dashboard instead vercel.tsonly runs at build time, not at request time -- it generates static config- Edge function code size limits are after gzip: 1 MB (Hobby), 2 MB (Pro), 4 MB (Enterprise)
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST always include "$schema": "https://openapi.vercel.sh/vercel.json" in vercel.json for IDE validation)
(You MUST place functions in a region close to your data source -- the default iad1 may add latency if your database is elsewhere)
(You MUST verify CRON_SECRET in cron job handlers -- Vercel cron endpoints are publicly accessible URLs)
(You MUST design cron jobs to be idempotent -- Vercel may deliver the same cron event more than once)
(You MUST NOT store secrets in vercel.json or source code -- use Environment Variables in the Vercel dashboard)
Failure to follow these rules will result in security vulnerabilities (exposed secrets, unprotected cron endpoints), poor performance (cross-region latency), and deployment failures (invalid config, exceeded limits).
</critical_reminders>
Files (skills)
-
examples
-
core.md 8.8 KB
# Vercel -- Core Configuration & Functions Examples > Core configuration and function patterns for Vercel deployments. See [SKILL.md](../SKILL.md) for decision guidance. **Related examples:** - [Routing & Middleware](routing.md) -- Headers, redirects, rewrites, Routing Middleware - [Cron Jobs & Scheduling](cron-jobs.md) -- Cron configuration and handler patterns - [Monorepo & Advanced](monorepo.md) -- Monorepo setup, vercel.ts, image optimization --- ## vercel.json Complete Configuration ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "pnpm build", "installCommand": "pnpm install", "outputDirectory": "dist", "framework": "nextjs", "cleanUrls": true, "trailingSlash": false, "fluid": true, "regions": ["iad1"], "functionFailoverRegions": ["sfo1"], "functions": { "api/**/*.ts": { "maxDuration": 30, "regions": ["iad1"] }, "api/eu-data.ts": { "maxDuration": 60, "regions": ["cdg1"], "functionFailoverRegions": ["lhr1"] } }, "headers": [ { "source": "/(.*)", "headers": [ { "key": "X-Content-Type-Options", "value": "nosniff" }, { "key": "X-Frame-Options", "value": "DENY" } ] } ], "redirects": [ { "source": "/old-path", "destination": "/new-path", "permanent": true } ], "rewrites": [{ "source": "/api/v1/:path*", "destination": "/api/:path*" }], "crons": [ { "path": "/api/cron/daily-cleanup", "schedule": "0 2 * * *" } ] } ``` **Why good:** Schema enables IDE validation, per-function regions optimize latency, Fluid compute enabled, security headers on all routes, cron declared alongside deployment config ```json { "builds": [{ "src": "api/*.ts", "use": "@vercel/node" }], "functions": { "api/*.ts": { "memory": 3009 } } } ``` **Why bad:** `builds` is a legacy property and cannot be combined with `functions`, missing `$schema` loses IDE validation, `memory` in vercel.json is ignored when Fluid compute is enabled (set in dashboard instead) --- ## Serverless Functions (Node.js Runtime) ### fetch Handler (Recommended) ```typescript // api/users.ts -- uses Web Standard fetch signature const DEFAULT_LIMIT = 20; const MAX_LIMIT = 100; export default { async fetch(request: Request) { const url = new URL(request.url); if (request.method !== "GET") { return new Response("Method Not Allowed", { status: 405 }); } const limitParam = url.searchParams.get("limit"); const limit = Math.min(Number(limitParam) || DEFAULT_LIMIT, MAX_LIMIT); // Your data fetching logic here const users = await fetchUsers(limit); return Response.json({ users, limit }); }, }; ``` **Why good:** Web Standard `fetch` signature works across runtimes (Node.js and Edge), named constants for limits, proper HTTP method checking, `Response.json` helper for typed responses ### HTTP Method Handlers ```typescript // api/items.ts -- named HTTP method exports (framework-agnostic) export function GET(request: Request) { return Response.json({ items: [] }); } export async function POST(request: Request) { const body = await request.json(); // Create item logic return Response.json({ created: true }, { status: 201 }); } ``` **Why good:** Separate exports per HTTP method, Vercel auto-routes to the correct handler, clean separation of concerns --- ## Edge Runtime Functions ```typescript // api/geo.ts -- Edge runtime for global low-latency export const runtime = "edge"; export default { async fetch(request: Request) { const country = request.headers.get("x-vercel-ip-country") ?? "US"; const city = request.headers.get("x-vercel-ip-city") ?? "Unknown"; const region = process.env.VERCEL_REGION; return Response.json({ country, city, executedIn: region }); }, }; ``` **Why good:** `runtime = "edge"` deploys globally, reads geo headers for location-aware responses, lightweight and fast ```typescript // api/heavy.ts -- BAD: Edge with Node.js-only code export const runtime = "edge"; import { readFileSync } from "fs"; // FAILS -- no fs in Edge export default { async fetch() { const data = readFileSync("./config.json"); // Runtime error return Response.json(data); }, }; ``` **Why bad:** Edge runtime has no `fs` module, `readFileSync` throws at runtime, use Node.js runtime for file system access --- ## Edge vs Node.js Runtime Decision ```typescript // Node.js -- when you need full APIs // api/generate-pdf.ts import { createWriteStream } from "fs"; import { join } from "path"; export default { async fetch(request: Request) { // Full Node.js API access const tmpPath = join("/tmp", "output.pdf"); // ... generate PDF using Node.js libraries return new Response("PDF generated"); }, }; ``` ```typescript // Edge -- when you need global speed with minimal deps // api/redirect.ts export const runtime = "edge"; export default { async fetch(request: Request) { const country = request.headers.get("x-vercel-ip-country"); const target = country === "DE" ? "/de" : "/en"; return Response.redirect(new URL(target, request.url)); }, }; ``` --- ## Streaming Responses ```typescript // api/stream.ts -- streaming for long-running responses export default { async fetch(request: Request) { const encoder = new TextEncoder(); const stream = new ReadableStream({ async start(controller) { for (const chunk of ["Hello", " ", "World"]) { controller.enqueue(encoder.encode(chunk)); await new Promise((resolve) => setTimeout(resolve, 100)); } controller.close(); }, }); return new Response(stream, { headers: { "Content-Type": "text/plain" }, }); }, }; ``` **Why good:** Streaming avoids buffering entire response in memory, begins sending data immediately (important for Edge's 25s initial response limit), efficient for large payloads --- ## Environment Variables ```typescript // Built-in Vercel environment variables const ENV = process.env.VERCEL_ENV; // "production" | "preview" | "development" const URL = process.env.VERCEL_URL; // "my-app-abc123.vercel.app" (no protocol!) const REGION = process.env.VERCEL_REGION; // "iad1" const SHA = process.env.VERCEL_GIT_COMMIT_SHA; // full commit hash const BRANCH = process.env.VERCEL_GIT_COMMIT_REF; // "main", "feature/x" // IMPORTANT: VERCEL_URL does not include protocol const BASE_URL = process.env.VERCEL_ENV === "production" ? "https://myapp.com" : `https://${process.env.VERCEL_URL}`; ``` **Why good:** Handles protocol correctly, distinguishes production (custom domain) from preview (generated URL), uses built-in variables for deployment context ### Managing Environment Variables via CLI ```bash # Pull env vars from Vercel to local .env file vercel env pull .env.local # Add a new environment variable vercel env add MY_API_KEY # Add for specific environment vercel env add MY_API_KEY production # List all environment variables vercel env ls # Remove an environment variable vercel env rm MY_API_KEY ``` --- ## Function Configuration in vercel.json ### Per-Function Overrides ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "regions": ["iad1"], "functions": { "api/**/*.ts": { "maxDuration": 30 }, "api/heavy-compute.ts": { "maxDuration": 300, "regions": ["iad1"] }, "api/eu-data.ts": { "regions": ["cdg1"], "functionFailoverRegions": ["lhr1"] } } } ``` **Why good:** Global defaults with per-function overrides, heavy compute gets longer duration, EU data function runs in Paris with London failover ### Function Configuration via Code (Framework-Specific) ```typescript // For frameworks that support route segment config: export const runtime = "edge"; // or "nodejs" (default) export const preferredRegion = ["iad1", "cdg1"]; export const maxDuration = 30; export const dynamic = "force-dynamic"; // disable caching ``` --- ## Fluid Compute Fluid compute (enabled by default since April 2025) reuses function instances for concurrent requests, reducing cold starts and cost. ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "fluid": true } ``` **Key behaviors:** - Function instances handle multiple concurrent requests - Module-level state persists across requests within the same instance - Priced on active CPU time (idle waiting is cheaper) - Memory configuration is set in the dashboard, not vercel.json **Gotcha:** Because instances are reused, module-level variables persist. This is useful for connection pooling but can cause issues if you store request-specific state at module scope. ```typescript // Module-level state persists across requests -- use intentionally let requestCount = 0; // Counts requests within this instance export default { async fetch(request: Request) { requestCount++; // requestCount is NOT global -- it's per-instance return Response.json({ instanceRequests: requestCount }); }, }; ``` -
cron-jobs.md 4.9 KB
# Vercel -- Cron Jobs & Scheduling Examples > Cron job configuration and handler patterns for Vercel. See [SKILL.md](../SKILL.md) for decision guidance. **Related examples:** - [Core Configuration & Functions](core.md) -- vercel.json, functions, runtime selection - [Routing & Middleware](routing.md) -- Headers, redirects, rewrites - [Monorepo & Advanced](monorepo.md) -- Monorepo and advanced config --- ## Cron Configuration in vercel.json ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "crons": [ { "path": "/api/cron/cleanup", "schedule": "0 2 * * *" }, { "path": "/api/cron/sync", "schedule": "*/15 * * * *" }, { "path": "/api/cron/weekly-report", "schedule": "0 9 * * 1" } ] } ``` ### Cron Expression Reference ``` * * * * * | | | | | | | | | +--- Day of week (0-6, Sun=0) | | | +-------- Month (1-12) | | +------------- Day of month (1-31) | +------------------ Hour (0-23, UTC) +----------------------- Minute (0-59) ``` | Expression | Meaning | | -------------- | ---------------------------------------- | | `* * * * *` | Every minute | | `*/15 * * * *` | Every 15 minutes | | `0 * * * *` | Every hour | | `0 2 * * *` | Daily at 2:00 AM UTC | | `0 9 * * 1` | Every Monday at 9:00 AM UTC | | `0 0 1 * *` | First day of every month at midnight UTC | --- ## Secure Cron Handler ```typescript // api/cron/cleanup.ts export default { async fetch(request: Request) { // CRITICAL: Verify CRON_SECRET -- cron endpoints are public URLs const authHeader = request.headers.get("authorization"); if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) { return new Response("Unauthorized", { status: 401 }); } // Idempotent cleanup logic const result = await performCleanup(); return Response.json({ success: true, cleaned: result.deletedCount, timestamp: new Date().toISOString(), }); }, }; ``` **Why good:** Verifies `CRON_SECRET` before executing, returns structured response for observability, timestamp helps debug timing issues ```typescript // BAD: No auth check export default { async fetch(request: Request) { await deleteExpiredRecords(); // Anyone can trigger this! return Response.json({ success: true }); }, }; ``` **Why bad:** No CRON_SECRET verification, anyone who discovers the URL can trigger the cleanup, potential for abuse or data loss --- ## Setting Up CRON_SECRET 1. Generate a secure random value: ```bash openssl rand -base64 32 ``` 2. Add it as an environment variable in the Vercel dashboard: - Go to Project Settings > Environment Variables - Add `CRON_SECRET` with the generated value - Scope it to Production only (crons only run in production) 3. Vercel automatically sends the secret as `Authorization: Bearer <CRON_SECRET>` when invoking cron endpoints. --- ## Idempotent Handler Pattern Vercel may deliver cron events more than once. Design handlers to produce the same result on repeated execution. ```typescript // api/cron/process-pending.ts const MAX_BATCH_SIZE = 100; export default { async fetch(request: Request) { const authHeader = request.headers.get("authorization"); if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) { return new Response("Unauthorized", { status: 401 }); } // Idempotent: only process items in "pending" state // If this runs twice, the second run finds no pending items const pending = await fetchPendingItems(MAX_BATCH_SIZE); if (pending.length === 0) { return Response.json({ processed: 0, message: "No pending items" }); } const results = await Promise.allSettled( pending.map((item) => processItem(item)), ); const succeeded = results.filter((r) => r.status === "fulfilled").length; const failed = results.filter((r) => r.status === "rejected").length; return Response.json({ processed: succeeded, failed, total: pending.length, }); }, }; ``` **Why good:** Processes only "pending" items (idempotent -- second run is a no-op), batch size limit prevents timeout, `Promise.allSettled` handles partial failures, structured response for monitoring --- ## Plan Limits for Crons | Plan | Max Cron Jobs | Minimum Interval | | ---------- | ------------- | ---------------- | | Hobby | 2 | Daily | | Pro | 40 | Every minute | | Enterprise | 100+ | Every minute | **Key constraints:** - Crons only execute on Production deployments (not Preview) - All cron times are in UTC - Vercel's event system may deliver events more than once - Cron handlers are regular Vercel Functions -- same maxDuration limits apply -
monorepo.md 4.9 KB
# Vercel -- Monorepo & Advanced Configuration Examples > Monorepo setup, programmatic config, and advanced patterns for Vercel. See [SKILL.md](../SKILL.md) for decision guidance. **Related examples:** - [Core Configuration & Functions](core.md) -- vercel.json, functions, runtime selection - [Routing & Middleware](routing.md) -- Headers, redirects, rewrites - [Cron Jobs & Scheduling](cron-jobs.md) -- Scheduled task patterns --- ## Monorepo Setup ### Project Structure ``` my-monorepo/ apps/ web/ <-- Vercel project, Root Directory: apps/web vercel.json package.json docs/ <-- Separate Vercel project, Root Directory: apps/docs vercel.json package.json packages/ ui/ utils/ package.json <-- Root package.json turbo.json ``` ### Key Configuration 1. **In the Vercel dashboard**, set Root Directory to the app path (e.g., `apps/web`) 2. **Always run `vercel` CLI from the monorepo root**, not from the app directory 3. **Each app is a separate Vercel project** with its own vercel.json ### vercel.json for Monorepo App ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "installCommand": "pnpm install", "buildCommand": "pnpm --filter web build", "ignoreCommand": "git diff --quiet HEAD^ HEAD ./" } ``` **Why good:** `pnpm --filter` builds only the target app, `ignoreCommand` skips builds when the app directory hasn't changed, saving build minutes --- ## Ignored Build Steps ### Skip Build When Directory Unchanged ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "ignoreCommand": "git diff --quiet HEAD^ HEAD ./" } ``` ### Nx Integration ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "ignoreCommand": "npx nx-ignore my-app" } ``` ### Custom Ignore Script ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "ignoreCommand": "bash scripts/should-build.sh" } ``` **How `ignoreCommand` works:** - Exit code `1` = build continues - Exit code `0` = build is skipped --- ## Programmatic Configuration with vercel.ts `vercel.ts` runs at build time and generates configuration dynamically. Use the `@vercel/config` package for type safety. ```typescript // vercel.ts -- dynamic configuration at build time import { defineConfig } from "@vercel/config"; export default defineConfig({ regions: [process.env.DEPLOY_REGION ?? "iad1"], functions: { "api/**/*.ts": { maxDuration: process.env.VERCEL_ENV === "production" ? 60 : 30, }, }, headers: [ { source: "/(.*)", headers: [ { key: "X-Content-Type-Options", value: "nosniff" }, { key: "X-Frame-Options", value: "DENY" }, ], }, ], crons: process.env.VERCEL_ENV === "production" ? [{ path: "/api/cron/cleanup", schedule: "0 2 * * *" }] : [], }); ``` **Why good:** Type-safe with `defineConfig`, environment-aware (different maxDuration per env), crons only in production (they only run there anyway, but this makes intent explicit), dynamic region selection **When to use vercel.ts over vercel.json:** - You need environment-variable-based configuration - Configuration needs to be generated from an external API at build time - Shared config logic between multiple apps in a monorepo - Conditional crons, headers, or rewrites based on deployment environment --- ## .vercelignore Exclude files from deployment (like `.gitignore` for Vercel). In a monorepo, a `.vercelignore` in the Root Directory takes precedence over one at the repository root. ``` # .vercelignore docs/ tests/ *.test.ts *.spec.ts .env.local coverage/ ``` --- ## Image Optimization ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "images": { "sizes": [256, 640, 1080, 2048, 3840], "formats": ["image/avif", "image/webp"], "minimumCacheTTL": 60, "remotePatterns": [ { "protocol": "https", "hostname": "images.example.com", "pathname": "/assets/**" } ], "localPatterns": [ { "pathname": "^/public/images/.*$" } ] } } ``` **Key properties:** - `sizes` -- Allowed widths for the optimization API - `formats` -- Output formats (avif and/or webp) - `remotePatterns` -- Allow-list of external image domains - `minimumCacheTTL` -- Cache duration in seconds for optimized images - `dangerouslyAllowSVG` -- Disabled by default for security --- ## cleanUrls and trailingSlash ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "cleanUrls": true, "trailingSlash": false } ``` | Setting | Behavior | | ---------------------- | ----------------------------------- | | `cleanUrls: true` | `/about.html` redirects to `/about` | | `trailingSlash: false` | `/about/` redirects to `/about` | | `trailingSlash: true` | `/about` redirects to `/about/` | **Gotcha:** `cleanUrls: true` causes 404 errors locally with `vercel dev` but works correctly when deployed. See [reference.md](../reference.md) for Vercel CLI commands. -
routing.md 7.4 KB
# Vercel -- Routing & Middleware Examples > Routing configuration and middleware patterns for Vercel. See [SKILL.md](../SKILL.md) for decision guidance. **Related examples:** - [Core Configuration & Functions](core.md) -- vercel.json, functions, runtime selection - [Cron Jobs & Scheduling](cron-jobs.md) -- Scheduled task patterns - [Monorepo & Advanced](monorepo.md) -- Monorepo and advanced config --- ## Routing Middleware Routing Middleware runs before the cache on every request. Create a `middleware.ts` (or `middleware.js`) at the project root. ### Basic Middleware ```typescript // middleware.ts export default function middleware(request: Request) { const url = new URL(request.url); // Skip middleware for static assets (adjust paths for your framework) if (url.pathname.startsWith("/static/") || url.pathname.includes(".")) { return; } // Add custom header to all responses const response = new Response(null, { status: 200 }); response.headers.set("x-middleware-ran", "true"); return response; } ``` ### Auth Check Middleware ```typescript // middleware.ts -- protect routes with auth check const PROTECTED_PATHS = ["/dashboard", "/settings", "/admin"]; export default async function middleware(request: Request) { const url = new URL(request.url); const isProtected = PROTECTED_PATHS.some((path) => url.pathname.startsWith(path), ); if (!isProtected) return; const token = request.headers.get("authorization")?.replace("Bearer ", ""); if (!token) { return Response.redirect(new URL("/login", request.url)); } // Validate token with your auth provider const isValid = await validateToken(token); if (!isValid) { return Response.redirect(new URL("/login", request.url)); } // Continue to the requested page (return nothing or undefined) } ``` **Why good:** Named constant for protected paths, early return for unprotected routes, proper Bearer token extraction, redirect to login on failure ### Geo-Routing Middleware ```typescript // middleware.ts -- route users by geography const COUNTRY_REDIRECTS: Record<string, string> = { DE: "/de", FR: "/fr", JP: "/ja", }; export default function middleware(request: Request) { const url = new URL(request.url); // Don't redirect if already on a localized path if ( url.pathname.startsWith("/de") || url.pathname.startsWith("/fr") || url.pathname.startsWith("/ja") ) { return; } const country = request.headers.get("x-vercel-ip-country") ?? "US"; const redirectPath = COUNTRY_REDIRECTS[country]; if (redirectPath) { return Response.redirect(new URL(redirectPath + url.pathname, request.url)); } } ``` **Why good:** Lookup table for country mappings, avoids redirect loops by checking current path, preserves original pathname in redirect ### Changing Middleware Runtime ```typescript // middleware.ts -- use Node.js instead of Edge (default) export const config = { runtime: "nodejs", // default is "edge" }; export default function middleware(request: Request) { // Full Node.js API access here return new Response("Hello from Node.js middleware"); } ``` --- ## Headers Configuration ### Security Headers ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "headers": [ { "source": "/(.*)", "headers": [ { "key": "X-Content-Type-Options", "value": "nosniff" }, { "key": "X-Frame-Options", "value": "DENY" }, { "key": "X-XSS-Protection", "value": "1; mode=block" }, { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }, { "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" } ] } ] } ``` ### Cache Control for Static Assets ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "headers": [ { "source": "/static/(.*)", "headers": [ { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } ] }, { "source": "/service-worker.js", "headers": [ { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" } ] } ] } ``` ### Conditional Headers ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "headers": [ { "source": "/:path*", "has": [ { "type": "query", "key": "authorized" } ], "headers": [{ "key": "x-authorized", "value": "true" }] } ] } ``` **Why good:** `has` condition only applies header when `?authorized` query param is present, avoiding unnecessary headers on all requests --- ## Redirects ### Basic Redirects ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "redirects": [ { "source": "/me", "destination": "/profile", "permanent": false }, { "source": "/blog/:slug", "destination": "/posts/:slug", "permanent": true }, { "source": "/docs/(.*)", "destination": "https://docs.example.com/$1" } ] } ``` **Key rules:** - `permanent: true` = 308 status (browsers cache aggressively) - `permanent: false` = 307 status (temporary, no browser caching) - `statusCode` can override (301, 302, etc.) but cannot be combined with `permanent` ### Geo-Based Redirects ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "redirects": [ { "source": "/:path((?!uk/).*)", "has": [ { "type": "header", "key": "x-vercel-ip-country", "value": "GB" } ], "destination": "/uk/:path*", "permanent": false } ] } ``` **Why good:** Regex negative lookahead prevents redirect loops (already on `/uk/`), uses Vercel's geo header for country detection, temporary redirect allows easy testing **Gotcha:** `has`/`missing` conditions do not work locally with `vercel dev`, only in deployed environments. --- ## Rewrites Rewrites map one path to another without changing the browser URL. ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "rewrites": [ { "source": "/api/v1/:path*", "destination": "/api/:path*" }, { "source": "/proxy/:path*", "destination": "https://api.external.com/:path*" }, { "source": "/(.*)", "destination": "/index.html" } ] } ``` **Use cases:** - **API versioning**: Rewrite `/api/v1/users` to `/api/users` while keeping the v1 URL - **External proxying**: Forward requests to an external API without exposing the URL to the client - **SPA fallback**: Rewrite all paths to `index.html` for client-side routing --- ## has/missing Condition Reference Both `has` and `missing` accept the same object structure: ```json { "type": "header | cookie | query | host", "key": "the-key-name", "value": "optional-value-to-match" } ``` ### Examples ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "redirects": [ { "source": "/feature/:path*", "has": [{ "type": "cookie", "key": "beta", "value": "true" }], "destination": "/beta/feature/:path*", "permanent": false }, { "source": "/:path*", "missing": [{ "type": "header", "key": "x-api-key" }], "destination": "/unauthorized", "permanent": false } ] } ``` **Why good:** Cookie-based routing enables A/B testing and beta feature rollout, missing header check enforces API key requirements at the CDN layer
-
-
reference.md 8.2 KB
# Vercel Quick Reference ## vercel.json Property Reference | Property | Type | Description | | ------------------------- | ---------------- | ------------------------------------------------------------ | | `$schema` | `string` | `"https://openapi.vercel.sh/vercel.json"` for IDE validation | | `buildCommand` | `string \| null` | Override framework build command | | `installCommand` | `string \| null` | Override package install command | | `outputDirectory` | `string \| null` | Override build output directory | | `framework` | `string \| null` | Framework preset (`"nextjs"`, `"remix"`, `null` for Other) | | `regions` | `string[]` | Default function regions (e.g., `["iad1"]`) | | `functionFailoverRegions` | `string[]` | Failover regions for outages (Enterprise) | | `functions` | `object` | Per-function config (maxDuration, regions, runtime) | | `headers` | `object[]` | Custom response headers | | `redirects` | `object[]` | URL redirect rules | | `rewrites` | `object[]` | URL rewrite rules (no browser URL change) | | `crons` | `object[]` | Scheduled function invocations | | `cleanUrls` | `boolean` | Remove `.html` extensions (default: false) | | `trailingSlash` | `boolean` | Add/remove trailing slashes | | `fluid` | `boolean` | Enable Fluid compute (default: true for new projects) | | `images` | `object` | Image optimization configuration | | `ignoreCommand` | `string \| null` | Custom build skip logic (exit 0 = skip) | | `public` | `boolean` | Make deployment logs/source public | | `bunVersion` | `string` | Use Bun runtime (`"1.x"`) | ## functions Object Properties | Property | Type | Description | | ------------------------- | ---------- | ------------------------------------------------ | | `maxDuration` | `number` | Max execution time in seconds | | `memory` | `number` | Memory in MB (ignored with Fluid, use dashboard) | | `runtime` | `string` | Community runtime npm package | | `regions` | `string[]` | Override project-level regions | | `functionFailoverRegions` | `string[]` | Override project-level failover | | `includeFiles` | `string` | Glob for files to include | | `excludeFiles` | `string` | Glob for files to exclude | | `supportsCancellation` | `boolean` | Enable request cancellation (Node.js only) | ## Plan Limits | Resource | Hobby | Pro | Enterprise | | --------------------- | ------------- | ------------- | ------------- | | maxDuration (default) | 10s | 15s | 15s | | maxDuration (max) | 60s | 300s | 900s | | Memory (default) | 2 GB / 1 vCPU | 2 GB / 1 vCPU | 2 GB / 1 vCPU | | Memory (max) | 2 GB / 1 vCPU | 4 GB / 2 vCPU | 4 GB / 2 vCPU | | Edge code size (gzip) | 1 MB | 2 MB | 4 MB | | Node.js code size | 250 MB | 250 MB | 250 MB | | Cron jobs | 2 | 40 | 100+ | | Cron min interval | Daily | 1 minute | 1 minute | ## Common Region IDs | Region | ID | Location | | ---------------- | ------ | ----------------- | | Washington, D.C. | `iad1` | US East (default) | | San Francisco | `sfo1` | US West | | Portland | `pdx1` | US West | | Paris | `cdg1` | Europe | | London | `lhr1` | Europe | | Frankfurt | `fra1` | Europe | | Tokyo | `hnd1` | Asia | | Singapore | `sin1` | Asia | | Sydney | `syd1` | Oceania | | Sao Paulo | `gru1` | South America | ## Built-in Environment Variables | Variable | Description | Example | | --------------------------- | ---------------------------- | -------------------------------------------- | | `VERCEL_ENV` | Deployment environment | `"production"`, `"preview"`, `"development"` | | `VERCEL_URL` | Deployment URL (no protocol) | `"my-app-abc123.vercel.app"` | | `VERCEL_REGION` | Function execution region | `"iad1"` | | `VERCEL_GIT_COMMIT_SHA` | Full commit hash | `"abc123..."` | | `VERCEL_GIT_COMMIT_REF` | Git branch name | `"main"` | | `VERCEL_GIT_COMMIT_MESSAGE` | Commit message | `"fix: update..."` | | `VERCEL_GIT_PROVIDER` | Git provider | `"github"` | | `VERCEL_GIT_REPO_SLUG` | Repository name | `"my-app"` | | `VERCEL_GIT_REPO_OWNER` | Repository owner | `"my-org"` | ## Vercel CLI Commands ```bash vercel link # Link to Vercel project vercel dev # Local development vercel build # Local build vercel # Deploy to preview vercel --prod # Deploy to production vercel env pull # Pull env vars to .env.local vercel env add KEY # Add environment variable vercel env ls # List environment variables vercel env rm KEY # Remove environment variable vercel ls # List deployments vercel inspect <url> # Inspect deployment details vercel promote <url> # Promote preview to production vercel logs <url> # View deployment logs vercel domains ls # List domains vercel domains add # Add custom domain ``` ## Runtime Comparison | Feature | Node.js (default) | Edge | | ------------------- | --------------------------- | ------------------------------------ | | Deployment | Single/multi region | Global (closest to user) | | Cold starts | Higher (mitigated by Fluid) | Near-zero | | API access | Full Node.js | Web Standards only | | Code size | 250 MB | 1-4 MB (by plan) | | Duration | Plan-based maxDuration | 25s initial response, 300s streaming | | File system | Yes (`/tmp` writable) | No | | eval / new Function | Yes | No (security restriction) | | npm packages | All | ES Modules only, no native deps | ## Redirect/Rewrite Pattern Syntax | Pattern | Matches | Example | | ----------------------- | ------------------ | ------------------------------------- | | `/path` | Exact path | `/about` | | `/:param` | Named parameter | `/users/:id` matches `/users/123` | | `/:param*` | Wildcard (0+) | `/blog/:path*` matches `/blog/a/b/c` | | `/(regex)` | Regex group | `/post/:id(\\d+)` matches digits only | | `/:path((?!prefix/).*)` | Negative lookahead | Exclude paths starting with prefix | -
SKILL.md 18.2 KB
--- name: infra-platform-vercel description: Vercel deployment platform — project configuration, serverless/edge functions, Routing Middleware, cron jobs, environment variables, monorepo setup --- # Vercel Platform Patterns > **Quick Guide:** Configure deployments with `vercel.json` (static) or `vercel.ts` (programmatic, build-time). Functions default to Node.js runtime in `iad1` region. Use `export const runtime = 'edge'` for edge functions (V8 isolates, global deployment). Routing Middleware runs before the cache globally. Secure cron jobs with `CRON_SECRET`. Enable Fluid compute for better concurrency and cost. Always place functions near your data source. --- <critical_requirements> ## CRITICAL: Before Using This Skill > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants) **(You MUST always include `"$schema": "https://openapi.vercel.sh/vercel.json"` in vercel.json for IDE validation)** **(You MUST place functions in a region close to your data source -- the default `iad1` may add latency if your database is elsewhere)** **(You MUST verify `CRON_SECRET` in cron job handlers -- Vercel cron endpoints are publicly accessible URLs)** **(You MUST design cron jobs to be idempotent -- Vercel may deliver the same cron event more than once)** **(You MUST NOT store secrets in vercel.json or source code -- use Environment Variables in the Vercel dashboard)** </critical_requirements> --- ## Examples - [Core Configuration & Functions](examples/core.md) -- vercel.json schema, functions config, runtime selection, regions, environment variables, Fluid compute - [Routing & Middleware](examples/routing.md) -- Routing Middleware, headers, redirects, rewrites, conditional routing, geo-routing - [Cron Jobs & Scheduling](examples/cron-jobs.md) -- cron configuration, CRON_SECRET verification, idempotent handlers - [Monorepo & Advanced](examples/monorepo.md) -- monorepo setup, vercel.ts programmatic config, ignoreCommand, image optimization - [Quick Reference](reference.md) -- vercel.json property reference, plan limits, region IDs, CLI commands --- **Auto-detection:** Vercel, vercel.json, vercel.ts, @vercel/config, Vercel Functions, Vercel deploy, VERCEL_URL, VERCEL_ENV, VERCEL_REGION, Routing Middleware, middleware.ts, Edge Runtime, export const runtime, Fluid compute, vercel cron, cron jobs vercel, vercel.json crons, vercel dev, vercel build, vercel deploy, vercel env, vercel link, vercel pull, .vercelignore, vercel monorepo, vercel regions, vercel headers, vercel redirects, vercel rewrites **When to use:** - Configuring Vercel project settings via `vercel.json` or `vercel.ts` - Deploying serverless functions (Node.js or Edge runtime) - Setting up Routing Middleware for auth, geo-routing, or A/B testing - Configuring cron jobs for scheduled tasks - Managing environment variables across preview/production - Setting up monorepo deployments with per-app configuration - Configuring headers, redirects, rewrites, and URL routing - Choosing function regions and memory/duration limits **When NOT to use:** - Long-running background jobs exceeding plan limits (use a dedicated job runner) - Workloads requiring persistent WebSocket connections (Vercel functions are request/response) - Applications needing custom server runtimes beyond Node.js/Edge/Bun/Python/Go/Ruby **Key patterns covered:** - `vercel.json` / `vercel.ts` project configuration with IDE schema validation - Serverless function configuration (runtime, memory, maxDuration, regions) - Edge Runtime vs Node.js runtime tradeoffs - Routing Middleware (runs before cache, global edge execution) - Cron jobs with `CRON_SECRET` authentication - Headers, redirects, rewrites with conditional matching (`has`/`missing`) - Monorepo setup with root directory and ignored build steps - Fluid compute for improved concurrency and cost efficiency - Environment variables (`VERCEL_ENV`, `VERCEL_URL`, `VERCEL_REGION`) --- <philosophy> ## Philosophy Vercel is a deployment platform that auto-detects your framework and optimizes builds, routing, and function deployment. The key architectural principle: **configure only what you need to override**. Vercel's defaults are sensible for most projects -- `vercel.json` exists for when those defaults don't fit. 1. **Convention over configuration** -- Vercel auto-detects frameworks, build commands, and output directories. Only override when the defaults don't work. 2. **Functions near data** -- Serverless functions default to `iad1` (Washington, D.C.). If your database is in Europe, set `regions` to a European region. 3. **Edge for global, Node.js for power** -- Edge runtime runs globally with low latency but has limited APIs. Node.js runtime has full API access but runs in a single region by default. 4. **Routing Middleware runs before cache** -- Use it for personalization, auth, geo-routing. Keep it fast (50ms CPU average on Edge). 5. **Fluid compute** -- Enabled by default for new projects (since April 2025). Reuses function instances for concurrent requests, reducing cold starts and cost. **When to use Vercel:** - Deploying web applications with automatic framework detection - Serverless API endpoints that scale to zero - Edge-first applications needing global low-latency - Projects needing preview deployments per PR **When NOT to use Vercel:** - Long-running compute exceeding plan maxDuration limits - Applications requiring persistent connections (WebSockets beyond Vercel's support) - Workloads with heavy sustained compute (cost-prohibitive at scale) </philosophy> --- <patterns> ## Core Patterns ### Pattern 1: vercel.json Configuration Every Vercel project can have a `vercel.json` at the root for static configuration, or `vercel.ts` for programmatic build-time configuration. Always include the `$schema` for IDE autocompletion. ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "regions": ["iad1"], "functions": { "api/**/*.ts": { "maxDuration": 30 } }, "crons": [ { "path": "/api/daily-cleanup", "schedule": "0 2 * * *" } ] } ``` **Why good:** Schema enables IDE validation, regions explicit about function placement, maxDuration prevents runaway functions, cron declared in config alongside deployment See [examples/core.md](examples/core.md) for complete configuration with all properties. --- ### Pattern 2: Serverless Functions (Node.js Runtime) Functions in the `api/` directory are automatically deployed. The recommended signature uses the `fetch` handler. Node.js is the default runtime. ```typescript // api/users.ts -- Node.js runtime (default) export default { async fetch(request: Request) { const url = new URL(request.url); const id = url.searchParams.get("id"); if (!id) { return Response.json({ error: "Missing id" }, { status: 400 }); } // Your data fetching logic here return Response.json({ id, name: "Example User" }); }, }; ``` **Why good:** Uses Web Standard `fetch` signature (works across runtimes), `Response.json` for typed responses, proper error handling with status codes See [examples/core.md](examples/core.md) for function configuration, streaming, and runtime selection. --- ### Pattern 3: Edge Runtime Functions Edge functions run on V8 isolates globally, closest to the user. Use for latency-sensitive operations with limited API needs. Vercel now recommends Node.js for most use cases due to Fluid compute improvements. ```typescript // api/geo.ts -- Edge runtime export const runtime = "edge"; export default { async fetch(request: Request) { const country = request.headers.get("x-vercel-ip-country") ?? "US"; return Response.json({ country, region: process.env.VERCEL_REGION }); }, }; ``` **When to use:** Latency-critical responses, geo-routing, simple request/response transformations **When not to use:** Heavy computation, Node.js-only APIs (fs, child_process), large dependencies (1-4 MB code size limit) See [examples/core.md](examples/core.md) for Edge vs Node.js comparison and limitations. --- ### Pattern 4: Routing Middleware Routing Middleware executes before the cache on every request. Create a `middleware.ts` file at the project root. Default runtime is Edge but can be changed to Node.js. ```typescript // middleware.ts -- runs before every request export default function middleware(request: Request) { const url = new URL(request.url); // Redirect old paths if (url.pathname === "/old-page") { return new Response(null, { status: 302, headers: { Location: "/new-page" }, }); } } // Optional: change runtime from Edge (default) to Node.js export const config = { runtime: "nodejs", }; ``` **Why good:** Runs globally before cache, can personalize static content, supports auth checks, geo-routing, A/B testing **Limits:** 50ms average CPU time on Edge runtime, 4 MB max request body, 14 KB max URL length See [examples/routing.md](examples/routing.md) for auth, geo-routing, and conditional middleware patterns. --- ### Pattern 5: Cron Jobs Define scheduled functions in `vercel.json`. Cron endpoints are regular API routes -- secure them with `CRON_SECRET`. ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "crons": [ { "path": "/api/cron/cleanup", "schedule": "0 2 * * *" } ] } ``` ```typescript // api/cron/cleanup.ts export default { async fetch(request: Request) { const authHeader = request.headers.get("authorization"); if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) { return new Response("Unauthorized", { status: 401 }); } // Your scheduled task logic here (must be idempotent) return Response.json({ success: true }); }, }; ``` **Why good:** CRON_SECRET verification prevents unauthorized invocation, idempotent design handles duplicate delivery, simple cron expression syntax **Gotcha:** Crons only run on Production deployments, not Preview. Vercel may deliver events more than once. See [examples/cron-jobs.md](examples/cron-jobs.md) for schedule expressions and handler patterns. --- ### Pattern 6: Headers, Redirects, and Rewrites Configure routing rules in `vercel.json` with optional `has`/`missing` conditions for matching request properties. ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "headers": [ { "source": "/(.*)", "headers": [ { "key": "X-Content-Type-Options", "value": "nosniff" }, { "key": "X-Frame-Options", "value": "DENY" } ] } ], "redirects": [ { "source": "/blog/:slug", "destination": "/posts/:slug", "permanent": true } ], "rewrites": [ { "source": "/api/:path*", "destination": "https://api.example.com/:path*" } ] } ``` **Why good:** Declarative routing rules applied at the CDN layer (fast), pattern matching with named segments (`:slug`, `:path*`), conditional matching with `has`/`missing` See [examples/routing.md](examples/routing.md) for geo-based redirects, conditional headers, and rewrite patterns. --- ### Pattern 7: Environment Variables Vercel provides built-in environment variables and supports custom ones via the dashboard. Access framework-agnostic variables via `process.env`. ```typescript // Built-in environment variables const DEPLOYMENT_ENV = process.env.VERCEL_ENV; // "production" | "preview" | "development" const DEPLOYMENT_URL = process.env.VERCEL_URL; // e.g., "my-app-abc123.vercel.app" const FUNCTION_REGION = process.env.VERCEL_REGION; // e.g., "iad1" const GIT_COMMIT_SHA = process.env.VERCEL_GIT_COMMIT_SHA; ``` **Gotcha:** `VERCEL_URL` does not include the protocol (`https://`). Always prepend it: `` `https://${process.env.VERCEL_URL}` `` See [examples/core.md](examples/core.md) for environment variable scoping and CRON_SECRET setup. --- ### Pattern 8: Monorepo Setup Vercel supports monorepos by setting the Root Directory per project. Use `ignoreCommand` to skip builds when the relevant app hasn't changed. ```json { "$schema": "https://openapi.vercel.sh/vercel.json", "ignoreCommand": "git diff --quiet HEAD^ HEAD ./", "buildCommand": "pnpm --filter my-app build", "installCommand": "pnpm install" } ``` **Key setup:** In the Vercel dashboard, set Root Directory to `apps/my-app` (or wherever the app lives). Vercel CLI should always be run from the monorepo root. See [examples/monorepo.md](examples/monorepo.md) for monorepo setup, Nx integration, custom ignore patterns, and vercel.ts programmatic config. </patterns> --- <performance> ## Performance Optimization ### Function Placement | Setting | Purpose | When to use | | ------------------------- | --------------------------------------- | -------------------------------------------- | | `regions: ["iad1"]` | Deploy to specific region(s) | When your data source is in a known region | | `functionFailoverRegions` | Failover during outages | Enterprise plan, critical APIs | | `fluid: true` | Reuse instances for concurrent requests | Default for new projects since April 2025 | | Per-function `regions` | Different regions per function | When functions access different data sources | For runtime comparison and plan limits, see [reference.md](reference.md). </performance> --- <decision_framework> ## Decision Framework ### Choosing a Runtime ``` What does your function need? | +-- Full Node.js APIs (fs, child_process, native modules)? | --> Node.js runtime | +-- Global low-latency, minimal dependencies? | --> Edge runtime (but consider Node.js + multi-region) | +-- Heavy computation or large dependencies? | --> Node.js runtime (250 MB limit vs 1-4 MB Edge) | +-- Not sure? --> Node.js (default, recommended by Vercel for most cases) ``` ### Choosing Where to Put Logic ``` Does it need to run before every request (auth, redirects)? | +-- YES --> Routing Middleware (middleware.ts) | +-- NO --> Is it a scheduled task? | +-- YES --> Cron job (vercel.json crons + api route) | +-- NO --> Is it an API endpoint? | +-- YES --> Vercel Function (api/ directory) | +-- NO --> Is it a static routing rule? | +-- YES --> vercel.json (headers/redirects/rewrites) +-- NO --> Framework-specific solution ``` ### Static Config vs Programmatic Config ``` Is your configuration static and predictable? | +-- YES --> vercel.json | +-- NO --> Do you need env vars, API calls, or conditional logic at build time? | +-- YES --> vercel.ts (with @vercel/config) +-- NO --> vercel.json ``` </decision_framework> --- <red_flags> ## RED FLAGS **High Priority Issues:** - Storing secrets in `vercel.json` or committing `.env` files -- use the Vercel dashboard Environment Variables or `vercel env` CLI - Missing `CRON_SECRET` verification in cron handlers -- cron endpoints are publicly accessible URLs anyone can call - Using Edge runtime when you need Node.js APIs (fs, native modules, large packages) -- will fail at runtime with missing API errors - Setting `maxDuration` above your plan limit -- deployment will fail - Not setting `regions` when your database is outside `iad1` -- every function call makes a cross-region database round trip **Medium Priority Issues:** - Missing `$schema` in vercel.json -- loses IDE autocompletion and validation that catches config errors before deployment - Using `permanent: true` redirects during development -- browsers cache 308s aggressively, hard to undo - Not using `ignoreCommand` in monorepos -- every commit triggers builds for all apps, wasting build minutes - Hardcoding `VERCEL_URL` without protocol -- `VERCEL_URL` does not include `https://`, must be prepended **Common Mistakes:** - Assuming cron jobs run on Preview deployments -- they only run on Production - Using `statusCode` and `permanent` together in redirects -- they are mutually exclusive - Not making cron handlers idempotent -- Vercel may deliver events more than once - Expecting Edge functions to have file system access -- Edge runtime has no `fs` module - Using `builds` property in vercel.json -- it is legacy, use `functions` instead **Gotchas & Edge Cases:** - Edge Runtime: `eval()`, `new Function()`, and dynamic `WebAssembly.instantiate` are disabled for security - Edge Runtime: Must begin sending response within 25 seconds (can stream up to 300s after) - Routing Middleware: 50ms average CPU time limit on Edge, 4 MB max request body, 14 KB max URL length - `cleanUrls: true` causes 404s in local `vercel dev` but works in production - `has`/`missing` conditions on redirects/headers don't work locally with `vercel dev` - `VERCEL_URL` differs between Production (custom domain) and Preview (generated `.vercel.app` URL) - Fluid compute reuses function instances -- module-level state persists across requests (can be useful for caching, but be aware of stale data) - Function `memory` cannot be set in vercel.json when Fluid compute is enabled -- use the dashboard instead - `vercel.ts` only runs at build time, not at request time -- it generates static config - Edge function code size limits are after gzip: 1 MB (Hobby), 2 MB (Pro), 4 MB (Enterprise) </red_flags> --- <critical_reminders> ## CRITICAL REMINDERS > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants) **(You MUST always include `"$schema": "https://openapi.vercel.sh/vercel.json"` in vercel.json for IDE validation)** **(You MUST place functions in a region close to your data source -- the default `iad1` may add latency if your database is elsewhere)** **(You MUST verify `CRON_SECRET` in cron job handlers -- Vercel cron endpoints are publicly accessible URLs)** **(You MUST design cron jobs to be idempotent -- Vercel may deliver the same cron event more than once)** **(You MUST NOT store secrets in vercel.json or source code -- use Environment Variables in the Vercel dashboard)** **Failure to follow these rules will result in security vulnerabilities (exposed secrets, unprotected cron endpoints), poor performance (cross-region latency), and deployment failures (invalid config, exceeded limits).** </critical_reminders>
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.