Claude Skill

infra-platform-vercel

Vercel deployment platform — project configuration, serverless/edge functions, Routing Middleware, cron jobs, environment variables, monorepo setup

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_infra-platform-vercel_skills_infra-platform-vercel-3a51ef5.zip · 18 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/infra-platform-vercel/skills/infra-platform-vercel
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git 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) 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 -- 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.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)




<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>

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.

No comments yet.

Reviews (0)

No reviews yet.

Related