Deploy and optimize applications on Vercel platform with Edge Functions, Fluid Compute, analytics, and custom domains. Configure deployments, environment variables, serverless functions, and performance settings. Use when deploying to Vercel, optimizing Vercel apps, configuring vercel.json, setting up Edge Functions, Vercel analytics, Speed Insights, custom domains, or when user mentions Vercel deployment, serverless optimization, preview deployments.
A comprehensive skill for building, deploying, and optimizing applications on the Vercel platform. This skill covers everything from initial project setup to production deployment, performance optimization, and monitoring.
# Using pnpm (recommended)
pnpm add -g vercel
# Using npm
npm install -g vercel
# Verify installation
vercel --version
vercel login
# Navigate to your project
cd your-project
# Deploy (interactive setup for first time)
vercel
# Deploy to production
vercel --prod
# Open in browser
vercel open
Vercel is a cloud platform for static sites and serverless functions that enables developers to build, deploy, and scale web applications with zero configuration. It was created by the makers of Next.js and provides first-class support for modern frontend frameworks.
| Feature | Description |
|---|---|
| Zero-Config Deployments | Automatic framework detection and build configuration |
| Edge Functions | Run serverless functions at the edge (300+ locations) |
| Fluid Compute | Optimized serverless with extended durations and reduced cold starts |
| Preview Deployments | Automatic preview URLs for every Git branch |
| Analytics | Built-in Web Analytics and Speed Insights |
| Custom Domains | Easy domain configuration with automatic SSL |
| Git Integration | Native support for GitHub, GitLab, Bitbucket, Azure DevOps |
Vercel provides three default environments:
| Environment | Description | Use Case |
|---|---|---|
| Development | Local development | Testing on your machine |
| Preview | Branch deployments | QA and collaboration |
| Production | Main branch deployment | Live user-facing site |
| Tier | Max Duration | Memory | Use Case |
|---|---|---|---|
| Hobby | 60 seconds | 1024 MB | Personal projects |
| Pro | 300 seconds (with Fluid Compute: 800s) | 3008 MB | Professional teams |
| Enterprise | 900 seconds | 3008 MB | Large organizations |
Before deploying to Vercel, gather the following information:
## Project Requirements
### Framework & Runtime
- [ ] Framework: (Next.js, React, Vue, etc.)
- [ ] Node.js version requirement: (20.x recommended)
- [ ] Package manager: (pnpm, npm, yarn)
### Deployment Needs
- [ ] Custom domain required?
- [ ] Preview deployments needed?
- [ ] Multiple environments (staging, production)?
### Function Requirements
- [ ] API routes needed?
- [ ] Expected function duration: (10s, 30s, 60s+)
- [ ] Edge Functions needed for low latency?
### Performance & Monitoring
- [ ] Analytics required?
- [ ] Speed Insights needed?
- [ ] Custom monitoring integration?
### Security & Access
- [ ] Authentication on preview deployments?
- [ ] IP allowlisting needed?
- [ ] Environment variables/secrets count?
# Global installation with pnpm
pnpm add -g vercel
# Verify installation
vercel --version
# Expected output: Vercel CLI 50.1.3
vercel login
# Opens browser for authentication
# Set token as environment variable
export VERCEL_TOKEN=your_token_here
# Or use with command
vercel --token $VERCEL_TOKEN
# Link to existing Vercel project
vercel link
# Link with specific scope (team)
vercel link --scope your-team
Key CLI commands for project management:
# List all projects
vercel ls
# Pull environment variables
vercel env pull
# Add environment variable
vercel env add MY_VAR
# List environments
vercel env ls
# Open project in dashboard
vercel open
This skill includes a complete starter template at templates/starter-app/.
# Copy the template
cp -r templates/starter-app/ my-new-project/
cd my-new-project
# Install dependencies
pnpm install
# Start development
pnpm dev
my-vercel-app/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── api/ # API routes (serverless functions)
│ │ │ ├── health/route.ts
│ │ │ └── data/route.ts
│ │ ├── layout.tsx # Root layout with Analytics
│ │ ├── page.tsx # Home page
│ │ └── globals.css # Global styles
│ ├── components/ # React components
│ │ ├── Header.tsx
│ │ ├── Footer.tsx
│ │ └── FeatureCard.tsx
│ ├── lib/ # Utilities
│ │ └── utils.ts
│ ├── middleware.ts # Edge middleware
│ └── test/ # Test setup
├── public/ # Static assets
├── vercel.json # Vercel configuration
├── next.config.ts # Next.js configuration
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
├── vitest.config.ts # Test configuration
└── .env.example # Environment template
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"framework": "nextjs",
"regions": ["iad1"],
"functions": {
"src/app/api/**/*.ts": {
"maxDuration": 30,
"memory": 1024
}
},
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{ "key": "Access-Control-Allow-Methods", "value": "GET, POST, PUT, DELETE, OPTIONS" }
]
}
],
"cleanUrls": true,
"trailingSlash": false
}
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
reactStrictMode: true,
images: {
remotePatterns: [
{ protocol: 'https', hostname: '**.vercel.app' },
],
formats: ['image/avif', 'image/webp'],
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
],
},
];
},
};
export default nextConfig;
// src/app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
export const runtime = 'nodejs';
export const maxDuration = 30;
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
// Your logic here
return NextResponse.json({ message: 'Success', id });
}
export async function POST(request: NextRequest) {
const body = await request.json();
// Validate and process
return NextResponse.json({ created: true }, { status: 201 });
}
// src/app/api/geo/route.ts
import { NextRequest, NextResponse } from 'next/server';
export const runtime = 'edge';
export async function GET(request: NextRequest) {
const country = request.geo?.country || 'Unknown';
const city = request.geo?.city || 'Unknown';
const region = request.geo?.region || 'Unknown';
return NextResponse.json({
country,
city,
region,
timestamp: new Date().toISOString(),
});
}
// src/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Add security headers
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
// Add request tracing
response.headers.set('X-Request-ID', crypto.randomUUID());
// Geolocation-based logic
const country = request.geo?.country || 'US';
response.headers.set('X-Geo-Country', country);
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
// 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 lang="en">
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
);
}
# Add to specific environment
vercel env add DATABASE_URL production
# Add to all environments
vercel env add API_KEY
# Pull to local .env
vercel env pull
// Server-side (API routes, Server Components)
const dbUrl = process.env.DATABASE_URL;
// Client-side (must prefix with NEXT_PUBLIC_)
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
Sensitive variables are write-only after creation:
# Mark as sensitive when adding
vercel env add API_SECRET --sensitive
Fluid Compute provides extended durations and reduced cold starts:
// Enable Fluid Compute benefits
export const runtime = 'nodejs';
export const maxDuration = 300; // Up to 300s on Pro, 800s with Fluid Compute
// Use waitUntil for background tasks
import { waitUntil } from '@vercel/functions';
export async function POST(request: NextRequest) {
const data = await request.json();
// Send response immediately
const response = NextResponse.json({ received: true });
// Continue processing in background
waitUntil(async () => {
await processDataAsync(data);
await sendNotification(data);
});
return response;
}
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts'],
include: ['src/**/*.{test,spec}.{js,ts,jsx,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
// src/test/setup.ts
import '@testing-library/jest-dom/vitest';
import { cleanup } from '@testing-library/react';
import { afterEach, vi } from 'vitest';
afterEach(() => {
cleanup();
});
// Mock Next.js router
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: vi.fn(),
replace: vi.fn(),
}),
usePathname: () => '/',
}));
// Mock Vercel packages
vi.mock('@vercel/analytics/react', () => ({
Analytics: () => null,
}));
vi.mock('@vercel/speed-insights/next', () => ({
SpeedInsights: () => null,
}));
// src/components/Button.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('calls onClick when clicked', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledOnce();
});
});
# Run tests
pnpm test
# Run with coverage
pnpm test:coverage
# Run in watch mode
pnpm test -- --watch
Connect your repository for automatic deployments:
# Preview deployment (default)
vercel
# Production deployment
vercel --prod
# Deploy with build output
vercel build && vercel deploy --prebuilt
# Force no cache
vercel --force
# Add domain via CLI
vercel domains add yourdomain.com
# List domains
vercel domains ls
# Inspect domain
vercel domains inspect yourdomain.com
For Apex Domains (example.com):
76.76.21.21For Subdomains (www.example.com):
cname.vercel-dns.comUsing Vercel Nameservers:
ns1.vercel-dns.com
ns2.vercel-dns.com
// vercel.json
{
"git": {
"deploymentEnabled": {
"main": true,
"develop": true,
"staging": true
}
}
}
// vercel.json at root
{
"buildCommand": "cd apps/web && pnpm build",
"outputDirectory": "apps/web/.next",
"installCommand": "pnpm install",
"framework": "nextjs"
}
For Turborepo projects:
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
}
}
}
Location: templates/starter-app/
Features:
Build Command: pnpm build
Run Command: pnpm dev
Output: .next/
# Create new project
mkdir my-template && cd my-template
# Initialize
pnpm init
pnpm add next react react-dom
pnpm add -D typescript @types/react @types/node
# Add Vercel packages
pnpm add @vercel/analytics @vercel/speed-insights
# Create vercel.json
echo '{"framework":"nextjs"}' > vercel.json
A simple API that returns geolocation data at the edge.
// src/app/api/location/route.ts
import { NextRequest, NextResponse } from 'next/server';
export const runtime = 'edge';
interface LocationResponse {
ip: string;
country: string;
city: string;
region: string;
latitude: number | null;
longitude: number | null;
timezone: string;
}
export async function GET(request: NextRequest) {
const location: LocationResponse = {
ip: request.headers.get('x-forwarded-for') || 'unknown',
country: request.geo?.country || 'Unknown',
city: request.geo?.city || 'Unknown',
region: request.geo?.region || 'Unknown',
latitude: request.geo?.latitude ? parseFloat(request.geo.latitude) : null,
longitude: request.geo?.longitude ? parseFloat(request.geo.longitude) : null,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
};
return NextResponse.json(location, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
},
});
}
// src/app/api/protected/route.ts
import { NextRequest, NextResponse } from 'next/server';
export const runtime = 'nodejs';
async function validateToken(token: string): Promise<boolean> {
// Implement your token validation
return token === process.env.API_SECRET;
}
export async function GET(request: NextRequest) {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json(
{ error: 'Missing authorization header' },
{ status: 401 }
);
}
const token = authHeader.slice(7);
const isValid = await validateToken(token);
if (!isValid) {
return NextResponse.json(
{ error: 'Invalid token' },
{ status: 403 }
);
}
return NextResponse.json({
message: 'Authenticated successfully',
data: { secret: 'value' },
});
}
// src/app/api/cron/cleanup/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { waitUntil } from '@vercel/functions';
export const runtime = 'nodejs';
export const maxDuration = 300;
async function performCleanup() {
// Simulate cleanup task
console.log('Starting cleanup...');
await new Promise(resolve => setTimeout(resolve, 5000));
console.log('Cleanup completed');
}
export async function GET(request: NextRequest) {
// Verify cron secret
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Start background task
waitUntil(performCleanup());
return NextResponse.json({
status: 'Cleanup initiated',
timestamp: new Date().toISOString(),
});
}
Configure in vercel.json:
{
"crons": [
{
"path": "/api/cron/cleanup",
"schedule": "0 0 * * *"
}
]
}
// src/app/api/og/route.tsx
import { ImageResponse } from 'next/og';
import { NextRequest } from 'next/server';
export const runtime = 'edge';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Default Title';
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#000',
color: '#fff',
fontSize: 60,
fontWeight: 'bold',
}}
>
{title}
</div>
),
{
width: 1200,
height: 630,
}
);
}
// src/app/api/chat/route.ts
import { NextRequest } from 'next/server';
export const runtime = 'edge';
export const maxDuration = 30;
export async function POST(request: NextRequest) {
const { message } = await request.json();
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// Simulate streaming response
const words = `Response to: ${message}`.split(' ');
for (const word of words) {
controller.enqueue(encoder.encode(word + ' '));
await new Promise(r => setTimeout(r, 100));
}
controller.close();
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Transfer-Encoding': 'chunked',
},
});
}
// src/app/api/limited/route.ts
import { NextRequest, NextResponse } from 'next/server';
export const runtime = 'edge';
const RATE_LIMIT = 100; // requests per minute
const WINDOW_MS = 60000; // 1 minute
// Simple in-memory store (use Redis/Edge Config in production)
const requestCounts = new Map<string, { count: number; timestamp: number }>();
function isRateLimited(ip: string): boolean {
const now = Date.now();
const record = requestCounts.get(ip);
if (!record || now - record.timestamp > WINDOW_MS) {
requestCounts.set(ip, { count: 1, timestamp: now });
return false;
}
if (record.count >= RATE_LIMIT) {
return true;
}
record.count++;
return false;
}
export async function GET(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for') || 'unknown';
if (isRateLimited(ip)) {
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{
status: 429,
headers: {
'Retry-After': '60',
},
}
);
}
return NextResponse.json({ message: 'Success' });
}
// src/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const EXPERIMENT_COOKIE = 'ab-experiment';
const VARIANTS = ['control', 'variant-a', 'variant-b'];
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Check for existing experiment cookie
let variant = request.cookies.get(EXPERIMENT_COOKIE)?.value;
if (!variant || !VARIANTS.includes(variant)) {
// Assign random variant
variant = VARIANTS[Math.floor(Math.random() * VARIANTS.length)];
response.cookies.set(EXPERIMENT_COOKIE, variant, {
maxAge: 60 * 60 * 24 * 30, // 30 days
httpOnly: true,
});
}
// Add variant header for analytics
response.headers.set('X-Experiment-Variant', variant);
return response;
}
export const config = {
matcher: ['/'],
};
// vercel.json
{
"regions": ["iad1", "sfo1", "cdg1", "hnd1"],
"functions": {
"src/app/api/global/**/*.ts": {
"runtime": "edge"
},
"src/app/api/us/**/*.ts": {
"regions": ["iad1", "sfo1"]
},
"src/app/api/eu/**/*.ts": {
"regions": ["cdg1", "lhr1"]
}
}
}
| Property | Type | Description |
|---|---|---|
framework | string | Framework preset (nextjs, react, vue, etc.) |
regions | string[] | Deployment regions |
functions | object | Function-specific configuration |
headers | array | Custom response headers |
redirects | array | URL redirects |
rewrites | array | URL rewrites |
cleanUrls | boolean | Remove .html extensions |
trailingSlash | boolean | Add/remove trailing slashes |
crons | array | Scheduled cron jobs |
git | object | Git deployment configuration |
interface FunctionConfig {
runtime?: 'nodejs' | 'edge';
maxDuration?: number; // Max execution time in seconds
memory?: 128 | 256 | 512 | 1024 | 2048 | 3008; // Memory in MB
regions?: string[]; // Specific regions
}
| Code | Location |
|---|---|
arn1 | Stockholm, Sweden |
bom1 | Mumbai, India |
cdg1 | Paris, France |
cle1 | Cleveland, USA |
cpt1 | Cape Town, South Africa |
dub1 | Dublin, Ireland |
fra1 | Frankfurt, Germany |
gru1 | São Paulo, Brazil |
hkg1 | Hong Kong |
hnd1 | Tokyo, Japan |
iad1 | Washington, D.C., USA |
icn1 | Seoul, South Korea |
kix1 | Osaka, Japan |
lhr1 | London, UK |
pdx1 | Portland, USA |
sfo1 | San Francisco, USA |
sin1 | Singapore |
syd1 | Sydney, Australia |
| Command | Description |
|---|---|
vercel | Deploy project |
vercel --prod | Deploy to production |
vercel dev | Run local development server |
vercel build | Build project locally |
vercel link | Link to Vercel project |
vercel env pull | Pull environment variables |
vercel env add | Add environment variable |
vercel domains add | Add custom domain |
vercel logs | View deployment logs |
vercel inspect | Inspect deployment |
vercel rollback | Rollback to previous deployment |
vercel open | Open project in dashboard |
// Use Edge Runtime for low-latency APIs
export const runtime = 'edge';
// Enable ISR for semi-static content
export const revalidate = 3600; // Revalidate every hour
// Use dynamic imports for code splitting
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <Loading />,
});
// next.config.ts
async headers() {
return [
{
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=()' },
],
},
];
}
# Use different values per environment
vercel env add DATABASE_URL production
vercel env add DATABASE_URL preview
vercel env add DATABASE_URL development
# Mark sensitive values
vercel env add API_SECRET production --sensitive
// Static caching
export async function GET() {
return Response.json(data, {
headers: {
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
},
});
}
// No cache for dynamic data
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export async function GET() {
try {
const data = await fetchData();
return Response.json(data);
} catch (error) {
console.error('API Error:', error);
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
// Use structured logging
console.log(JSON.stringify({
level: 'info',
message: 'Request processed',
requestId: request.headers.get('x-request-id'),
duration: Date.now() - startTime,
}));
// Use Fluid Compute
export const runtime = 'nodejs';
// Lazy load heavy dependencies
let heavyModule: typeof import('heavy-module') | null = null;
async function getHeavyModule() {
if (!heavyModule) {
heavyModule = await import('heavy-module');
}
return heavyModule;
}
maxDuration to avoid unexpected costswaitUntil for background tasksSymptoms: Build fails without clear error, mentions SIGKILL or out of memory.
Solutions:
# Clear build cache
vercel --force
# Or set environment variable
VERCEL_FORCE_NO_BUILD_CACHE=1
# Increase Node.js memory
NODE_OPTIONS="--max-old-space-size=6144" pnpm build
For Pro/Enterprise:
Symptoms: API returns 504 Gateway Timeout.
Solutions:
// Increase maxDuration
export const maxDuration = 60; // Up to 60s on Hobby
// Use Edge Runtime for simple operations
export const runtime = 'edge';
// Use streaming for long operations
return new Response(stream);
Symptoms: process.env.VAR returns undefined.
Solutions:
# Pull latest env vars
vercel env pull
# Verify variable exists
vercel env ls
# Redeploy after adding vars
vercel --prod
For client-side variables, prefix with NEXT_PUBLIC_:
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
Symptoms: Domain shows "Invalid Configuration" or verification pending.
Solutions:
# Check DNS records
dig +short yourdomain.com A
dig +short www.yourdomain.com CNAME
# Inspect domain in Vercel
vercel domains inspect yourdomain.com
Symptoms: Push to branch doesn't trigger deployment.
Solutions:
Check vercel.json:
{
"git": {
"deploymentEnabled": {
"main": true,
"develop": true,
"feature-*": true
}
}
}
Verify GitHub app permissions and webhook connectivity.
Symptoms: First request after idle period is slow.
Solutions:
// Enable Fluid Compute (Pro/Enterprise)
// - Reduces cold starts with bytecode caching
// - Enables function reuse
// Minimize bundle size
import { specific } from 'large-package'; // Not: import * as pkg
// Use dynamic imports
const Module = await import('heavy-module');
Symptoms: Browser blocks API requests with CORS error.
Solutions:
// Option 1: In API route
export async function GET() {
return Response.json(data, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
// Option 2: Handle OPTIONS preflight
export async function OPTIONS() {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
Or in vercel.json:
{
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" }
]
}
]
}
| Date | Update |
|---|---|
| Dec 2025 | vercel.ts TypeScript configuration support |
| Dec 2025 | Vercel Agent auto-installation for Analytics/Speed Insights |
| Nov 2025 | vercel open command to open dashboard |
| Oct 2025 | CLI v50 with enhanced environment variable handling |
| Sep 2025 | Speed Insights Intake API deprecated (use package) |
| Apr 2025 | Fluid Compute enabled by default for new projects |
| Mar 2025 | Edge Functions 300-second duration limit |
| Feb 2025 | Domain Connect for automated DNS configuration |
| Next.js Version | Vercel Support |
|---|---|
| 15.x | Full support with App Router |
| 14.x | Full support |
| 13.x | Full support (legacy) |
| 12.x | Maintenance mode |
User says: "Deploy my Next.js app to Vercel"
User says: "Configure Edge Functions for my API"
User says: "Set up custom domain on Vercel"
User says: "My Vercel build is failing"
User says: "Optimize my Vercel deployment"
User says: "Set up Vercel Analytics"
User says: "Configure environment variables"
User says: "Set up a monorepo on Vercel"
Last Updated: December 2025 Vercel CLI Version: 50.1.3 Next.js Version: 15.1.0