deploy-vercel
Provides comprehensive Vercel deployment standards optimized for Next.js applications, covering environment configuration, edge functions, serverless architecture, database integration, cron jobs, and production best practices
$ インストール
git clone https://github.com/majiayu000/claude-skill-registry /tmp/claude-skill-registry && cp -r /tmp/claude-skill-registry/skills/data/deploy-vercel ~/.claude/skills/claude-skill-registry// tip: Run this command in your terminal to install the skill
name: deploy-vercel description: Provides comprehensive Vercel deployment standards optimized for Next.js applications, covering environment configuration, edge functions, serverless architecture, database integration, cron jobs, and production best practices
Vercel Deployment Standards
This skill provides complete guidelines for deploying applications to Vercel, with specific focus on Next.js optimizations and serverless architecture patterns.
Pre-Deployment Checklist
Repository Requirements
- Code pushed to GitHub/GitLab/Bitbucket
- Next.js project properly configured
-
package.jsonwith build scripts -
.gitignoreincludes.env,.vercel,node_modules - Dependencies correctly categorized
- Database migrations strategy defined
- API routes follow serverless best practices
Vercel-Specific Requirements
-
vercel.jsonconfigured (optional but recommended) - Environment variables documented
- Build output optimized
- Edge runtime considered for performance-critical routes
- ISR/SSG strategy defined
Initial Setup
Connect Repository
Via Dashboard:
- Go to vercel.com
- Click "Add New" → "Project"
- Import Git Repository
- Select repository
- Configure project settings
- Deploy
Via CLI:
# Install Vercel CLI
npm i -g vercel
# Login
vercel login
# Deploy
vercel
# Production deploy
vercel --prod
Project Configuration
vercel.json:
{
"buildCommand": "npm run build",
"outputDirectory": ".next",
"framework": "nextjs",
"rewrites": [
{ "source": "/api/:path*", "destination": "/api/: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"
}
]
}
],
"regions": ["iad1", "sfo1"],
"crons": [
{
"path": "/api/cron/daily-cleanup",
"schedule": "0 0 * * *"
}
]
}
Environment Variables
Configuration
Via Dashboard:
- Project Settings → Environment Variables
- Add variables for each environment:
- Production
- Preview
- Development
Via CLI:
# Add environment variable
vercel env add DATABASE_URL production
# Pull environment variables locally
vercel env pull .env.local
Required Variables for Next.js
# Core Next.js
NODE_ENV=production # Auto-set by Vercel
NEXT_PUBLIC_VERCEL_URL=${VERCEL_URL} # Auto-provided
# Application URLs
NEXT_PUBLIC_SITE_URL=https://yourdomain.com
NEXTAUTH_URL=https://yourdomain.com
NEXTAUTH_SECRET=your-secret-min-32-chars
# Database (Vercel Postgres / Supabase / PlanetScale)
DATABASE_URL=postgresql://...
POSTGRES_URL=postgresql://...
POSTGRES_PRISMA_URL=postgresql://... # For Prisma
POSTGRES_URL_NON_POOLING=postgresql://... # For migrations
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...
# External Services
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
SENDGRID_API_KEY=SG...
OPENAI_API_KEY=sk-...
# Storage (Vercel Blob)
BLOB_READ_WRITE_TOKEN=vercel_blob_...
# KV (Vercel KV - Redis)
KV_REST_API_URL=https://...
KV_REST_API_TOKEN=...
KV_REST_API_READ_ONLY_TOKEN=...
# Edge Config (Feature flags)
EDGE_CONFIG=https://edge-config.vercel.com/...
Environment Variable Types
Public (Client-Side):
- Must start with
NEXT_PUBLIC_ - Exposed to browser
- Embedded at build time
Private (Server-Side Only):
- No prefix needed
- Available in API routes and Server Components
- Never exposed to client
System Variables (Auto-provided by Vercel):
VERCEL_URL- Deployment URLVERCEL_ENV- production | preview | developmentVERCEL_GIT_COMMIT_SHA- Git commit hashVERCEL_GIT_COMMIT_REF- Git branch name
Build Configuration
Next.js Optimization
next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
// Production optimizations
reactStrictMode: true,
swcMinify: true,
// Image optimization
images: {
domains: ['yourdomain.com', 'images.unsplash.com'],
formats: ['image/avif', 'image/webp'],
},
// Headers
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
}
]
}
]
},
// Redirects
async redirects() {
return [
{
source: '/old-page',
destination: '/new-page',
permanent: true,
}
]
},
// Experimental features
experimental: {
serverActions: {
bodySizeLimit: '2mb',
},
},
}
module.exports = nextConfig
Build Performance
Optimize Build Times:
// package.json
{
"scripts": {
"build": "next build",
"postbuild": "next-sitemap"
}
}
Turbopack (Faster Builds):
# Use Turbopack for builds
next build --turbo
Serverless Functions
API Routes Configuration
Function Config:
// app/api/users/route.ts
import { NextResponse } from 'next/server'
export const runtime = 'edge' // or 'nodejs' (default)
export const maxDuration = 60 // seconds (Pro plan: 300s)
export const dynamic = 'force-dynamic' // Disable caching
export async function GET(request: Request) {
// Your logic here
return NextResponse.json({ users: [] })
}
Runtime Options:
| Runtime | Use Case | Execution Time | Cold Start |
|---|---|---|---|
edge | Low-latency, simple logic | 30s (Hobby), 900s (Pro) | ~5ms |
nodejs | Complex logic, libraries | 10s (Hobby), 300s (Pro) | ~100ms |
Edge Functions
When to Use Edge:
- Authentication checks
- Geolocation-based routing
- A/B testing
- Bot detection
- Simple data fetching
Example:
// app/api/geo/route.ts
export const runtime = 'edge'
export async function GET(request: Request) {
const geo = request.headers.get('x-vercel-ip-country') || 'unknown'
return new Response(JSON.stringify({ country: geo }), {
headers: { 'content-type': 'application/json' }
})
}
Database Integration
Vercel Postgres
Setup:
- Dashboard → Storage → Create Database → Postgres
- Automatically provisions connection strings
- Environment variables auto-added
Environment Variables (Auto-provided):
POSTGRES_URL="postgresql://..."
POSTGRES_PRISMA_URL="postgresql://..." # With pgBouncer
POSTGRES_URL_NON_POOLING="postgresql://..." # Direct connection
Connection:
// lib/db.ts
import { Pool } from '@vercel/postgres'
export const db = new Pool({
connectionString: process.env.POSTGRES_URL,
})
// Usage
const { rows } = await db.query('SELECT * FROM users')
Prisma Integration
Prisma Setup:
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
Database Migrations:
// package.json
{
"scripts": {
"postinstall": "prisma generate",
"db:migrate": "prisma migrate deploy",
"vercel-build": "prisma generate && prisma migrate deploy && next build"
}
}
Vercel Build Settings:
# Build Command
npm run vercel-build
# This ensures migrations run before build
Supabase Integration
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
Cron Jobs
Configuration
vercel.json:
{
"crons": [
{
"path": "/api/cron/daily-cleanup",
"schedule": "0 0 * * *"
},
{
"path": "/api/cron/send-reminders",
"schedule": "0 9 * * *"
},
{
"path": "/api/cron/weekly-report",
"schedule": "0 10 * * 1"
}
]
}
Cron API Route:
// app/api/cron/daily-cleanup/route.ts
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
// Verify cron secret
const authHeader = request.headers.get('authorization')
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
try {
// Run cleanup logic
await cleanupOldData()
return NextResponse.json({ success: true })
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
}
async function cleanupOldData() {
// Your cleanup logic
console.log('Running daily cleanup...')
}
Cron Schedule Format:
* * * * *
│ │ │ │ │
│ │ │ │ └─ Day of week (0-7)
│ │ │ └─── Month (1-12)
│ │ └───── Day of month (1-31)
│ └─────── Hour (0-23)
└───────── Minute (0-59)
Examples:
- Every hour:
0 * * * * - Daily at 2 AM:
0 2 * * * - Every Monday:
0 0 * * 1 - First of month:
0 0 1 * *
Static & Dynamic Rendering
Rendering Strategies
Static Generation (SSG):
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return <Article post={post} />
}
Incremental Static Regeneration (ISR):
// app/products/[id]/page.tsx
export const revalidate = 3600 // Revalidate every hour
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProduct(params.id)
return <Product data={product} />
}
Server-Side Rendering (SSR):
// app/dashboard/page.tsx
export const dynamic = 'force-dynamic' // Always SSR
export default async function Dashboard() {
const session = await getSession()
const data = await getUserData(session.userId)
return <DashboardView data={data} />
}
Vercel KV (Redis)
Setup
- Dashboard → Storage → Create Database → KV
- Select region
- Environment variables auto-added
Usage:
// lib/kv.ts
import { kv } from '@vercel/kv'
// Set value
await kv.set('user:123', { name: 'John' })
// Get value
const user = await kv.get('user:123')
// Set with expiration
await kv.setex('session:abc', 3600, { userId: '123' })
// Delete
await kv.del('user:123')
// Increment
await kv.incr('page:views')
Rate Limiting Example:
// app/api/protected/route.ts
import { kv } from '@vercel/kv'
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const ip = request.headers.get('x-forwarded-for') || 'unknown'
const key = `ratelimit:${ip}`
const requests = await kv.incr(key)
if (requests === 1) {
await kv.expire(key, 60) // 60 second window
}
if (requests > 10) {
return NextResponse.json(
{ error: 'Too many requests' },
{ status: 429 }
)
}
// Process request
return NextResponse.json({ success: true })
}
Vercel Blob (File Storage)
Setup & Usage
// Upload file
import { put } from '@vercel/blob'
export async function POST(request: Request) {
const form = await request.formData()
const file = form.get('file') as File
const blob = await put(file.name, file, {
access: 'public',
token: process.env.BLOB_READ_WRITE_TOKEN,
})
return Response.json({ url: blob.url })
}
// List files
import { list } from '@vercel/blob'
const { blobs } = await list()
// Delete file
import { del } from '@vercel/blob'
await del(url)
Monitoring & Logs
Real-Time Logs
Via Dashboard:
- Project → Logs
- Filter by function, status, time range
- Search logs
Via CLI:
# Stream logs
vercel logs my-app --follow
# Logs from specific deployment
vercel logs my-app --deployment dpl_xxx
# Filter by function
vercel logs my-app --function /api/users
Analytics
Web Analytics (Built-in):
- Dashboard → Analytics
- Page views, unique visitors
- Top pages, referrers
- Real-time data
Speed Insights:
- Core Web Vitals
- Performance metrics
- Real user monitoring
Enable in next.config.js:
module.exports = {
experimental: {
webVitalsAttribution: ['CLS', 'LCP', 'FCP', 'FID', 'TTFB']
}
}
Error Tracking
Integrate Sentry:
// sentry.config.ts
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.VERCEL_ENV,
tracesSampleRate: 1.0,
})
Custom Domains
Setup
Add Domain:
- Project Settings → Domains
- Enter domain name
- Choose domain type (apex or subdomain)
Configure DNS:
For subdomain (www, app, etc.):
Type: CNAME
Name: www
Value: cname.vercel-dns.com
TTL: 3600
For apex domain (yourdomain.com):
Type: A
Name: @
Value: 76.76.21.21
Type: AAAA (IPv6)
Name: @
Value: 2606:4700:4700::1111
SSL Certificate:
- Automatically provisioned
- Free via Let's Encrypt
- Auto-renewal
Performance Optimization
Image Optimization
// Use Next.js Image component
import Image from 'next/image'
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // For above-the-fold images
placeholder="blur" // Better UX
/>
next.config.js:
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
}
}
Edge Config (Feature Flags)
// lib/feature-flags.ts
import { get } from '@vercel/edge-config'
export async function isFeatureEnabled(flag: string): Promise<boolean> {
try {
return await get(flag) || false
} catch {
return false
}
}
// Usage
const newUIEnabled = await isFeatureEnabled('new-ui')
Bundle Analysis
# Analyze bundle size
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
})
# Run analysis
ANALYZE=true npm run build
Deployment Strategies
Preview Deployments
Automatic:
- Every push to non-production branch creates preview
- Unique URL:
my-app-git-branch-username.vercel.app - Test changes before merging
Comments on PRs:
- Vercel bot comments with preview URL
- QA team can review
- Automatic updates on new commits
Production Deployments
Automatic:
- Push to
mainbranch triggers production deploy - Alias to custom domain
Manual:
# Deploy to production
vercel --prod
# Rollback to previous deployment
vercel rollback
Environment-Specific Builds
// lib/config.ts
const config = {
apiUrl: process.env.VERCEL_ENV === 'production'
? 'https://api.yourdomain.com'
: process.env.VERCEL_ENV === 'preview'
? 'https://api-staging.yourdomain.com'
: 'http://localhost:3001',
}
Deployment Checklist
Pre-Deployment
- All environment variables set
- Database migrations tested
- Build passes locally (
npm run build) - No console errors or warnings
- Images optimized
- API routes tested
- Authentication working
- Third-party integrations configured
Post-Deployment
- Custom domain resolving correctly
- SSL certificate active
- Health check endpoint responding
- Database connection working
- Cron jobs running (check logs)
- Analytics tracking
- Error tracking configured
- Performance metrics monitored
- SEO meta tags correct
- Social share previews working
Troubleshooting
Build Errors
# Common issues:
- Missing dependencies → Check package.json
- TypeScript errors → Run tsc --noEmit
- Environment variable missing → Set in dashboard
- Out of memory → Upgrade plan or optimize build
Runtime Errors
# Check:
- Function logs in dashboard
- Environment variables in deployment
- Database connection
- External API availability
Performance Issues
# Investigate:
- Bundle size (use bundle analyzer)
- Slow database queries
- Unoptimized images
- Blocking server-side operations
- Cold starts (consider edge runtime)
Best Practices
Security
- Use environment secrets
- Implement CSRF protection
- Rate limit API routes
- Validate all inputs
- Sanitize user content
- Use secure headers
- Keep dependencies updated
Performance
- Use ISR for dynamic content
- Implement edge caching
- Optimize images with next/image
- Minimize JavaScript bundle
- Use compression
- Lazy load components
- Prefetch critical routes
Cost Optimization
- Use Edge functions where possible (cheaper)
- Implement proper caching strategies
- Optimize serverless function duration
- Use ISR instead of SSR when possible
- Monitor bandwidth usage
- Clean up unused deployments
Resources
Pro Tip: Use preview deployments aggressively for QA. Every PR should have a working preview URL before merging to production.
Repository
