Problem Summary
The canonical 'works on my machine' problem has a Vercel-specific form: the app builds successfully in your local environment and often in your CI pipeline, but either the Vercel build step fails or the deployed functions crash at runtime. Vercel's build environment has meaningful differences from a local Node.js environment tighter TypeScript enforcement, no access to local filesystem, Edge Runtime's module restrictions, and environment variables that must be explicitly set per-environment in the Vercel dashboard.
Symptoms
- Vercel build log shows TypeScript errors that do not appear when running tsc locally
- Deployed API routes return 500 errors but work perfectly when running next dev
- Build fails with 'Module not found' for a package that is in node_modules locally
- Edge Runtime functions throw 'Module X is not available in the Edge Runtime'
- Environment variables accessed via process.env return undefined in production
- Serverless function exceeds the 50 MB compressed size limit and the build fails
- The app uses a Node.js version feature that Vercel's build environment does not support
- Dynamic imports or fs.readFileSync calls fail in serverless functions
Business Impact
A broken Vercel deployment means every push to your production branch triggers a failed deploy. Depending on your rollback strategy, this either leaves the previous deployment serving traffic (safe but stale) or results in a production outage if no previous deployment exists. For teams using Vercel preview deployments as part of the PR review process, build failures block code review and slow the entire engineering team.
Root Cause
The most common root causes in order of frequency: environment variables that exist in your local .env file but are not configured in the Vercel dashboard, code that runs in Node.js locally but is deployed to the Edge Runtime where Node.js built-ins (crypto, fs, net, http, Buffer, process.env not fully available) do not exist, TypeScript errors that were suppressed locally (ts-ignore, skipLibCheck: true in tsconfig) but caught by Vercel's strict build, and dependencies bundled into the function that push it over the 50 MB compressed size limit.
Vercel's Edge Runtime is NOT Node.js. It is a V8-based runtime similar to a Service Worker. Node.js built-in modules (fs, path, crypto, net, child_process, http, https) are not available. If your API route imports any library that uses these modules, it will fail in Edge Runtime even if it works in Node.js Runtime.
Diagnosis
Step 1 Check Environment Variables in Vercel Dashboard
Open Vercel dashboard → your project → Settings → Environment Variables. Verify that every variable referenced in your code exists for the Production environment. Variables set only in .env.local are never sent to Vercel.
vercel env ls// Add this to your app startup to catch missing env vars early
const requiredEnvVars = [
"DATABASE_URL",
"NEXTAUTH_SECRET",
"NEXTAUTH_URL",
"STRIPE_SECRET_KEY",
"RESEND_API_KEY",
] as const;
for (const varName of requiredEnvVars) {
if (!process.env[varName]) {
throw new Error(
"Missing required environment variable: " + varName +
". Add it to Vercel Dashboard → Settings → Environment Variables."
);
}
}In Next.js, environment variables are only exposed to the browser if prefixed with NEXT_PUBLIC_. Server-side variables (without the prefix) are available in Server Components, API routes, and Server Actions but NOT in Client Components.
Step 2 Identify Edge Runtime vs Node.js Runtime Conflicts
If a route is deployed to the Edge Runtime (either explicitly via export const runtime = 'edge' or because Vercel inferred it), any import of a Node.js-only module causes a build or runtime error.
// If you need Node.js modules, explicitly declare Node.js runtime
export const runtime = "nodejs"; // <-- add this line
// Now you can use Node.js built-ins
import { createHash } from "crypto";
import fs from "fs";
export async function GET() {
const hash = createHash("sha256").update("test").digest("hex");
return Response.json({ hash });
}#!/bin/bash
# Find packages in your dependencies that use Node.js built-ins
# These will fail in Edge Runtime
npx @vercel/nft --edge . 2>&1 | grep -E '(not available|require()'
# Or check which routes are deploying to edge
grep -r "runtime = .edge." ./app ./pages --include="*.ts" --include="*.tsx" -lStep 3 Fix TypeScript Build Errors
Vercel runs a production TypeScript build which is stricter than your local dev server. Even if next dev runs fine, next build will catch type errors. Reproduce Vercel's build locally before pushing.
npm run build 2>&1 | grep -E '(error TS|Type error|Failed to compile)'npx tsc --noEmit 2>&1 | head -50If you use ignoreBuildErrors: true in next.config.js to skip TypeScript errors during build, remove it. This hides real bugs and causes subtle runtime failures. Fix the TypeScript errors instead they are usually pointing at real problems.
Step 4 Check Function Bundle Size
Vercel serverless functions have a 50 MB compressed bundle size limit. If you import large libraries (PDF parsers, image processing, ML libraries) in API routes, you can easily exceed this. Check the bundle size after build.
npm run build && ls -lh .next/server/app/api/**/*.js 2>/dev/null || ls -lh .next/serverless/ 2>/dev/nullimport type { NextConfig } from "next";
const nextConfig: NextConfig = {
// Enable bundle analyzer to visualize what is large
// npm install @next/bundle-analyzer
experimental: {
// Move large dependencies to serverless function layers
// or use dynamic imports to lazy-load them
},
};
// To analyze bundle: ANALYZE=true npm run build
// npm install @next/bundle-analyzer
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
export default withBundleAnalyzer(nextConfig);Step 5 Match Node.js Version Between Local and Vercel
Vercel uses Node.js 20.x by default for new projects. If your code uses features available in Node.js 22 locally but Vercel builds with Node.js 20, you will see unexpected errors. Set the Node.js version explicitly.
node --version && cat .nvmrc 2>/dev/null || echo 'No .nvmrc found'{
"name": "my-app",
"engines": {
"node": ">=20.0.0"
}
}In Vercel dashboard: Settings → General → Node.js Version. Set this to match your local version and your package.json engines field.
Production Failure Scenarios
Scenario 1 Middleware Using Node.js crypto Module
Next.js middleware always runs in the Edge Runtime. If your middleware imports a JWT library that internally uses Node.js crypto, it will fail in production. Use the Web Crypto API (crypto.subtle) instead, or switch to the jose library which is Edge-compatible.
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { jwtVerify } from "jose"; // Edge-compatible, not 'jsonwebtoken'
export async function middleware(request: NextRequest) {
const token = request.cookies.get("token")?.value;
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
try {
// jose uses Web Crypto API internally works in Edge Runtime
await jwtVerify(
token,
new TextEncoder().encode(process.env.JWT_SECRET)
);
return NextResponse.next();
} catch {
return NextResponse.redirect(new URL("/login", request.url));
}
}Scenario 2 Server Action Missing 'use server' in Nested Import
A Server Action file that re-exports functions from another file without 'use server' at the top of each file causes the build to include client-side code in the server bundle or vice versa. This produces a cryptic error about calling server functions from client components.
Scenario 3 PDF or Sharp Library Exceeds Function Size Limit
The sharp image processing library includes native binaries for multiple platforms. When bundled into a Vercel function it easily exceeds 50 MB. Solution: move image processing to a separate microservice, use Cloudinary/Imgix, or use the @vercel/og API which includes Sharp built-in.
Prevention Checklist
- Run npm run build locally and in CI before every Vercel deployment catch TypeScript and build errors early
- Add env var validation at app startup that throws with a clear message for each missing variable
- Use vercel env pull .env.local to sync production environment variables to your local environment
- Explicitly declare export const runtime = 'nodejs' on any API route that uses Node.js built-ins
- Install @next/bundle-analyzer and run it monthly to catch growing bundle sizes before they hit the limit
- Pin Node.js version in .nvmrc, package.json engines, and Vercel dashboard settings all three must match
- Avoid dynamic requires and __dirname in API routes these do not work in serverless function environments
- Test your production build with vercel dev locally (simulates Vercel's edge and serverless environment) before deploying
- Never use ignoreBuildErrors: true or ignoreDuringBuilds: true in next.config fix the errors instead
Resolution Summary
Check environment variables first they account for over half of Vercel production failures. Then identify whether the failing code is in an Edge Runtime context, which restricts Node.js built-ins. Run npm run build locally to reproduce TypeScript errors. Check function bundle size if the error is about size limits. Verify Node.js version alignment between local and Vercel settings.
Lessons Learned
- next dev and next build are different environments always run a production build locally before pushing to Vercel
- Edge Runtime is not Node.js this is a hard boundary, not a compatibility issue you can workaround with polyfills
- Environment variable management is a deployment problem that requires process discipline, not just code discipline
- Function bundle size grows silently monitor it before it becomes a production blocker
- vercel dev is the closest local approximation of the Vercel production environment use it for final verification
Conclusion
Vercel deployment failures almost always fall into one of five categories: missing environment variables, Edge Runtime module restrictions, TypeScript build errors, function size limits, and Node.js version mismatches. Each has a clear diagnosis path and a straightforward fix. The key discipline is running a production build locally not just the dev server before every deploy. That single habit catches 80% of Vercel deployment issues before they reach production.