← All posts
·12 min read

Claude Code with Vercel: Deploys, Env Vars, Edge Functions

Claude CodeVercelDeploymentWorkflow
Claude Code with Vercel: Deploys, Env Vars, Edge Functions

Why Vercel projects need a Claude Code config

Vercel is opinionated about how a project gets built and served. The build container has a specific Node version and a specific way it interprets vercel.json, next.config.js, astro.config.mjs, and svelte.config.js. When Claude Code generates code that ignores those constraints, the build fails on Vercel even when it passes locally.

Claude Code knows Vercel: Edge Runtime and Node Runtime, three env var scopes (development, preview, production), and vercel.json for rewrites, headers, redirects, and function config. What it does not know is your project. Without a CLAUDE.md telling it which framework you use, which runtime each route should target, how your env vars are named, and which deploy commands are safe to run unattended, Claude generates code that builds locally and breaks in production.

This guide covers the CLAUDE.md template, vercel.json patterns, env var workflow, runtime decisions, and preview deploy habits that produce consistent shipping. If you are new to Claude Code, the Claude Code setup guide covers installation first.

The Vercel CLAUDE.md template

The CLAUDE.md at your project root is read at the start of every Claude Code session. For a Vercel project, it needs to declare the framework, the runtime targets, the env var conventions, the deploy commands, and the hard rules that prevent destructive operations.

# Vercel project rules

## Stack
- Framework: Next.js 15 (App Router)
- Node: 22.x (matches Vercel build container)
- Package manager: pnpm 9.x (lockfile committed, do not switch to npm)
- Deployment target: Vercel
- Region: lhr1 (London) for serverless, edge runs globally

## Project structure
- app/: Next.js App Router pages and route handlers
- app/api/: route handlers (one route.ts per endpoint)
- lib/: shared modules, no React imports
- components/: client and server components, .tsx only
- public/: static assets served at root
- vercel.json: rewrites, headers, function config (do not delete)
- .env.local: development secrets, gitignored
- .env.example: every required variable, no real values

## Runtime conventions
- Default route handler runtime: nodejs
- Edge runtime ONLY for: auth checks, geolocation, A/B routing, header rewrites
- Long-running jobs (>10s expected): use background functions or queue, never inline
- Database: Postgres via @vercel/postgres (uses POSTGRES_URL env var)

## Env var rules
- All env vars listed in .env.example with a comment describing purpose
- NEXT_PUBLIC_ prefix ONLY for browser-exposed values (public keys, feature flags)
- Server-only secrets must NOT have NEXT_PUBLIC_ prefix
- Add to Vercel dashboard or via `vercel env add`, never hardcode

## Deploy commands
- Local dev: `pnpm dev` (runs Next on :3000)
- Local Vercel build (mirrors prod): `vercel build`
- Preview deploy: `vercel` (creates preview URL on a branch)
- Production deploy: ONLY via git push to main, never `vercel --prod` directly

## Hard rules
- NEVER run `vercel --prod` from Claude Code without explicit user request
- NEVER edit env vars in the Vercel dashboard via API without confirming the scope (development / preview / production)
- NEVER add a new env var without updating .env.example
- NEVER ship a route handler without explicit `export const runtime` declaration
- NEVER delete or rewrite vercel.json wholesale, edit specific keys only

The runtime convention rule is the highest leverage line. Without it, Claude sometimes omits export const runtime, leaving the platform to use the default. That default has shifted across Next.js versions and Vercel build images, so silent reliance produces inconsistent behaviour between dev and production. With the rule, every new route file includes the runtime declaration explicitly.

The deploy command rule is second. Claude has terminal access and can run vercel --prod if it decides that is what you wanted. That decision should belong to you. The hard rule prevents pushing to production unattended. The git push to main path goes through your Vercel git integration, which gives you build logs and a chance to cancel.

vercel.json patterns

vercel.json is the configuration file Vercel reads at build time. It controls URL rewrites, response headers, redirects, function configuration, and crons. Most projects need a small, stable vercel.json. Claude Code can edit it safely if you give it the patterns it should use and a hard rule against rewriting it from scratch.

A typical pattern for a Next.js or Astro project on Vercel:

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "buildCommand": "pnpm build",
  "installCommand": "pnpm install --frozen-lockfile",
  "framework": "nextjs",
  "regions": ["lhr1"],
  "functions": {
    "app/api/heavy/route.ts": {
      "maxDuration": 60,
      "memory": 1024
    },
    "app/api/edge-check/route.ts": {
      "runtime": "edge"
    }
  },
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Frame-Options", "value": "DENY" },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
      ]
    },
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "https://app.example.com" }
      ]
    }
  ],
  "rewrites": [
    {
      "source": "/docs/:path*",
      "destination": "https://docs.example.com/:path*"
    }
  ]
}

A few things matter. The $schema line gives Claude and your editor live validation. If Claude tries to add a key that does not exist in the schema, the editor flags it. The regions key pins serverless functions to a single region. Edge runtime ignores this and runs globally regardless.

The functions block is where per-route config lives. Claude needs to know maxDuration is bounded by your Vercel plan (Hobby: 10s, Pro: 60s default and 300s max, Enterprise: higher). Adding this to CLAUDE.md prevents Claude from generating maxDuration: 900 on a Pro account, which would fail at deploy.

The headers block applies security headers. Claude preserves it across edits if you never ask it to "rewrite vercel.json", which is exactly why the CLAUDE.md hard rule says edit specific keys only. Bulk rewrites of structured config files are where Claude introduces drift.

For framework-side conventions that pair with this Vercel config, see the Claude Code with Next.js guide, the Claude Code with Astro guide, and the Claude Code with SvelteKit guide.

Environment variable workflow

Env vars on Vercel have three scopes: Development (used by vercel dev locally), Preview (every preview deployment), and Production (deploys to your production domain). A variable can exist in one, two, or all three scopes with different values. This is powerful and easy to misuse. Claude needs an explicit workflow to avoid pushing a development key into production scope.

The canonical commands:

# List env vars across all scopes
vercel env ls

# Add a new env var, prompts for scope and value
vercel env add MY_API_KEY

# Add a var only for production
vercel env add STRIPE_SECRET_KEY production

# Pull all development vars into .env.local (overwrites the file)
vercel env pull .env.local

# Pull only production vars (rare, useful for debugging)
vercel env pull .env.production --environment=production

# Remove a var from a specific scope
vercel env rm MY_API_KEY preview

The vercel env pull command is the workflow primitive Claude should use most often. When a new env var is added on the Vercel dashboard, vercel env pull brings it into .env.local. Pushing local vars to Vercel requires vercel env add per variable. There is no bulk push command and that is a feature.

The "every env var goes in .env.example" rule makes the workflow auditable. When Claude needs a new env var, it adds the name and description to .env.example first, then runs vercel env add. The example file is the contract. Anyone joining the project, including Claude in a fresh session, can read it and know what is required.

# .env.example pattern
# Stripe secret key for server-side calls. Use sk_test_* for dev, sk_live_* for prod.
STRIPE_SECRET_KEY=

# Public Stripe key, embedded in client-side code. Safe to expose.
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=

# Postgres connection string, automatically set by Vercel Postgres integration.
POSTGRES_URL=

# Resend API key for transactional email. Server-only, never expose to client.
RESEND_API_KEY=

For dev, staging, and production env handling that applies across hosts, see the Claude Code environment variables guide. For Stripe-specific secret handling, the Claude Code with Stripe guide covers the patterns that prevent live key leaks.

Edge runtime vs Node runtime

Vercel offers two runtimes for serverless functions and route handlers. Claude Code defaults to Node runtime unless told otherwise. That default is correct most of the time. The exceptions are worth getting right.

Edge runtime runs at the network edge with cold start times in single-digit milliseconds. It uses a subset of the Web Platform API: fetch, Request, Response, crypto.subtle, URLPattern. It does not have access to the Node standard library, which means no fs, no child_process, no Buffer without polyfills, and no native modules. Execution is capped at 25 seconds total.

Node runtime runs in a regional serverless environment with the full Node API surface. Cold starts are slower, typically 100 to 800 milliseconds. Execution time is bounded by your plan's maxDuration. You get access to npm packages that depend on Node built-ins, including database drivers, image processing libraries, and the Anthropic SDK.

The decision rule that should live in CLAUDE.md:

## Runtime decision tree

Use edge runtime when ALL of the following are true:
- Response is generated from request data + a fast network call (auth check, geo lookup, header rewrite)
- No filesystem access required
- No npm packages with Node built-in dependencies
- Total work fits in 25 seconds with no buffering

Use node runtime when ANY of the following are true:
- Database query that requires a node-native driver
- Image processing, PDF generation, or any binary manipulation
- LLM call to OpenAI / Anthropic / Google (SDKs use streams + Buffer)
- Total response time may exceed 25 seconds
- Working with files on disk (rare on Vercel, but uploads + temp files happen)

Default: nodejs (export const runtime = 'nodejs')

The runtime declaration is one line at the top of a route handler:

// app/api/auth/check/route.ts
export const runtime = 'edge'
export const preferredRegion = 'auto'

export async function GET(request: Request) {
  const country = request.headers.get('x-vercel-ip-country')
  const isAllowed = country !== 'XK'
  return Response.json({ allowed: isAllowed, country })
}
// app/api/generate/route.ts
import Anthropic from '@anthropic-ai/sdk'

export const runtime = 'nodejs'
export const maxDuration = 60

const client = new Anthropic()

export async function POST(request: Request) {
  const { prompt } = await request.json()
  const response = await client.messages.create({
    model: 'claude-opus-4-7',
    max_tokens: 2048,
    messages: [{ role: 'user', content: prompt }]
  })
  return Response.json({ output: response.content[0] })
}

When Claude generates a new route handler, it should pick a runtime before writing the code. The decision tree gives it the criteria. The explicit export const runtime line at the top of the file proves the decision was made deliberately.

For typed handler signatures, the Claude Code with TypeScript guide covers the patterns. If you are weighing Vercel's edge against alternative edge platforms, the Claude Code with Cloudflare Workers guide covers that migration.

Deploy and preview workflow

Claude Code can run vercel commands directly. That capability is useful but needs guardrails. The pattern that works: Claude can run vercel build (local mirror of the build container) and vercel (preview deploy on the current branch). It cannot run vercel --prod. Production happens through git push to main.

The full local-to-preview-to-production flow:

# 1. Make changes locally, develop with hot reload
pnpm dev

# 2. Run a local production build to catch issues before push
vercel build

# 3. Optional: deploy a preview to get a real Vercel URL for testing
vercel

# 4. Commit changes
git add .
git commit -m "feat: new endpoint for usage stats"

# 5. Push to feature branch, opens preview deploy via git integration
git push origin feature/usage-stats

# 6. Open PR, review preview URL, merge when green
# Production deploy happens automatically on merge to main

The vercel build step is undervalued. It runs the same build Vercel runs in production, with the same Node version and the same dev-scope env vars. If your build is going to fail on Vercel, it usually fails here first. Claude should run this before any push that touches build config, lockfiles, or runtime declarations.

The preview deploy step (vercel with no flags) is what makes the workflow safe. Every push to a non-main branch creates a preview URL with full backend functionality and isolated preview-scope env vars. Claude can deploy a preview, test it via fetch, and report back. Production stays untouched.

Production protection is enforced two ways: the CLAUDE.md rule against vercel --prod, and Vercel project settings that require git push to main. For multi-host deploy patterns across Vercel, Netlify, Cloudflare, and Render, see the Claude Code deploy guide.

Permission hooks for Vercel projects

Claude Code's permission system in .claude/settings.local.json is where the deploy guardrails become enforceable rather than aspirational. The CLAUDE.md hard rules tell Claude what not to do. Permission hooks tell the harness to refuse if Claude tries anyway.

{
  "permissions": {
    "allow": [
      "Bash(pnpm dev*)",
      "Bash(pnpm build*)",
      "Bash(pnpm test*)",
      "Bash(pnpm lint*)",
      "Bash(vercel build*)",
      "Bash(vercel env ls*)",
      "Bash(vercel env pull*)",
      "Bash(git push origin feat/*)",
      "Bash(git push origin fix/*)"
    ],
    "deny": [
      "Bash(vercel --prod*)",
      "Bash(vercel deploy --prod*)",
      "Bash(vercel env rm*production*)",
      "Bash(vercel env add*production*)",
      "Bash(vercel domains rm*)",
      "Bash(vercel project rm*)",
      "Bash(git push origin main*)",
      "Bash(git push --force*)"
    ]
  }
}

This config lets Claude run safe local commands, inspect env state, pull dev vars to .env.local, and push feature branches. It blocks production deploys, production env mutations, domain removal, project deletion, force pushes, and direct pushes to main. When Claude tries any of those, the harness refuses and shows you the command. You confirm or reject explicitly.

The deny list matters more than the allow list. The allow list is convenience. The deny list is safety. If you have time for only one, configure the deny list first.

What Claude handles well, what to review

Claude generates good Vercel configuration in most places. vercel.json edits to specific keys are accurate. Route handler boilerplate with correct runtime declarations is consistent. Env var patterns following .env.example conventions are clean. Monorepo build setup with turbo, pnpm, or nx is reliable.

Three areas warrant review. First, next.config.js (or the equivalent for SvelteKit and Astro) when changes affect output mode, image optimization, or middleware. These configs have framework-specific quirks that interact with the Vercel build in non-obvious ways. Read the diff carefully.

Second, middleware. Vercel runs Next.js middleware at the edge, so edge-runtime constraints apply. Claude sometimes generates middleware that imports a Node-only package, which builds locally and fails on Vercel. Check imports manually.

Third, cron functions. The cron entry in vercel.json and the route handler must match exactly. A typo in the path or schedule means the cron silently never fires. Verify cron entries in the Vercel dashboard after the first deploy that includes them.

For broader configuration principles, the Claude Code best practices guide covers patterns that apply beyond Vercel.

Hard rules and a working baseline

The Vercel CLAUDE.md template, vercel.json patterns, env var workflow, runtime decision tree, and permission hooks in this guide produce a development environment where deploys are quiet, env vars are auditable, runtime declarations are explicit, and production stays protected.

Six hard rules are worth restating because they are the difference between deploys that ship and deploys that break:

  1. Every route handler exports runtime explicitly.
  2. Every env var lives in .env.example before it lives anywhere else.
  3. vercel.json is edited by key, never rewritten wholesale.
  4. vercel build runs locally before any push that touches build config.
  5. Production deploys go through git push to main, never vercel --prod from Claude.
  6. The .claude/settings.local.json deny list blocks production-scope mutations and force pushes.

The principle behind all six is the same. Claude Code performs at the level of the context you give it. Without a Vercel-specific CLAUDE.md, Claude omits runtime declarations, leaks env vars into the wrong scope, and writes deploy commands that touch production directly. With the configuration above, it respects platform constraints, declares its runtime explicitly, manages env vars through the canonical CLI, and ships through preview deploys. Claudify ships a Vercel-specific CLAUDE.md template and .claude/settings.local.json deny list as part of the Claude Code workflow kit, pre-configured for Next.js, SvelteKit, and Astro projects on Vercel. For the underlying rationale on why CLAUDE.md is the lever that controls Claude's output quality, the CLAUDE.md explained guide covers the principles.

More like this

Ready to upgrade your Claude Code setup?

Get Claudify
Featured on Dofollow.Tools AI Toolz Dir