| name | vercel-deployment |
| description | Deploy Next.js portfolio to Vercel using MCP tools, CLI commands, and production-ready configurations. |
| author | Jaivish Chauhan @ GDG SSIT |
| version | 1.0.0 |
| url | https://github.com/JaivishChauhan/vibecoding-starter |
Vercel Deployment Mastery
Core Philosophy
Deployment is not "pushing code"—it's delivering value reliably. We use Vercel's edge network, preview deployments, and analytics to ship with confidence. Every commit is deployable; every deployment is reversible.
Vercel MCP Integration
Available MCP Tools
The Vercel MCP provides these tools for deployment automation:
1. vercel_deploy - Deploy to Vercel
2. vercel_list_projects - List all projects
3. vercel_get_project - Get project details
4. vercel_list_deployments - List deployments
5. vercel_get_deployment - Get deployment details
6. vercel_list_domains - List domains
7. vercel_add_domain - Add custom domain
8. vercel_get_env - Get environment variables
9. vercel_set_env - Set environment variables
10. vercel_delete_env - Delete environment variables
Deployment Workflow
await mcp.vercel_list_projects();
await mcp.vercel_deploy({
projectId: "prj_xxx",
target: "production",
ref: "main",
});
await mcp.vercel_get_deployment({
deploymentId: "dpl_xxx",
});
Project Configuration
vercel.json (Full Configuration)
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"framework": "nextjs",
"buildCommand": "next build",
"installCommand": "npm install",
"outputDirectory": ".next",
"regions": ["iad1", "sfo1", "cdg1"],
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
},
{
"key": "Referrer-Policy",
"value": "strict-origin-when-cross-origin"
},
{
"key": "Permissions-Policy",
"value": "camera=(), microphone=(), geolocation=()"
}
]
},
{
"source": "/fonts/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/_next/static/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
],
"redirects": [
{
"source": "/github",
"destination": "https://github.com/yourusername",
"permanent": false
},
{
"source": "/linkedin",
"destination": "https://linkedin.com/in/yourusername",
"permanent": false
},
{
"source": "/twitter",
"destination": "https://twitter.com/yourusername",
"permanent": false
},
{
"source": "/resume",
"destination": "/resume.pdf",
"permanent": false
}
],
"rewrites": [
{
"source": "/api/analytics/:path*",
"destination": "https://analytics.example.com/:path*"
}
],
"crons": [
{
"path": "/api/cron/update-stats",
"schedule": "0 0 * * *"
}
]
}
next.config.js for Vercel
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "images.unsplash.com",
},
{
protocol: "https",
hostname: "cdn.sanity.io",
},
{
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
],
formats: ["image/avif", "image/webp"],
},
experimental: {
optimizeCss: true,
optimizePackageImports: ["lucide-react", "framer-motion"],
},
logging: {
fetches: {
fullUrl: true,
},
},
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
],
},
];
},
async redirects() {
return [
{
source: "/blog/old-post",
destination: "/blog/new-post",
permanent: true,
},
];
},
};
module.exports = nextConfig;
Environment Variables
Setting via MCP
await mcp.vercel_set_env({
projectId: "prj_xxx",
key: "DATABASE_URL",
value: "postgresql://...",
target: ["production"],
type: "encrypted",
});
await mcp.vercel_set_env({
projectId: "prj_xxx",
key: "NEXT_PUBLIC_SITE_URL",
value: "https://yourportfolio.com",
target: ["production", "preview", "development"],
});
Environment Variable Categories
# .env.local (local development - git ignored)
# ============================================
# PUBLIC (exposed to browser)
# ============================================
NEXT_PUBLIC_SITE_URL=https://yourportfolio.com
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
# ============================================
# PRIVATE (server-only)
# ============================================
# Email (Resend)
RESEND_API_KEY=re_xxxxx
# CMS (Sanity/Contentful)
SANITY_PROJECT_ID=xxxxx
SANITY_DATASET=production
SANITY_API_TOKEN=xxxxx
# Database (if using)
DATABASE_URL=postgresql://...
# Analytics (PostHog)
POSTHOG_API_KEY=phc_xxxxx
# AI (if applicable)
OPENAI_API_KEY=sk-xxxxx
Accessing Environment Variables
const apiKey = process.env.RESEND_API_KEY;
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
const secret = process.env.RESEND_API_KEY;
Domain Configuration
Add Custom Domain via MCP
await mcp.vercel_add_domain({
projectId: "prj_xxx",
domain: "yourportfolio.com",
});
await mcp.vercel_add_domain({
projectId: "prj_xxx",
domain: "www.yourportfolio.com",
redirect: "yourportfolio.com",
});
DNS Configuration
Type Name Value TTL
A @ 76.76.21.21 Auto
CNAME www cname.vercel-dns.com Auto
SSL/TLS
Vercel automatically provisions and renews SSL certificates via Let's Encrypt.
Deployment Strategies
1. Preview Deployments
Every push to a branch creates a unique preview URL.
Feature branch: feature/new-hero
Preview URL: portfolio-git-feature-new-hero-username.vercel.app
2. Production Deployment
await mcp.vercel_deploy({
projectId: "prj_xxx",
target: "production",
ref: "main",
});
3. Instant Rollback
const deployments = await mcp.vercel_list_deployments({
projectId: "prj_xxx",
limit: 10,
});
await mcp.vercel_deploy({
projectId: "prj_xxx",
deploymentId: deployments[1].id,
target: "production",
});
Edge Middleware
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-Content-Type-Options", "nosniff");
const country = request.geo?.country || "US";
response.headers.set("X-User-Country", country);
const variant =
request.cookies.get("ab-variant")?.value ||
(Math.random() > 0.5 ? "a" : "b");
if (!request.cookies.get("ab-variant")) {
response.cookies.set("ab-variant", variant, {
maxAge: 60 * 60 * 24 * 30,
});
}
return response;
}
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};
Analytics & Monitoring
Vercel Analytics (Built-in)
import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
);
}
Web Vitals Tracking
export function reportWebVitals(metric: any) {
if (metric.label === "web-vital") {
console.log(metric);
}
}
Cron Jobs
Configuration
{
"crons": [
{
"path": "/api/cron/daily-stats",
"schedule": "0 0 * * *"
},
{
"path": "/api/cron/weekly-newsletter",
"schedule": "0 9 * * 1"
}
]
}
Cron Handler
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new NextResponse("Unauthorized", { status: 401 });
}
try {
await updateDailyStats();
return NextResponse.json({ success: true });
} catch (error) {
return NextResponse.json({ error: "Cron failed" }, { status: 500 });
}
}
Build Optimization
Analyze Bundle Size
{
"scripts": {
"analyze": "ANALYZE=true next build"
}
}
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
module.exports = withBundleAnalyzer(nextConfig);
Tree Shaking Imports
import { motion, AnimatePresence, useAnimation } from "framer-motion";
module.exports = {
experimental: {
optimizePackageImports: [
"lucide-react",
"framer-motion",
"@radix-ui/react-icons",
],
},
};
Error Handling in Production
Global Error Boundary
"use client";
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html>
<body>
<div className="flex min-h-screen items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold">Something went wrong!</h1>
<p className="mt-4 text-muted">Error ID: {error.digest}</p>
<button
onClick={reset}
className="mt-6 rounded-lg bg-brand-500 px-6 py-3 text-white"
>
Try again
</button>
</div>
</div>
</body>
</html>
);
}
Error Monitoring Integration
export function logError(error: Error, context?: Record<string, any>) {
if (process.env.NODE_ENV === "production") {
console.error("Production error:", error, context);
}
}
Pre-Deployment Checklist
Build Verification
npm run build
npm run lint
npm run type-check
Performance Checks
Security Checks
SEO Checks
Troubleshooting
"Build works locally but fails on Vercel"
-
Case sensitivity: Linux is case-sensitive; Windows/Mac aren't
import Button from "./button";
import Button from "./Button";
-
Environment variables: Pull from Vercel
vercel env pull .env.local
-
Node version mismatch:
{
"engines": {
"node": "20.x"
}
}
"Dynamic usage errors"
Using headers(), cookies(), or searchParams opts out of static generation.
export const dynamic = "force-dynamic";
export async function generateStaticParams() {
return [{ slug: "project-1" }, { slug: "project-2" }];
}
"Cold start latency"
- Use Edge Runtime where possible
- Reduce function bundle size
- Use ISR instead of SSR when data is cacheable
export const runtime = "edge";
export const revalidate = 3600;
CLI Quick Reference
vercel --prod
vercel
vercel env pull .env.local
vercel link
vercel logs <url>
vercel inspect <url>
vercel ls
vercel alias set <deployment-url> <domain>
vercel domains add yourportfolio.com