--- 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.json` with build scripts - [ ] `.gitignore` includes `.env`, `.vercel`, `node_modules` - [ ] Dependencies correctly categorized - [ ] Database migrations strategy defined - [ ] API routes follow serverless best practices ### Vercel-Specific Requirements - [ ] `vercel.json` configured (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:** 1. Go to [vercel.com](https://vercel.com) 2. Click "Add New" → "Project" 3. Import Git Repository 4. Select repository 5. Configure project settings 6. Deploy **Via CLI:** ```bash # Install Vercel CLI npm i -g vercel # Login vercel login # Deploy vercel # Production deploy vercel --prod ``` ### Project Configuration **vercel.json:** ```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:** 1. Project Settings → Environment Variables 2. Add variables for each environment: - Production - Preview - Development **Via CLI:** ```bash # Add environment variable vercel env add DATABASE_URL production # Pull environment variables locally vercel env pull .env.local ``` ### Required Variables for Next.js ```bash # 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 URL - `VERCEL_ENV` - production | preview | development - `VERCEL_GIT_COMMIT_SHA` - Git commit hash - `VERCEL_GIT_COMMIT_REF` - Git branch name ## Build Configuration ### Next.js Optimization **next.config.js:** ```javascript /** @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:** ```json // package.json { "scripts": { "build": "next build", "postbuild": "next-sitemap" } } ``` **Turbopack (Faster Builds):** ```bash # Use Turbopack for builds next build --turbo ``` ## Serverless Functions ### API Routes Configuration **Function Config:** ```typescript // 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:** ```typescript // 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:** 1. Dashboard → Storage → Create Database → Postgres 2. Automatically provisions connection strings 3. Environment variables auto-added **Environment Variables (Auto-provided):** ```bash POSTGRES_URL="postgresql://..." POSTGRES_PRISMA_URL="postgresql://..." # With pgBouncer POSTGRES_URL_NON_POOLING="postgresql://..." # Direct connection ``` **Connection:** ```typescript // 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:** ```typescript // 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:** ```json // package.json { "scripts": { "postinstall": "prisma generate", "db:migrate": "prisma migrate deploy", "vercel-build": "prisma generate && prisma migrate deploy && next build" } } ``` **Vercel Build Settings:** ```bash # Build Command npm run vercel-build # This ensures migrations run before build ``` ### Supabase Integration ```typescript // 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:** ```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:** ```typescript // 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):** ```typescript // 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
} ``` **Incremental Static Regeneration (ISR):** ```typescript // 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 } ``` **Server-Side Rendering (SSR):** ```typescript // 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 } ``` ## Vercel KV (Redis) ### Setup 1. Dashboard → Storage → Create Database → KV 2. Select region 3. Environment variables auto-added **Usage:** ```typescript // 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:** ```typescript // 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 ```typescript // 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:** ```bash # 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:** ```javascript module.exports = { experimental: { webVitalsAttribution: ['CLS', 'LCP', 'FCP', 'FID', 'TTFB'] } } ``` ### Error Tracking **Integrate Sentry:** ```typescript // 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:** 1. Project Settings → Domains 2. Enter domain name 3. 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 ```typescript // Use Next.js Image component import Image from 'next/image' Hero ``` **next.config.js:** ```javascript 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) ```typescript // lib/feature-flags.ts import { get } from '@vercel/edge-config' export async function isFeatureEnabled(flag: string): Promise { try { return await get(flag) || false } catch { return false } } // Usage const newUIEnabled = await isFeatureEnabled('new-ui') ``` ### Bundle Analysis ```bash # 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 `main` branch triggers production deploy - Alias to custom domain **Manual:** ```bash # Deploy to production vercel --prod # Rollback to previous deployment vercel rollback ``` ### Environment-Specific Builds ```typescript // 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 ```bash # 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 ```bash # Check: - Function logs in dashboard - Environment variables in deployment - Database connection - External API availability ``` ### Performance Issues ```bash # 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 - [Vercel Documentation](https://vercel.com/docs) - [Next.js Documentation](https://nextjs.org/docs) - [Vercel CLI Reference](https://vercel.com/docs/cli) - [Edge Functions](https://vercel.com/docs/functions/edge-functions) - [Serverless Functions](https://vercel.com/docs/functions/serverless-functions) --- **Pro Tip:** Use preview deployments aggressively for QA. Every PR should have a working preview URL before merging to production.