Identify and avoid Vercel anti-patterns and common integration mistakes.
Use when reviewing Vercel code for issues, onboarding new developers,
or auditing existing Vercel deployments for best practice violations.
Trigger with phrases like "vercel mistakes", "vercel anti-patterns",
"vercel pitfalls", "vercel what not to do", "vercel code review".
Instrucciones de origen · Vista previa de solo lectura
name
vercel-known-pitfalls
description
Identify and avoid Vercel anti-patterns and common integration mistakes.
Use when reviewing Vercel code for issues, onboarding new developers,
or auditing existing Vercel deployments for best practice violations.
Trigger with phrases like "vercel mistakes", "vercel anti-patterns",
"vercel pitfalls", "vercel what not to do", "vercel code review".
Designed for Claude Code, also compatible with Codex and OpenClaw
Vercel Known Pitfalls
Overview
Catalog of the most common Vercel anti-patterns with severity ratings, detection methods, and fixes. Organized by category: secret exposure, serverless function mistakes, edge runtime violations, configuration errors, and cost traps.
Prerequisites
Access to Vercel codebase for review
Understanding of Vercel's deployment model
Familiarity with vercel-common-errors for error codes
Instructions
Category 1: Secret Exposure (Critical)
P1: Secrets in NEXT_PUBLIC_ variables
// BAD — exposed in client JavaScript bundle, visible to anyoneconst apiKey = process.env.NEXT_PUBLIC_API_SECRET;
// This value is inlined at build time into the browser bundle// GOOD — server-only accessconst apiKey = process.env.API_SECRET;
// Only accessible in serverless functions and server components
Fix: Move to environment variables, add pre-commit hook
P3: Secrets in vercel.json
// BAD — vercel.json is committed to git{"env":{"API_KEY":"sk_live_abc123"}}// GOOD — use Vercel dashboard or CLI// vercel env add API_KEY production
Category 2: Serverless Function Mistakes (High)
P4: Heavy initialization at module level
// BAD — runs on every cold start, adds 500ms+import { PrismaClient } from'@prisma/client';
const prisma = newPrismaClient(); // Connects on importconst cache = awaitloadLargeDataset(); // Blocks cold start// GOOD — lazy initializationletprisma: PrismaClient | null = null;
functiongetDb() {
if (!prisma) prisma = newPrismaClient();
return prisma;
}
exportdefaultasyncfunctionhandler(req, res) {
const db = getDb(); // Only connects on first request// ...
}
P5: Not returning responses from all code paths
// BAD — some paths don't return, causing NO_RESPONSE_FROM_FUNCTIONexportdefaultfunctionhandler(req, res) {
if (req.method === 'GET') {
res.json({ data: 'ok' });
}
// POST, PUT, DELETE — no response returned!
}
// GOODexportdefaultfunctionhandler(req, res) {
if (req.method === 'GET') {
return res.json({ data: 'ok' });
}
return res.status(405).json({ error: 'Method not allowed' });
}
P6: Ignoring function timeout limits
// BAD — no timeout awareness, function silently killedexportdefaultasyncfunctionhandler(req, res) {
const results = awaitprocessMillionRecords(); // Takes 5 minutes
res.json(results);
}
// GOOD — chunk work, respect timeoutexportdefaultasyncfunctionhandler(req, res) {
const batch = req.query.batch ?? 0;
const results = awaitprocessBatch(batch, 100); // Process 100 at a time
res.json({
results,
nextBatch: batch + 1,
done: results.length < 100,
});
}
P7: Connection pool exhaustion
// BAD — each function instance creates its own connection pool// With 100 concurrent functions × 10 pool connections = 1000 DB connectionsconst pool = newPool({ max: 10 });
// GOOD — use a connection pooler// Use Prisma Accelerate, PgBouncer, or Supabase connection pooler// Configure pool size to 1-2 per function instanceconst pool = newPool({ max: 2 });
Category 3: Edge Runtime Violations (High)
P8: Node.js APIs in edge functions
// BAD — these crash silently in Edge Runtimeexportconst config = { runtime: 'edge' };
import fs from'fs'; // Not availableimport path from'path'; // Not availableimport crypto from'crypto'; // Use crypto.subtle insteadimport { Buffer } from'buffer'; // Use Uint8Array instead// GOOD — Web Standard APIsconst hash = await crypto.subtle.digest('SHA-256', data);
const encoded = btoa(String.fromCharCode(...newUint8Array(hash)));
// BAD — throws "Dynamic Code Evaluation not allowed"exportconst config = { runtime: 'edge' };
const fn = newFunction('return 42'); // Not allowedeval('console.log("hi")'); // Not allowed// GOOD — use static code onlyconstfn = () => 42;
Category 4: Configuration Errors (Medium)
P10: Missing environment variable scoping
# BAD — variable only in Production, preview deployments break
vercel env add DATABASE_URL production
# GOOD — add to all environments that need it
vercel env add DATABASE_URL production preview development
P11: Using deprecated builds property
// BAD (deprecated){"builds":[{"src":"api/**/*.ts","use":"@vercel/node"}]}// GOOD (current){"functions":{"api/**/*.ts":{"runtime":"nodejs20.x","maxDuration":30}}}
P12: Middleware running on static assets
// BAD — middleware runs on every request including static filesexportfunctionmiddleware(request) { /* auth check */ }
// GOOD — exclude static assetsexportconst config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
Category 5: Cost Traps (Medium)
P13: Uncached high-traffic endpoints
// BAD — every request invokes a functionexportdefaultfunctionhandler(req, res) {
res.json({ config: getConfig() }); // No cache headers
}
// GOOD — cache at the edge, save function invocationsexportdefaultfunctionhandler(req, res) {
res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400');
res.json({ config: getConfig() });
}
P14: Over-allocated function memory
// BAD — 3GB for a simple JSON response{"functions":{"api/config.ts":{"memory":3008}}}// GOOD — right-size per endpoint{"functions":{"api/config.ts":{"memory":128},"api/image-process.ts":{"memory":1024}}}
P15: Middleware doing heavy work
// BAD — database query on every requestexportasyncfunctionmiddleware(request) {
const user = await db.user.findUnique({ where: { id: token.sub } });
// Runs on EVERY matched request, expensive at scale
}
// GOOD — validate JWT locally, no DB callexportfunctionmiddleware(request) {
const token = request.cookies.get('session')?.value;
// Verify JWT signature locally (cheap, no external call)
}