Deploy to Vercel: Production Setup
Deploy to Vercel: Production Setup
Deploying a Next.js app to Vercel is fast. Deploying it correctly — with environment variables, database connections, CI/CD, custom domains, and monitoring — takes more care. This lesson covers the full production setup.
Initial Deployment
# Install Vercel CLI
npm install -g vercel
# Login
vercel login
# Deploy (from project root)
vercel
# Follow prompts:
# - Link to existing project or create new
# - Vercel auto-detects Next.js and configures build settings
Or go to vercel.com/new, import your GitHub repository, and click Deploy. Vercel detects Next.js automatically.
Environment Variables
Set these in Vercel dashboard → Project → Settings → Environment Variables:
Required for production:
DATABASE_URL postgresql://user:pass@host:5432/db?sslmode=require
AUTH_SECRET [32+ character random string]
AUTH_GOOGLE_ID [from Google Cloud Console]
AUTH_GOOGLE_SECRET [from Google Cloud Console]
NEXT_PUBLIC_APP_URL https://yourapp.com
For your OAuth callback URLs in production, update the authorized redirect URIs:
- Google:
https://yourapp.com/api/auth/callback/google - GitHub:
https://yourapp.com/api/auth/callback/github
Pull environment variables to your local machine:
vercel env pull .env.local
Production Database
Never use your local database in production. Use a managed provider:
Neon (recommended for Next.js):
# Install Neon serverless driver (faster cold starts)
npm install @neondatabase/serverless
# Connection string format:
DATABASE_URL=postgres://user:pass@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require
Configuration for Prisma with Neon:
// src/lib/db.ts
import { PrismaClient } from "@prisma/client";
import { Pool, neonConfig } from "@neondatabase/serverless";
import { PrismaNeon } from "@prisma/adapter-neon";
import ws from "ws";
neonConfig.webSocketConstructor = ws;
const connectionString = process.env.DATABASE_URL!;
const pool = new Pool({ connectionString });
const adapter = new PrismaNeon(pool);
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const db = globalForPrisma.prisma ?? new PrismaClient({ adapter });
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;
Run migrations on deploy:
// package.json
{
"scripts": {
"build": "prisma generate && prisma migrate deploy && next build"
}
}
CI/CD with GitHub Actions
Vercel deploys automatically when you push to GitHub. But you probably want to run tests first:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
env:
DATABASE_URL: ${{ secrets.DATABASE_URL_TEST }}
AUTH_SECRET: test-secret-for-ci
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- run: npm run lint
- run: npm test -- --run
- run: npm run build
# Only deploy to Vercel on main branch push
- name: Deploy to Vercel
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: npx vercel --prod --token ${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
Add secrets to your GitHub repository → Settings → Secrets and variables → Actions.
Custom Domain
- Vercel dashboard → Project → Settings → Domains
- Add your domain (e.g.,
myapp.comandwww.myapp.com) - Vercel shows you DNS records to add at your registrar:
- A record:
@→76.76.21.21 - CNAME:
www→cname.vercel-dns.com
- A record:
- Wait for DNS propagation (usually under 15 minutes)
- SSL certificate is provisioned automatically
Preview Deployments
Every pull request gets a unique URL automatically. Use this to:
- Share features with teammates before merging
- Test deployment-specific issues (env vars, edge functions)
- Let QA test without a staging server
Configure branch-specific environments in Vercel → Settings → Environment Variables → select "Preview" scope for staging values.
Performance Monitoring
Enable Vercel Analytics:
npm install @vercel/analytics @vercel/speed-insights
// src/app/layout.tsx
import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
);
}
Vercel Analytics shows real user visits. Speed Insights shows Core Web Vitals from real users.
Edge vs Serverless Functions
// Default: Serverless function (Node.js runtime)
export async function GET() {
const data = await db.course.findMany(); // Prisma, full Node.js
return Response.json(data);
}
// Edge runtime: Faster cold starts, but no Node.js APIs
export const runtime = "edge";
export async function GET() {
// Can't use Prisma here — use fetch or Neon's HTTP driver
const data = await fetch("https://api.example.com/courses").then(r => r.json());
return Response.json(data);
}
Use Edge runtime for:
- Authentication middleware (already runs on edge)
- Geolocation-based redirects
- A/B testing at the edge
Use Node.js runtime (default) for:
- Database queries with Prisma
- File system access
- Any Node.js-specific npm package
Production Checklist
✅ Environment variables set in Vercel (not hardcoded)
✅ Production database set up (Neon, Supabase, Railway)
✅ Prisma migration runs in build script
✅ Custom domain configured with HTTPS
✅ OAuth redirect URIs updated for production domain
✅ Error monitoring enabled (Sentry, Vercel logs)
✅ Analytics enabled (Vercel Analytics or Plausible)
✅ Security headers configured (HSTS, CSP, X-Frame-Options)
✅ next.config.ts has image domains configured
✅ No secrets in git history
Security Headers
// next.config.ts
const config: NextConfig = {
async headers() {
return [
{
source: "/(.*)",
headers: [
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
],
},
];
},
};
Next lesson: Performance optimization and Core Web Vitals — making your Next.js app fast for real users.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises