Vercel Deployment Skill
Expert deployment of Next.js applications to Vercel with optimal build settings, environment configuration, and custom domain setup.
Quick Reference
| Task |
File/Command |
| Configure build |
vercel.json |
| Set env vars |
Vercel Dashboard or CLI |
| Deploy |
vercel --prod |
| Check status |
vercel inspect |
| Custom domain |
Vercel Dashboard > Settings > Domains |
Project Structure
project/
├── vercel.json # Vercel configuration
├── next.config.js # Next.js configuration
├── .env.local # Local development (not committed)
├── .env.example # Template (committed)
├── docs/
│ └── deployment.md # Deployment documentation
└── frontend/ # Next.js app
├── app/
├── public/
└── src/
vercel.json Configuration
Basic Configuration
{
"buildCommand": "npm run build",
"outputDirectory": ".next",
"installCommand": "npm install",
"framework": "nextjs",
"regions": ["iad1"],
"env": {
"NEXT_PUBLIC_API_URL": "@api_url"
}
}
Advanced Configuration with Rewrites
{
"version": 2,
"buildCommand": "npm run build",
"outputDirectory": ".next",
"installCommand": "npm install",
"framework": "nextjs",
"regions": ["iad1"],
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://api.yourdomain.com/:path*"
}
],
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
}
]
},
{
"source": "/static/:path*",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/api/:path*",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
],
"redirects": [
{
"source": "/old-path/:path*",
"destination": "/new-path/:path*",
"permanent": true
},
{
"source": "/www/:path*",
"destination": "/:path*",
"permanent": true
}
]
}
Next.js Config Integration
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Output configuration for Vercel
output: 'standalone',
// Image optimization
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'api.yourdomain.com',
pathname: '/uploads/**',
},
],
formats: ['image/avif', 'image/webp'],
},
// Headers for security
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin',
},
],
},
];
},
// Redirects
async redirects() {
return [
{
source: '/legacy/:path*',
destination: '/:path*',
permanent: true,
},
];
},
// Experimental features
experimental: {
serverActions: {
allowedOrigins: ['yourdomain.com'],
},
},
};
module.exports = nextConfig;
Environment Variables
Variable Naming Convention
| Prefix |
Access |
Description |
NEXT_PUBLIC_ |
Client + Server |
Exposed to browser |
| No prefix |
Server only |
Backend/API use only |
Required Variables
# .env.example - Copy to .env.local for local dev
# API Configuration
# Backend API URL (development)
NEXT_PUBLIC_API_URL="http://localhost:8000/api/v1"
# Authentication
NEXT_PUBLIC_AUTH_ENABLED="true"
# Feature Flags
NEXT_PUBLIC_ENABLE_DARK_MODE="true"
NEXT_PUBLIC_SHOW_BETA_FEATURES="false"
Production Variables (Set in Vercel Dashboard)
# Environment variables for Production
# API Configuration
NEXT_PUBLIC_API_URL="https://api.yourdomain.com"
# Optional: Analytics
NEXT_PUBLIC_GA_ID="G-XXXXXXXXXX"
NEXT_PUBLIC_POSTHOG_KEY="phc_xxx"
# Optional: Error tracking
NEXT_PUBLIC_SENTRY_DSN="https://xxx@sentry.io/xxx"
Sensitive Variables (Server-Only)
These should NOT have NEXT_PUBLIC_ prefix:
# Server-only (never exposed to client)
API_SECRET_KEY="vercel-secret-key"
DATABASE_URL="postgresql://..."
REDIS_URL="redis://..."
Vercel CLI Deployment
Installation and Login
# Install Vercel CLI
npm i -g vercel
# Login to Vercel
vercel login
# Link to project
cd frontend
vercel link
Deployment Commands
# Deploy to preview (staging)
vercel
# Deploy to production
vercel --prod
# Deploy with environment
vercel --env=NODE_ENV=production
# Pull environment variables from Vercel
vercel env pull .env.local
CI/CD Deployment
# In CI pipeline
npm i -g vercel
vercel --token=$VERCEL_TOKEN --yes
Custom Domain Setup
Adding Domain in Vercel
- Go to Vercel Dashboard > Project > Settings > Domains
- Add your domain (e.g.,
yourdomain.com)
- Configure DNS records
DNS Configuration
# For root domain (yourdomain.com)
Type: A
Name: @
Value: 76.76.21.21
# For www subdomain
Type: CNAME
Name: www
Value: cname.vercel-dns.com.
# For API subdomain (optional)
Type: CNAME
Name: api
Value: cname.vercel-dns.com.
www to Root Redirect
{
"redirects": [
{
"source": "/:path*",
"destination": "https://yourdomain.com/:path*",
"permanent": true
}
]
}
API Proxy Setup
Option 1: Vercel Rewrites (Recommended)
{
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://api.yourdomain.com/:path*"
}
]
}
Option 2: Next.js Rewrite
// next.config.js
async rewrites() {
return [
{
source: '/api/:path*',
destination: `${process.env.NEXT_PUBLIC_API_URL}/:path*`,
},
];
}
Edge Function for API (Advanced)
// frontend/app/api/[...route]/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/endpoint`,
{
headers: {
'Authorization': request.headers.get('Authorization') || '',
'Content-Type': 'application/json',
},
}
);
const data = await response.json();
return NextResponse.json(data);
}
Build Optimization
Build Command
# Standard build
npm run build
# With TypeScript check only
npm run build -- --no-lint
# Custom build
next build
Performance Settings
// next.config.js
const nextConfig = {
// Enable SWC minifier (faster builds)
swcMinify: true,
// Compiler options
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
// Image optimization
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
// Enable react strict mode
reactStrictMode: true,
// Generate Etag
generateEtags: true,
};
Bundle Analysis
# Install bundle analyzer
npm install -D @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
// your config
});
Error Pages
Custom 404 Page
// frontend/app/not-found.tsx
import Link from 'next/link';
export default function NotFound() {
return (
<div className="min-h-screen flex flex-col items-center justify-center">
<h1 className="text-4xl font-bold">404 - Page Not Found</h1>
<p className="mt-2 text-gray-600">
The page you're looking for doesn't exist.
</p>
<Link
href="/"
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded"
>
Go Home
</Link>
</div>
);
}
Custom 500 Page
// frontend/app/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="min-h-screen flex flex-col items-center justify-center">
<h1 className="text-4xl font-bold">Something went wrong!</h1>
<p className="mt-2 text-gray-600">
An unexpected error occurred.
</p>
<button
onClick={() => reset()}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded"
>
Try Again
</button>
</div>
);
}
Global Error Boundary
// frontend/app/global-error.tsx
'use client';
export default function GlobalError({
error,
}: {
error: Error & { digest?: string };
}) {
return (
<html>
<body>
<div className="min-h-screen flex items-center justify-center">
<h1 className="text-2xl">Application Error</h1>
</div>
</body>
</html>
);
}
Deployment Checklist
Deployment Documentation Template
# Deployment Guide
## Quick Deploy
[](https://vercel.com/new)
## Environment Variables
### Development
```bash
NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1
Production
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
Custom Domain
- Add
yourdomain.com in Vercel Dashboard > Settings > Domains
- Configure DNS:
- A record:
@ -> 76.76.21.21
- CNAME:
www -> cname.vercel-dns.com
Troubleshooting
Build Fails
# Run locally to see error
npm run build
Environment Variables Not Working
- Check variable name starts with
NEXT_PUBLIC_ for client access
- Redeploy after adding new variables
404 on Production
- Verify
vercel.json outputDirectory matches build output
- Check page files are in
app/ directory (App Router)
## Integration Points
| Skill | Integration |
|-------|-------------|
| `@frontend-nextjs-app-router` | Next.js App Router configuration |
| `@env-config` | Environment variable management |
| `@tailwind-css` | CSS build optimization |
| `@api-route-design` | API routes and rewrites |
| `@error-handling` | Custom error pages |