Vercel
CLI Essentials
npm i -g vercel # install CLI
vercel login # authenticate (browser flow)
vercel link # link current directory to a Vercel project
vercel # deploy to preview
vercel --prod # deploy to production
vercel dev # run local dev server (mirrors Vercel environment)
vercel dev --listen 4000 # custom port
# Environment variables
vercel env ls # list all env vars
vercel env add SECRET_KEY # add interactively (prompts for value and scope)
vercel env add DATABASE_URL production < .env.production # pipe value for specific scope
vercel env pull .env.local # pull remote env vars to local file
vercel env rm SECRET_KEY production # remove from specific environment
# Domains
vercel domains ls # list domains
vercel domains add example.com # add domain
vercel domains inspect example.com # DNS and certificate status
# Logs and inspection
vercel logs https://my-deploy-url.vercel.app # stream deployment logs
vercel inspect <deployment-url> # deployment details (functions, routes, size)
vercel ls # list recent deployments
vercel rollback <deployment-url> # revert production to a previous deployment
Project Configuration (vercel.json)
{
// Rewrites (URL stays the same, content served from destination)
"rewrites": [
{ "source": "/api/:path*", "destination": "/api/:path*" },
{ "source": "/(.*)", "destination": "/index.html" } // SPA fallback
],
// Redirects
"redirects": [
{ "source": "/old-page", "destination": "/new-page", "permanent": true },
{ "source": "/blog/:slug", "destination": "https://blog.example.com/:slug", "statusCode": 308 }
],
// Custom headers
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{ "key": "Cache-Control", "value": "s-maxage=86400" }
]
}
],
// Function configuration
"functions": {
"api/heavy-task.ts": {
"memory": 1024, // MB (128–3008)
"maxDuration": 60 // seconds (Hobby: 60, Pro: 300, Enterprise: 900)
},
"api/edge-fn.ts": {
"runtime": "edge"
}
},
// Cron jobs
"crons": [
{
"path": "/api/cron/daily-cleanup",
"schedule": "0 0 * * *" // standard cron syntax (UTC)
},
{
"path": "/api/cron/hourly-sync",
"schedule": "0 * * * *"
}
],
// Build and output
"buildCommand": "npm run build",
"outputDirectory": "dist",
"installCommand": "npm ci",
"framework": "nextjs", // auto-detected; override if needed
"regions": ["iad1", "sfo1"], // deploy functions to specific regions
// Clean URLs: /about instead of /about.html
"cleanUrls": true,
"trailingSlash": false
}
Framework Detection and Build Settings
Vercel auto-detects frameworks: Next.js, Nuxt, SvelteKit, Remix, Astro, Vite, CRA, Gatsby, Angular, Hugo, etc. Override in project settings or vercel.json when auto-detection fails.
// Override build settings in vercel.json
{
"framework": "vite",
"buildCommand": "vite build",
"outputDirectory": "dist",
"installCommand": "pnpm install",
"devCommand": "vite dev --port $PORT"
}
Node.js version: set in package.json engines field or project settings. Supports 18.x (default) and 20.x.
Environment Variables
Three scopes: Development, Preview, Production.
- Production: available only on production deployments
- Preview: available on preview deployments (each PR gets one)
- Development: available when running
vercel dev
System env vars are auto-injected: VERCEL_ENV, VERCEL_URL, VERCEL_BRANCH_URL, VERCEL_GIT_COMMIT_SHA, VERCEL_GIT_COMMIT_REF.
Sensitive values: mark as "Sensitive" in dashboard (encrypted, never shown in logs). For local dev, use vercel env pull to sync to .env.local.
Serverless Functions
Place files in the /api directory. Each file becomes an endpoint matching its path.
// api/hello.ts → /api/hello
import type { VercelRequest, VercelResponse } from '@vercel/node';
export default function handler(req: VercelRequest, res: VercelResponse) {
const { name = 'World' } = req.query;
res.status(200).json({ message: `Hello ${name}!` });
}
Streaming Responses
// api/stream.ts
import type { VercelRequest, VercelResponse } from '@vercel/node';
export default async function handler(req: VercelRequest, res: VercelResponse) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
for (let i = 0; i < 5; i++) {
res.write(`data: chunk ${i}\n\n`);
await new Promise((r) => setTimeout(r, 500));
}
res.end();
}
Runtime Options
// Set max duration and memory per-function
export const config = {
maxDuration: 30, // seconds
memory: 512, // MB
};
Supported runtimes: Node.js (default), Go, Python, Ruby. Install @vercel/python, @vercel/go, etc. for non-Node runtimes.
Edge Functions
Run on Vercel's edge network (no cold starts, ~0ms startup, limited API surface).
// api/geo.ts
export const config = { runtime: 'edge' };
export default function handler(request: Request) {
const country = request.headers.get('x-vercel-ip-country') || 'unknown';
const city = request.headers.get('x-vercel-ip-city') || 'unknown';
const latitude = request.headers.get('x-vercel-ip-latitude');
const longitude = request.headers.get('x-vercel-ip-longitude');
return Response.json({ country, city, latitude, longitude });
}
Middleware (Next.js)
// middleware.ts (project root)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US';
// Redirect non-US traffic
if (country !== 'US' && request.nextUrl.pathname === '/') {
return NextResponse.redirect(new URL(`/${country.toLowerCase()}`, request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!api|_next/static|favicon.ico).*)'],
};
Deployment Workflow
# Git-based (recommended): push to GitHub/GitLab/Bitbucket
git push origin feature-branch # → creates preview deployment automatically
# Merging to main/production branch # → creates production deployment
# CLI-based
vercel # preview deployment
vercel --prod # production deployment
vercel promote <deployment-url> # promote any deployment to production
# Instant rollback
vercel rollback # rollback production to previous deployment
Preview deployments get unique URLs: project-git-branch-team.vercel.app. Each PR comment shows the preview URL.
Custom Domains
vercel domains add myapp.com
vercel domains add www.myapp.com
vercel domains add "*.myapp.com" # wildcard subdomain (Pro plan+)
DNS configuration:
- Apex domain (myapp.com): A record →
76.76.21.21 - Subdomain (www.myapp.com): CNAME →
cname.vercel-dns.com - Nameservers (full control): point NS records to Vercel's nameservers
SSL certificates are auto-provisioned and renewed via Let's Encrypt.
Storage
Vercel KV (Redis-compatible)
import { kv } from '@vercel/kv';
// Set/Get
await kv.set('user:123', { name: 'Alice', plan: 'pro' });
const user = await kv.get('user:123');
// With expiry
await kv.set('session:abc', data, { ex: 3600 }); // expires in 1 hour
// Hash operations
await kv.hset('config', { theme: 'dark', lang: 'en' });
const theme = await kv.hget('config', 'theme');
// Lists
await kv.lpush('queue', 'task1');
const task = await kv.rpop('queue');
Vercel Postgres
import { sql } from '@vercel/postgres';
// Query
const { rows } = await sql`SELECT * FROM users WHERE id = ${userId}`;
// Insert
await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
// With connection pooling (for serverless)
import { db } from '@vercel/postgres';
const client = await db.connect();
try {
await client.sql`BEGIN`;
await client.sql`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${fromId}`;
await client.sql`UPDATE accounts SET balance = balance + ${amount} WHERE id = ${toId}`;
await client.sql`COMMIT`;
} catch (e) {
await client.sql`ROLLBACK`;
throw e;
} finally {
client.release();
}
Vercel Blob
import { put, del, list, head } from '@vercel/blob';
// Upload
const blob = await put('avatars/user-123.png', file, { access: 'public' });
console.log(blob.url); // https://abc123.public.blob.vercel-storage.com/avatars/user-123.png
// Upload from server action (Next.js)
const blob = await put(filename, body, {
access: 'public',
contentType: 'image/png',
addRandomSuffix: true,
});
// List, head, delete
const { blobs } = await list({ prefix: 'avatars/' });
const metadata = await head(blob.url);
await del(blob.url);
Edge Config (Feature Flags)
Ultra-low latency key-value store read at the edge (~0ms reads).
import { get, getAll, has } from '@vercel/edge-config';
// Read values (typically feature flags)
const isEnabled = await get<boolean>('new-dashboard');
const allFlags = await getAll();
const exists = await has('maintenance-mode');
// In middleware for feature gating
import { NextResponse } from 'next/server';
import { get } from '@vercel/edge-config';
export async function middleware() {
const maintenance = await get<boolean>('maintenance-mode');
if (maintenance) {
return NextResponse.rewrite(new URL('/maintenance', request.url));
}
return NextResponse.next();
}
Update Edge Config via API or dashboard. Changes propagate globally in ~seconds.
ISR (Incremental Static Regeneration)
Time-based ISR (Next.js App Router)
// app/products/page.tsx
export const revalidate = 60; // revalidate every 60 seconds
export default async function Products() {
const products = await fetch('https://api.example.com/products').then(r => r.json());
return <ProductList products={products} />;
}
On-demand Revalidation
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const secret = request.headers.get('x-revalidation-secret');
if (secret !== process.env.REVALIDATION_SECRET) {
return Response.json({ error: 'Invalid secret' }, { status: 401 });
}
const { path, tag } = await request.json();
if (tag) revalidateTag(tag);
else if (path) revalidatePath(path);
return Response.json({ revalidated: true, now: Date.now() });
}
# Trigger revalidation
curl -X POST https://myapp.com/api/revalidate \
-H "x-revalidation-secret: $SECRET" \
-H "Content-Type: application/json" \
-d '{"path": "/products"}'
Monorepo Support
// vercel.json at repo root
{
"projects": [
{ "name": "web", "rootDirectory": "apps/web" },
{ "name": "docs", "rootDirectory": "apps/docs" }
]
}
In project settings (dashboard or CLI):
- Root Directory:
apps/web— Vercel only builds this subdirectory - Include/Ignore Build Step: skip builds when no relevant files changed
# Ignore build step — custom script in project settings
# Only rebuild if files in this project or shared packages changed
git diff --quiet HEAD^ HEAD -- apps/web/ packages/shared/ || exit 0
Works with Turborepo, Nx, pnpm workspaces. Vercel auto-detects Turborepo and uses turbo run build --filter=<project>.
Speed Insights and Web Analytics
npm i @vercel/speed-insights @vercel/analytics
// app/layout.tsx (Next.js)
import { SpeedInsights } from '@vercel/speed-insights/next';
import { Analytics } from '@vercel/analytics/react';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<SpeedInsights />
<Analytics />
</body>
</html>
);
}
Speed Insights tracks Core Web Vitals (LCP, FID, CLS, TTFB, INP) with real user data. Analytics provides pageviews, unique visitors, and referrers — no cookies, GDPR-compliant.
Deployment Protection
// vercel.json
{
"passwordProtection": {
"deploymentType": "preview" // "all" | "preview"
}
}
Options:
- Vercel Authentication: only team members can view (enabled by default for preview deployments)
- Password Protection (Pro+): set a shared password for preview or all deployments
- Trusted IPs (Enterprise): restrict access by IP range
- Deployment Protection Bypass: generate bypass URLs for CI/testing with
x-vercel-protection-bypassheader
Troubleshooting
Build Failures
vercel logs <deployment-url> # check build logs
vercel inspect <deployment-url> # check build output and routes
Common causes:
- Missing env vars in build scope (add to "Preview" and "Production")
- Node.js version mismatch (set in package.json engines or project settings)
- Out of memory: increase function memory or optimize build (
NODE_OPTIONS=--max-old-space-size=4096) - Missing dependencies: ensure
devDependenciesaren't pruned if needed at build time
Function Timeouts
- Hobby: 60s max, Pro: 300s, Enterprise: 900s
- Use streaming for long-running responses
- Offload to background jobs (use Vercel Cron or external queue)
- Set
maxDurationin function config to extend timeout
Cold Starts
- Serverless functions: 250ms–1s cold start typical
- Reduce cold starts: keep bundles small, minimize imports, use dynamic imports
- Edge functions: no cold starts (~0ms startup)
- Use
@vercel/functionswarm-up pattern for critical paths
Cost Optimization
- Function duration: minimize execution time; use edge functions where possible (cheaper, no cold start)
- Bandwidth: enable compression, optimize images with
next/image, set cache headers (s-maxage,stale-while-revalidate) - Edge caching: use
Cache-Control: s-maxage=31536000on static assets; ISR reduces function invocations - Function invocations: batch operations, use SWR/React Query to reduce API calls
- Image optimization: set
images.minimumCacheTTLinnext.config.jsto reduce re-optimizations - Serverless regions: deploy functions to a single region near your database to reduce latency and cost
- Bundle size: tree-shake, lazy-load routes, use
@vercel/nftto trace dependencies accurately