Vercel deploy
Deploy to Vercel with edge functions, ISR, middleware, and proper caching. Use when deploying Next.js apps, configuring preview environments, or optimizing Vercel builds.From its SKILL.md
npx -y skills add medy-gribkov/arcana --skill vercel-deployAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.7 KB, ~2.7k tokens by cl100k_base, as published. Nobody here has run it
Vercel Deployment Skill
Core Concept
Deploy applications to Vercel with proper runtime selection, caching strategies, and environment configuration. Edge Functions run at CDN edge nodes for low latency (under 50ms cold start), Serverless Functions run in regional data centers (up to 10s execution).
vercel.json Configuration
BAD: Kitchen sink configuration
{
"functions": {
"api/**/*.js": {
"runtime": "nodejs18.x",
"maxDuration": 10
}
},
"headers": [],
"rewrites": []
}
GOOD: Targeted runtime and caching
{
"functions": {
"api/slow-process.ts": {
"runtime": "nodejs20.x",
"maxDuration": 60,
"memory": 1024
}
},
"headers": [
{
"source": "/api/static-data",
"headers": [
{
"key": "Cache-Control",
"value": "s-maxage=3600, stale-while-revalidate=86400"
}
]
}
],
"crons": [
{
"path": "/api/cron/cleanup",
"schedule": "0 0 * * *"
}
]
}
Edge vs Serverless Selection
BAD: Everything as serverless
// app/api/geo/route.ts
export async function GET(request: Request) {
// Runs in us-east-1 every time, slow for global users
const country = request.headers.get('x-forwarded-for')
return Response.json({ country })
}
GOOD: Edge for geo/auth, serverless for heavy compute
// app/api/geo/route.ts
export const runtime = 'edge'
export async function GET(request: Request) {
const country = request.headers.get('x-vercel-ip-country')
const city = request.headers.get('x-vercel-ip-city')
return Response.json({ country, city })
}
// app/api/process-video/route.ts
export const runtime = 'nodejs'
export const maxDuration = 300 // 5 minutes, requires Pro plan
export async function POST(request: Request) {
const formData = await request.formData()
// Heavy video processing requires serverless
const result = await processVideo(formData)
return Response.json(result)
}
Middleware for Auth and Geo-Routing
BAD: Auth in every route
// app/api/protected/route.ts
import { verify } from 'jsonwebtoken'
export async function GET(request: Request) {
const token = request.headers.get('authorization')
try {
verify(token, process.env.JWT_SECRET)
} catch {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
// Protected logic
}
GOOD: Middleware at edge for auth and geo
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export const config = {
matcher: ['/api/protected/:path*', '/dashboard/:path*']
}
export function middleware(request: NextRequest) {
// Runs at edge, low latency globally
const token = request.cookies.get('session')?.value
const country = request.geo?.country
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Geo-block sanctioned countries
if (['CU', 'IR', 'KP', 'SY'].includes(country || '')) {
return NextResponse.json(
{ error: 'Service unavailable in your region' },
{ status: 451 }
)
}
// Add custom headers for downstream routes
const response = NextResponse.next()
response.headers.set('x-user-country', country || 'unknown')
return response
}
Environment Variables Strategy
BAD: Hardcoded secrets, no environment separation
// lib/config.ts
export const config = {
apiKey: 'sk_live_abc123', // NEVER hardcode
dbUrl: process.env.DATABASE_URL // Same DB for preview and production
}
GOOD: Environment-specific variables
// lib/config.ts
export const config = {
apiKey: process.env.API_KEY!, // Set in Vercel dashboard
dbUrl: process.env.DATABASE_URL!,
isProduction: process.env.VERCEL_ENV === 'production',
isPreview: process.env.VERCEL_ENV === 'preview',
deploymentUrl: process.env.VERCEL_URL
}
// Vercel dashboard environment settings:
// API_KEY (production): sk_live_abc123
// API_KEY (preview): sk_test_xyz789
// DATABASE_URL (production): postgres://prod.db
// DATABASE_URL (preview): postgres://staging.db
Vercel automatically provides: VERCEL, VERCEL_ENV, VERCEL_URL, VERCEL_GIT_COMMIT_SHA, VERCEL_GIT_COMMIT_REF.
Preview Deployments Workflow
Every git push to non-production branch creates preview deployment:
# Automatic preview on PR
git checkout -b feature/new-ui
git push origin feature/new-ui
# Vercel creates: https://myapp-git-feature-new-ui-user.vercel.app
# Preview-specific env vars for testing
# Set in Vercel dashboard: ENABLE_FEATURE_FLAG=true (preview only)
Workflow for preview comments:
// app/api/comments/route.ts
export async function POST(request: Request) {
const isPreview = process.env.VERCEL_ENV === 'preview'
if (isPreview) {
// Use test Stripe key, mock email service
return Response.json({
success: true,
note: 'Preview mode, no real charges'
})
}
// Production logic with real services
const result = await stripe.charges.create({...})
return Response.json(result)
}
Caching Headers and ISR
BAD: No caching, every request hits origin
// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
const post = await fetchPost(params.slug)
return <article>{post.content}</article>
}
GOOD: ISR with on-demand revalidation
// app/blog/[slug]/page.tsx
export const revalidate = 3600 // ISR: regenerate after 1 hour
export async function generateStaticParams() {
const posts = await fetchAllPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export default async function BlogPost({ params }) {
const post = await fetchPost(params.slug)
return <article>{post.content}</article>
}
// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache'
export async function POST(request: Request) {
const { slug, secret } = await request.json()
if (secret !== process.env.REVALIDATION_SECRET) {
return Response.json({ error: 'Invalid secret' }, { status: 401 })
}
revalidatePath(`/blog/${slug}`)
return Response.json({ revalidated: true })
}
GOOD: API route caching with stale-while-revalidate
// app/api/stats/route.ts
export async function GET() {
const stats = await fetchStats()
return Response.json(stats, {
headers: {
'Cache-Control': 's-maxage=60, stale-while-revalidate=300'
// Cache for 60s, serve stale for 5min while revalidating
}
})
}
Image Optimization
BAD: Unoptimized images
<img src="/hero.png" alt="Hero" /> {/* Serves original 5MB PNG */}
GOOD: next/image with formats and sizes
import Image from 'next/image'
// next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200],
imageSizes: [16, 32, 48, 64, 96, 128, 256],
minimumCacheTTL: 31536000,
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.example.com',
pathname: '/images/**'
}
]
}
}
// Component
<Image
src="/hero.png"
alt="Hero"
width={1200}
height={600}
priority
sizes="(max-width: 768px) 100vw, 50vw"
/>
Monorepo Setup
GOOD: Turborepo with Vercel
// vercel.json (root)
{
"buildCommand": "turbo run build --filter=web",
"outputDirectory": "apps/web/.next",
"installCommand": "pnpm install"
}
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
}
}
}
// apps/web/package.json
{
"name": "web",
"scripts": {
"build": "next build"
}
}
Build Optimization
BAD: No output file tracing
// next.config.js
module.exports = {
// Default includes all node_modules in function bundles
}
GOOD: Output file tracing and standalone build
// next.config.js
module.exports = {
output: 'standalone', // Only includes necessary files
experimental: {
outputFileTracingRoot: require('path').join(__dirname, '../../')
},
webpack: (config, { isServer }) => {
if (isServer) {
// Externalize large dependencies
config.externals.push('canvas', 'bufferutil', 'utf-8-validate')
}
return config
}
}
Custom Domains and SSL
# Add custom domain (Vercel dashboard or CLI)
vercel domains add example.com
vercel domains add www.example.com
# DNS records (automatic with Vercel nameservers):
# A record: @ -> 76.76.21.21
# CNAME: www -> cname.vercel-dns.com
# SSL is automatic, renews via Let's Encrypt
Analytics and Speed Insights
GOOD: Integrate Vercel Analytics
// app/layout.tsx
import { Analytics } from '@vercel/analytics/react'
import { SpeedInsights } from '@vercel/speed-insights/next'
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
)
}
// package.json
{
"dependencies": {
"@vercel/analytics": "^1.1.1",
"@vercel/speed-insights": "^1.0.2"
}
}
Deployment Commands
# Install Vercel CLI
npm i -g vercel
# Initial setup (links project)
vercel
# Deploy to preview
vercel
# Deploy to production
vercel --prod
# Set environment variable
vercel env add API_KEY production
vercel env add API_KEY preview
# Pull environment variables locally
vercel env pull .env.local
# Check deployment logs
vercel logs https://myapp-abc123.vercel.app
# List deployments
vercel ls
# Promote preview to production
vercel promote https://myapp-git-feature.vercel.app
Common Deployment Checks
Before deploying:
- Run
next buildlocally to catch build errors - Check bundle size:
npx @next/bundle-analyzer - Verify environment variables are set in Vercel dashboard
- Test middleware logic doesn't block legitimate traffic
- Ensure ISR paths are properly configured
- Validate edge runtime compatibility (no Node.js APIs like
fs)
Edge runtime restrictions:
- No
fs,child_process,crypto(usecrypto.subtle) - Max 1MB per edge function after compression
- No native modules or binaries
Troubleshooting
Error: Function exceeds maximum size
Solution: Use output: 'standalone' and externalize large deps.
Error: Middleware blocking all traffic
Solution: Check matcher config, ensure auth logic has fallback.
Slow cold starts on serverless Solution: Switch to edge runtime or reduce dependencies.
Preview using production database
Solution: Set environment-specific DATABASE_URL for preview environment.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most containers cloud skills give in ~2.7k tokens
Counted across 607 of the 657 authors here whose files we hold, read 2026-08-07
- Run containers as a non-root userin 66 of 607, across 46 files
- Use multi-stage buildsin 53 of 607, across 44 files
- Use Promise.all for independent operationsin 47 of 607, across 13 files
- Import directly instead of barrel filesin 46 of 607, across 12 files
- Use ternary instead of AND for conditionalsin 45 of 607, across 12 files
- Use Set or Map for O(1) lookupsin 42 of 607, across 10 files
- Create a .dockerignore filein 41 of 607, across 31 files
- Read individual rule files for detailsin 39 of 607, across 9 files
- Copy dependency files before source codein 36 of 607, across 23 files
- Authenticate server actions like API routesin 35 of 607, across 7 files
- Use next/dynamic for heavy componentsin 34 of 607, across 9 files
- Use React.cache for per-request deduplicationin 34 of 607, across 10 files
Said here and by no other author read
- Use targeted runtimes and caching in configuration
- Use edge runtime for geo and auth
- Use serverless runtime for heavy compute
- Handle auth and geo-routing in middleware
- Enable ISR for dynamic pages
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.