
Vercel Deployment
- 55 installs
- 14 repo stars
- Updated March 2, 2026
- oakoss/agent-skills
Helps with devops & ci/cd tasks during AI-assisted development.
About
vercel-deployment is a Claude Code skill for devops & ci/cd. It helps solo builders move faster with AI-assisted coding.
- vercel-deployment
- DevOps & CI/CD
- AI-coding skill
Vercel Deployment by the numbers
- 55 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #690 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oakoss/agent-skills --skill vercel-deploymentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 55 |
|---|---|
| repo stars | ★ 14 |
| Last updated | March 2, 2026 |
| Repository | oakoss/agent-skills ↗ |
What it does
Helps with devops & ci/cd tasks during AI-assisted development.
Files
Vercel Deployment
Overview
Vercel is a cloud platform for deploying frontend frameworks and serverless functions with automatic CI/CD, preview deployments, and edge infrastructure. Projects are configured via vercel.json (or programmatic vercel.ts), the Vercel dashboard, or the Vercel CLI.
When to use: Static sites, SSR frameworks (Next.js, SvelteKit, Nuxt), serverless API routes, edge functions, preview environments per pull request, monorepo deployments.
When NOT to use: Long-running backend processes (use containers), WebSocket servers (use dedicated infrastructure), heavy compute workloads (use cloud VMs), applications requiring persistent file system access.
Quick Reference
| Pattern | Tool / API | Key Points |
|---|---|---|
| Project config | vercel.json or vercel.ts | Root of project, controls builds/routing/functions |
| Rewrites | rewrites array | Routes request to destination, URL unchanged |
| Redirects | redirects array | Changes URL, permanent: true for 301 |
| Headers | headers array | Custom response headers per path pattern |
| Clean URLs | cleanUrls: true | Strips .html extensions |
| Trailing slash | trailingSlash: false | Consistent URL format |
| Environment vars | Dashboard or vercel env | Scoped to production, preview, development |
| Custom domains | Project Settings > Domains | A record for apex, CNAME for subdomains |
| Preview deploys | Automatic per PR | Each push gets unique URL |
| Edge functions | export const runtime = 'edge' | V8 isolates, low latency, limited Node.js APIs |
| Serverless functions | api/ directory or framework routes | Node.js runtime, full API access |
| Deploy via CLI | vercel or vercel --prod | Preview by default, --prod for production |
| Promote deploy | vercel promote <url> | Promote existing preview to production |
| Monorepo | Root directory setting per project | One repo, multiple Vercel projects |
| GitHub integration | Automatic on push | Zero-config CI/CD with preview per branch |
| Programmatic config | vercel.ts with @vercel/config | Typed, dynamic configuration alternative |
| Fluid compute | Enabled by default for new projects | Multi-request workers, 300s default duration |
| Rolling releases | Incremental rollout with monitoring | Gradual traffic shift with auto-rollback triggers |
| Firewall rules | vercel.json WAF configuration | Block threats via dashboard, API, or config file |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
Using .env.production for preview-specific values | Use Vercel environment variables scoped to preview environment |
Expecting VERCEL_URL to include protocol | Prepend https:// manually; use VERCEL_PROJECT_PRODUCTION_URL for stable production URL |
Adding only apex domain without www | Add both yourdomain.com and www.yourdomain.com to avoid 404 on one |
| Using Node.js APIs in edge functions | Edge runtime uses V8 only; fs, path, process are unavailable |
| Exceeding 1024 static redirects | Use bulkRedirects property for large redirect sets (CSV/JSON/JSONL) |
| Creating QueryClient or fetching in edge without streaming | Use streaming responses for long operations in edge functions |
Setting maxDuration above plan limit | Fluid compute default: 300s; Hobby: 60s, Pro: 300s, Enterprise: 900s max |
| Conflicting DNS records for custom domain | Remove duplicate A records; keep only the Vercel-pointing record |
| Not awaiting build in CI before deploy | Use vercel build then vercel deploy --prebuilt for reliable CI deploys |
| Ignoring monorepo root directory setting | Set root directory per project in Vercel dashboard for correct builds |
Delegation
- Infrastructure review: Use
Taskagent to audit deployment configuration - Environment debugging: Use
Exploreagent to trace environment variable issues - CI/CD pipeline review: Use
code-revieweragent for GitHub Actions workflow review
References
- Configuration: vercel.json, builds, rewrites, redirects, headers
- Environment variables and custom domains
- Edge and serverless functions
- CLI commands and CI/CD integration
CLI and CI/CD
Vercel CLI Setup
Install globally or use via npx:
npm i -g vercelAuthenticate:
vercel loginLink a local project to a Vercel project:
vercel linkThis creates a .vercel/ directory with project.json containing orgId and projectId.
Core CLI Commands
Deploy
vercel # Deploy to preview
vercel --prod # Deploy to production
vercel --prebuilt # Deploy pre-built output (skip build on Vercel)Build Locally
vercel build # Build using preview environment
vercel build --prod # Build using production environmentvercel build outputs to .vercel/output/. Pair with --prebuilt for CI pipelines where you control the build step.
Promote
Promote an existing deployment to production without rebuilding:
vercel promote <deployment-url>
vercel promote <deployment-url> --yes # Skip confirmationInspect and Logs
vercel inspect <deployment-url> # View deployment details
vercel logs <deployment-url> # Stream function logs
vercel ls # List recent deploymentsEnvironment Variables
vercel env ls # List all variables
vercel env add VAR_NAME production # Add variable
vercel env rm VAR_NAME production # Remove variable
vercel env pull .env.local # Pull dev variables locallyLocal Development
vercel dev # Run local dev server with Vercel features
vercel dev --listen 4000 # Custom portvercel dev simulates serverless functions, environment variables, and routing locally.
Domains
vercel domains ls # List domains
vercel domains add example.com # Add domain
vercel domains inspect example.com # View DNS infoRollback
vercel rollback # Rollback to previous production deployment
vercel rollback <deployment-url> # Rollback to specific deploymentGitHub Integration (Zero-Config)
Connect a GitHub repository in the Vercel dashboard for automatic deployments:
- Push to production branch (e.g.,
main) triggers a production deployment - Push to any other branch triggers a preview deployment
- Pull requests get preview URLs posted as comments
No configuration files needed. Vercel auto-detects the framework and configures builds.
Disabling Auto-Deploy for Specific Branches
{
"git": {
"deploymentEnabled": {
"main": true,
"develop": true,
"feature/*": false
}
}
}Ignored Build Step
Skip deployments when specific files change (useful for monorepos):
{
"ignoreCommand": "git diff --quiet HEAD^ HEAD -- ."
}Returns exit code 0 (no changes, skip build) or 1 (changes detected, proceed).
GitHub Actions (Custom CI/CD)
For more control than the built-in integration, use GitHub Actions with the Vercel CLI.
Required Secrets
Add these as GitHub repository secrets:
| Secret | Source |
|---|---|
VERCEL_TOKEN | Vercel dashboard > Settings > Tokens |
VERCEL_ORG_ID | .vercel/project.json after vercel link |
VERCEL_PROJECT_ID | .vercel/project.json after vercel link |
Preview Deployment on Pull Request
name: Preview Deployment
on:
pull_request:
branches: [main]
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Vercel CLI
run: npm i -g vercel
- name: Pull Environment
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Build
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy
id: deploy
run: |
url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
echo "url=$url" >> "$GITHUB_OUTPUT"
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Preview: ${{ steps.deploy.outputs.url }}`
})Production Deployment on Push to Main
name: Production Deployment
on:
push:
branches: [main]
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Vercel CLI
run: npm i -g vercel
- name: Pull Environment
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- name: Build
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}Monorepo Setup
Dashboard Configuration
Each app in a monorepo is a separate Vercel project. Configure per project:
1. Root Directory: Set to the app's directory (e.g., apps/web) 2. Build Command: Framework-specific (e.g., next build) or Turborepo command 3. Install Command: Run from root (e.g., pnpm install --frozen-lockfile)
Turborepo Integration
Use Turborepo for efficient monorepo builds with caching:
{
"buildCommand": "cd ../.. && npx turbo run build --filter=web",
"installCommand": "pnpm install --frozen-lockfile"
}Monorepo with GitHub Actions
Each app gets its own workflow with a project-specific ID:
name: Deploy Web App
on:
push:
branches: [main]
paths:
- 'apps/web/**'
- 'packages/**'
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_WEB_PROJECT_ID }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 2
- name: Install Vercel CLI
run: npm i -g vercel
- name: Pull Environment
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- name: Build
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}Use paths filters to deploy only when relevant files change. Set fetch-depth: 2 for Turborepo change detection.
Ignored Build Step for Monorepos
Use ignoreCommand to skip unchanged apps in automatic deployments:
npx turbo-ignoreConfigure in vercel.json or Project Settings > Git > Ignored Build Step:
{
"ignoreCommand": "npx turbo-ignore"
}turbo-ignore checks if the current app or its dependencies changed since the last successful deployment.
Vercel CLI Quick Reference
| Command | Description |
|---|---|
vercel | Deploy to preview |
vercel --prod | Deploy to production |
vercel build | Build locally |
vercel deploy --prebuilt | Deploy pre-built output |
vercel promote <url> | Promote to production |
vercel rollback | Rollback production |
vercel dev | Local dev server |
vercel env pull | Pull env vars locally |
vercel logs <url> | Stream function logs |
vercel inspect <url> | Deployment details |
vercel domains ls | List domains |
vercel link | Link local to project |
vercel login | Authenticate |
vercel whoami | Current user |
Configuration
vercel.json Basics
Place vercel.json in the project root. It controls build settings, routing, functions, and deployment behavior.
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "npm run build",
"outputDirectory": "dist",
"installCommand": "pnpm install",
"framework": "nextjs"
}The $schema field enables IDE autocompletion and validation.
Programmatic Configuration with vercel.ts
As an alternative to vercel.json, use a typed config file. Install the package first:
npm install @vercel/configimport { type VercelConfig } from '@vercel/config';
export const config: VercelConfig = {
buildCommand: 'npm run build',
cleanUrls: true,
trailingSlash: false,
};Supported filenames: vercel.ts, vercel.js, vercel.mjs, vercel.cjs, vercel.mts.
Build Settings
Configure builds via vercel.json or the dashboard under Project Settings > General.
{
"buildCommand": "pnpm run build",
"outputDirectory": ".next",
"installCommand": "pnpm install --frozen-lockfile",
"devCommand": "pnpm dev",
"framework": "nextjs"
}Common framework values: nextjs, vite, remix, sveltekit, nuxtjs, astro, gatsby, angular, hugo, eleventy. Setting a framework auto-configures build/output defaults.
Override the Node.js version used during builds:
{
"engines": {
"node": "20.x"
}
}Rewrites
Rewrites route requests to a different destination without changing the browser URL. Useful for SPA routing, API proxying, and friendly URLs.
{
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://api.example.com/:path*"
},
{ "source": "/(.*)", "destination": "/index.html" }
]
}Path parameters use :param syntax. Wildcards use :path* to capture all segments.
Order matters: more specific rewrites should come first. API routes should precede catch-all SPA rewrites.
{
"rewrites": [
{ "source": "/docs/:slug", "destination": "/documentation/:slug" },
{ "source": "/blog/:year/:month/:slug", "destination": "/posts/:slug" },
{ "source": "/app/:path*", "destination": "/index.html" }
]
}Redirects
Redirects change the URL in the browser. Use permanent: true for 301 (SEO-friendly) or permanent: false for 307 (temporary).
{
"redirects": [
{ "source": "/old-page", "destination": "/new-page", "permanent": true },
{
"source": "/blog/:slug",
"destination": "/posts/:slug",
"permanent": true
},
{
"source": "/twitter",
"destination": "https://twitter.com/vercel",
"permanent": false
}
]
}Limit: 1024 static redirects per project. For larger sets, use bulkRedirects:
{
"bulkRedirects": [{ "source": "/redirects.csv" }]
}The CSV format: source,destination,permanent (one redirect per line).
Headers
Set custom response headers for specific paths. Commonly used for CORS, security headers, and caching.
{
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{ "key": "Access-Control-Allow-Methods", "value": "GET, POST, OPTIONS" }
]
},
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
]
}
]
}Cache control for static assets:
{
"headers": [
{
"source": "/assets/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}URL Formatting
Control URL appearance with cleanUrls and trailingSlash:
{
"cleanUrls": true,
"trailingSlash": false
}| Setting | Effect |
|---|---|
cleanUrls: true | /about.html becomes /about |
trailingSlash: false | /about/ redirects to /about |
trailingSlash: true | /about redirects to /about/ |
Pick one trailing slash strategy and apply it consistently to avoid redirect loops and duplicate content.
Functions Configuration
Configure serverless function behavior per-route or globally:
{
"functions": {
"api/heavy-computation.ts": {
"maxDuration": 60,
"memory": 1024
},
"api/**/*.ts": {
"maxDuration": 30
}
}
}| Plan | Default Duration | Max Duration | Fluid Compute |
|---|---|---|---|
| Hobby | 10s | 60s | 300s default |
| Pro | 15s | 300s | 300s default |
| Enterprise | 15s | 900s | 900s max |
Fluid Compute is enabled by default for new projects. It allows a single worker to handle multiple concurrent requests, improving resource utilization and reducing cold starts. Functions using Fluid Compute get a default execution time of 300s and are billed only for active CPU time.
Regions
Deploy functions to specific regions:
{
"regions": ["iad1", "sfo1", "cdg1"]
}Common regions: iad1 (US East), sfo1 (US West), cdg1 (Paris), hnd1 (Tokyo), syd1 (Sydney). Functions default to iad1 (US East).
Ignoring Files
Exclude files from deployment:
{
"ignoreCommand": "git diff --quiet HEAD^ HEAD -- .",
"git": {
"deploymentEnabled": {
"main": true,
"staging": true
}
}
}The ignoreCommand skips deployment if it exits with code 0 (no changes). Useful for monorepos to skip unchanged packages.
Full Example
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "pnpm run build",
"outputDirectory": "dist",
"framework": "vite",
"cleanUrls": true,
"trailingSlash": false,
"headers": [
{
"source": "/(.*)",
"headers": [{ "key": "X-Frame-Options", "value": "DENY" }]
}
],
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://api.example.com/:path*"
},
{ "source": "/(.*)", "destination": "/index.html" }
],
"redirects": [
{
"source": "/old-docs/:path*",
"destination": "/docs/:path*",
"permanent": true
}
],
"regions": ["iad1"]
}Edge and Serverless Functions
Runtime Overview
Vercel supports two function runtimes:
| Feature | Serverless (Node.js) | Edge (V8) |
|---|---|---|
| Runtime | Node.js | V8 isolates |
| Cold start | 100-1000ms | Sub-50ms |
| Max bundle size | 50MB (compressed) | 4MB |
| Max request body | 4.5MB | 1MB |
| Node.js APIs | Full | Limited subset |
| Network | TCP/UDP supported | HTTP only |
| Execution regions | Configurable | All edge locations |
| Use case | Complex logic, DB connections | Low-latency, lightweight |
Serverless Functions
File-Based Routing (api/ Directory)
Create files in the api/ directory at the project root. Each file becomes an endpoint:
api/
├── users.ts → GET/POST /api/users
├── users/[id].ts → GET/PUT/DELETE /api/users/:id
└── health.ts → GET /api/healthimport { type VercelRequest, type VercelResponse } from '@vercel/node';
export default function handler(req: VercelRequest, res: VercelResponse) {
const { id } = req.query;
res.status(200).json({ id, name: 'Example User' });
}Framework-Based Routes (Next.js)
Next.js App Router route handlers in app/api/:
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const data = await fetchData();
return NextResponse.json(data);
}
export async function POST(request: Request) {
const body = await request.json();
const result = await createItem(body);
return NextResponse.json(result, { status: 201 });
}Function Configuration
Set per-function options via the config export:
export const config = {
maxDuration: 60,
};
export default function handler(req: VercelRequest, res: VercelResponse) {
// Long-running operation
}Or configure globally in vercel.json:
{
"functions": {
"api/heavy/*.ts": {
"maxDuration": 60,
"memory": 1024
}
}
}Memory options: 128, 256, 512, 1024, 2048, 3008 (MB).
Edge Functions
Selecting Edge Runtime
Export a runtime config to opt into edge:
export const runtime = 'edge';
export default function handler(request: Request) {
return new Response(JSON.stringify({ message: 'Hello from the edge' }), {
headers: { 'Content-Type': 'application/json' },
});
}Next.js App Router edge route handler:
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const name = searchParams.get('name') ?? 'World';
return Response.json({ greeting: `Hello, ${name}!` });
}Edge Runtime Limitations
The edge runtime uses V8 isolates (not Node.js). These Node.js APIs are unavailable:
fs(file system)pathchild_processnet/dgram(TCP/UDP)process.envaccess varies (useprocess.env.VAR_NAMEdirectly)- Native Node.js modules (e.g.,
cryptofull API — use Web Crypto API instead)
Available Web APIs include: fetch, Request, Response, URL, Headers, TextEncoder, TextDecoder, crypto.subtle, ReadableStream, WritableStream.
When to Use Edge vs Serverless
Use Edge for:
- Authentication checks and redirects
- A/B testing and feature flags
- Geolocation-based responses
- Simple API responses (JSON transforms)
- Content personalization
Use Serverless for:
- Database connections (TCP required)
- File processing
- Complex computation
- Third-party SDKs requiring Node.js
- Operations exceeding 4MB bundle size
Streaming Responses
Stream data from both runtimes for long-running operations:
Edge Streaming
export const runtime = 'edge';
export async function GET() {
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' },
});
}Serverless Streaming (Node.js)
export const config = { supportsResponseStreaming: true };
export default function handler(req: VercelRequest, res: VercelResponse) {
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Transfer-Encoding', 'chunked');
const chunks = ['Hello', ' ', 'World'];
let i = 0;
const interval = setInterval(() => {
if (i < chunks.length) {
res.write(chunks[i]);
i++;
} else {
clearInterval(interval);
res.end();
}
}, 100);
}Middleware
Middleware runs before every request. It executes on the Edge runtime:
import { NextResponse } from 'next/server';
import { type NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
if (!request.cookies.has('session')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};Middleware cannot:
- Access databases directly (no TCP)
- Use Node.js-specific APIs
- Return response bodies (only redirect, rewrite, or set headers)
Use the matcher config to limit which paths trigger middleware. Avoid running middleware on static assets.
Cron Jobs
Schedule serverless functions to run on a schedule:
{
"crons": [
{
"path": "/api/cron/cleanup",
"schedule": "0 0 * * *"
},
{
"path": "/api/cron/sync",
"schedule": "*/15 * * * *"
}
]
}The cron handler should verify the request is from Vercel:
export default function handler(req: VercelRequest, res: VercelResponse) {
if (req.headers.authorization !== `Bearer ${process.env.CRON_SECRET}`) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Run scheduled task
}Cron limits by plan: Hobby (2 crons, daily minimum), Pro (40 crons, per-minute minimum).
Environment Variables and Domains
Environment Variables
Vercel environment variables are scoped to three environments: Production, Preview, and Development. Set them in the dashboard under Project Settings > Environment Variables, or via the CLI.
Adding Variables via Dashboard
Select which environments a variable applies to. A variable can apply to one, two, or all three environments.
| Environment | When Used |
|---|---|
| Production | Deployments from the production branch (usually main) |
| Preview | Deployments from non-production branches and pull requests |
| Development | Local development via vercel dev or vercel env pull |
Adding Variables via CLI
vercel env add DATABASE_URL production
vercel env add DATABASE_URL production preview
vercel env add NEXT_PUBLIC_API_URL production preview developmentRemove a variable:
vercel env rm DATABASE_URL productionList all variables:
vercel env lsPulling Variables for Local Development
Pull development environment variables into a local .env file:
vercel env pull .env.localThis downloads all variables scoped to the Development environment. Share with teammates who have project access.
System Environment Variables
Vercel automatically provides system variables during builds and at runtime:
| Variable | Description |
|---|---|
VERCEL | Always "1" when running on Vercel |
VERCEL_ENV | "production", "preview", or "development" |
VERCEL_URL | Deployment URL without protocol (e.g., my-app-abc123.vercel.app) |
VERCEL_PROJECT_PRODUCTION_URL | Stable production domain (e.g., my-app.vercel.app) |
VERCEL_BRANCH_URL | Branch-specific URL (e.g., my-app-git-feature.vercel.app) |
VERCEL_GIT_COMMIT_SHA | Git commit hash |
VERCEL_GIT_COMMIT_REF | Git branch name |
VERCEL_GIT_REPO_SLUG | Repository name |
VERCEL_URL does not include the protocol. Always prepend https://:
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: 'http://localhost:3000';For a stable production URL, prefer VERCEL_PROJECT_PRODUCTION_URL over VERCEL_URL (which changes per deployment).
Next.js and NODE_ENV
NODE_ENV is always "production" on Vercel deployments, including preview. This is by design. Do not use NODE_ENV to distinguish between preview and production. Use VERCEL_ENV instead:
const isProduction = process.env.VERCEL_ENV === 'production';
const isPreview = process.env.VERCEL_ENV === 'preview';Sensitive Variables
Prefix variables with NEXT_PUBLIC_ (Next.js) or VITE_ (Vite) only if they should be exposed to the browser. API keys and secrets should never use these prefixes.
NEXT_PUBLIC_API_URL=https://api.example.com # Exposed to browser
DATABASE_URL=postgresql://... # Server-only
STRIPE_SECRET_KEY=sk_live_... # Server-onlyCustom Domains
Adding a Domain
Navigate to Project Settings > Domains and add your domain. Vercel provides DNS configuration instructions.
DNS Configuration
Two approaches depending on your DNS provider:
Option A: External DNS (recommended for existing DNS)
For apex domains (e.g., example.com), add an A record:
Type: A
Name: @
Value: 76.76.21.21For subdomains (e.g., www.example.com, app.example.com), add a CNAME:
Type: CNAME
Name: www
Value: cname.vercel-dns.comOption B: Vercel Nameservers (required for wildcard domains)
Point your domain registrar to Vercel nameservers:
ns1.vercel-dns.com
ns2.vercel-dns.comMigrate any existing DNS records to Vercel before switching nameservers.
Adding Both Apex and www
Always add both example.com and www.example.com. Configure one as the primary domain and the other as a redirect:
example.com → Primary
www.example.com → Redirects to example.comVercel does not auto-add the www variant. Omitting it causes 404 errors for users who type www..
Domain per Environment
Assign custom domains to specific environments for staging workflows:
app.example.com → Production
staging.example.com → Preview (staging branch)Configure branch-specific domains in Project Settings > Domains by selecting the target environment and branch.
Branch Deployments
Preview URLs
Every push to a non-production branch generates a unique preview URL:
my-app-<hash>-team.vercel.app # Unique per commit
my-app-git-feature-branch-team.vercel.app # Stable per branchPreview deployments use Preview environment variables automatically.
Staging Environment
Create a staging workflow with custom environments:
1. Create a custom environment (e.g., "Staging") in Project Settings > Environments 2. Set branch rules to match your staging branch (e.g., staging) 3. Assign a custom domain (e.g., staging.example.com) 4. Configure environment-specific variables
Deployments to the staging branch automatically use the Staging environment configuration.
Staged Production Deployments
Deploy to production without immediately going live:
1. Disable "Auto-assign Custom Production Domains" in Branch Tracking settings 2. Deploy to production — the build runs but domains are not updated 3. Verify the deployment at its unique URL 4. Promote manually via dashboard or CLI: vercel promote <deployment-url>
Protection and Access
Restrict preview deployments to authenticated users:
{
"git": {
"deploymentEnabled": {
"main": true,
"staging": true
}
}
}Enable Vercel Authentication or password protection for preview URLs in Project Settings > Deployment Protection.