Cuando estaba desplegando 32blog.com en Vercel, el build moría en el momento que hacía push — aunque npm run build pasaba localmente. Cada vez.
"Funciona localmente" no significa "funciona en Vercel." Variables de entorno, diferencias en la versión de Node.js, comportamiento del caché, límites de Functions — la brecha entre local y Vercel es más grande de lo que parece.
Este artículo cubre los 7 patrones de error más comunes al desplegar Next.js en Vercel, con soluciones concretas para cada uno. Usa Ctrl+F para buscar el mensaje de error que estás viendo.
El build pasa localmente pero falla en Vercel
El escenario más común. La causa raíz es casi siempre una de dos cosas: variables de entorno faltantes o una resolución de dependencias fallida.
Variables de entorno faltantes
Localmente, los valores en .env.local se cargan automáticamente. En Vercel, tienes que agregarlos explícitamente en el dashboard — no se recogen de otra manera.
Cuando una ruta de código en tiempo de build lee una variable faltante, obtienes errores como estos:
Error: Missing required environment variable: DATABASE_URL
Build failed with errors
O la variable silenciosamente se convierte en undefined y el build se cae más adelante:
TypeError: Cannot read properties of undefined (reading 'split')
Solución: Ve al dashboard de Vercel → Proyecto → Settings → Environment Variables y agrega todo lo de tu .env.local.
# Verifica tu .env.local
cat .env.local
# Cada variable aquí necesita ser agregada a Vercel
DATABASE_URL=postgresql://...
NEXT_PUBLIC_API_URL=https://api.example.com
AUTH_SECRET=...
Después de agregar variables de entorno, no solo hagas clic en "Redeploy" — usa "Redeploy without cache" o haz push de un nuevo commit para asegurar que el build recoja los nuevos valores.
Errores de tipos TypeScript
Al ejecutar npm run dev, Next.js usa Turbopack que omite la verificación completa de tipos. Vercel ejecuta next build, que incluye la compilación de TypeScript. Así que los errores de tipo que eran silenciosamente ignorados localmente romperán tu build de Vercel.
Type error: Property 'name' does not exist on type 'User | null'
Solución: Ejecuta la verificación de tipos localmente antes de hacer push.
# Ejecuta antes de cada push
npx tsc --noEmit
# Corrige los errores, luego haz push
Puedes deshabilitar la verificación de tipos en next.config.ts, pero esto degrada la calidad del código en producción y no se recomienda.
// next.config.ts (no recomendado: deshabilita la verificación de tipos)
const nextConfig = {
typescript: {
ignoreBuildErrors: true, // solo si es absolutamente necesario
},
};
Errores de ESLint
next build ejecuta ESLint como parte del build. Los errores que no aparecen durante npm run dev romperán el build de Vercel.
./app/components/Button.tsx
ESLint: 'onClick' is defined but never used. (no-unused-vars)
Failed to compile.
Solución: Ejecuta lint localmente antes de hacer push.
# Verifica lint antes de hacer push
npm run lint
# Auto-corrige lo que sea posible
npm run lint -- --fix
Puedes deshabilitar temporalmente el lint durante los builds en next.config.ts, pero trátalo como deuda técnica que hay que corregir inmediatamente.
// next.config.ts
const nextConfig = {
eslint: {
ignoreDuringBuilds: true, // solo temporal
},
};
Errores de Function Timeout
Las Serverless Functions de Vercel tienen un techo de timeout. El predeterminado es 10 segundos en el plan Hobby. Los planes Pro permiten hasta 300 segundos (5 minutos).
Error: Task timed out after 10.00 seconds
Encontrar la causa
Abre la pestaña Functions en tu dashboard de Vercel. Puedes ver los tiempos de ejecución de cada función. Todo lo que se acerque a 10 segundos necesita atención.
Causas comunes:
- Llamadas lentas a APIs externas
- Consultas de base de datos sin optimizar
- Procesamiento de archivos grandes en memoria
- Bucles infinitos o recursión no intencionada
Solución 1: Establecer maxDuration (solo plan Pro)
// app/api/heavy-task/route.ts
export const maxDuration = 60; // hasta 60 segundos en plan Pro
export async function GET() {
const result = await heavyDatabaseQuery();
return Response.json(result);
}
Solución 2: Mover trabajo pesado a una cola en segundo plano
No ejecutes operaciones lentas sincrónicamente en API routes. Delégalas a trabajos en segundo plano usando Vercel Cron Jobs o un servicio externo como Upstash QStash.
// app/api/trigger-job/route.ts
import { Client } from "@upstash/qstash";
const qstash = new Client({ token: process.env.QSTASH_TOKEN! });
export async function POST(request: Request) {
const body = await request.json();
// Encolar el trabajo pesado
await qstash.publishJSON({
url: `${process.env.VERCEL_URL}/api/process-job`,
body: body,
});
// Retornar inmediatamente
return Response.json({ status: "queued" });
}
Solución 3: Cambiar a Edge Runtime
Para operaciones ligeras, Edge Runtime inicia más rápido y raramente tiene problemas de timeout.
// app/api/fast-api/route.ts
export const runtime = "edge";
export async function GET() {
return new Response("Hello from Edge!");
}
Errores de Out of Memory (OOM)
Cuando una función se queda sin memoria durante el build o la ejecución, verás algo como esto:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
O en los logs de Vercel:
Error: Function exceeded memory limit of 1024 MB
Problemas de memoria en tiempo de build
Proyectos grandes o apps que generan estáticamente muchas páginas en tiempo de build pueden alcanzar los límites de memoria.
# Aumentar el tamaño del heap de Node.js para el build
NODE_OPTIONS="--max-old-space-size=4096" npm run build
Puedes establecer esto como variable de entorno en el dashboard de Vercel:
# Vercel dashboard > Environment Variables
NODE_OPTIONS = --max-old-space-size=4096
Problemas de memoria en tiempo de ejecución
Las API routes que cargan datasets grandes en memoria pueden causar errores OOM en tiempo de ejecución.
// Mal: carga 100MB de datos en memoria de una vez
export async function GET() {
const hugeData = await fetchAllRecords();
return Response.json(hugeData);
}
// Bien: transmitir la respuesta en su lugar
export async function GET() {
const stream = new ReadableStream({
async start(controller) {
const records = await fetchRecordsInBatches();
for await (const batch of records) {
controller.enqueue(
new TextEncoder().encode(JSON.stringify(batch) + "\n")
);
}
controller.close();
},
});
return new Response(stream, {
headers: { "Content-Type": "application/json" },
});
}
Errores 404 que solo aparecen en producción
Páginas que funcionan localmente pero devuelven 404 en producción usualmente son causadas por generación estática incompleta o mala configuración del middleware.
generateStaticParams incompleto
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
Cualquier slug no devuelto por generateStaticParams dará 404 a menos que dynamicParams sea true (el predeterminado).
// app/blog/[slug]/page.tsx
// dynamicParams tiene true por defecto — las rutas no generadas se renderizan bajo demanda
export const dynamicParams = true;
// Establecer false significa que solo las rutas pre-generadas funcionan; todo lo demás da 404
Mala configuración de rutas de next-intl
Si usas next-intl para i18n, las discrepancias en la configuración de locales pueden causar 404s en producción.
// middleware.ts
import createMiddleware from "next-intl/middleware";
export default createMiddleware({
locales: ["ja", "en"],
defaultLocale: "ja",
localePrefix: "as-needed", // el locale predeterminado no tiene prefijo
});
export const config = {
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
};
Caché atascado en estado obsoleto
El comportamiento de caché de Vercel puede producir resultados inesperados, especialmente al mezclar caché de fetch con ISR (Incremental Static Regeneration).
// Sin estrategia de caché explícita — intención poco clara
export async function getArticles() {
const res = await fetch("https://api.example.com/articles");
// Next.js 15+ usa no-store por defecto, pero ser explícito evita confusión
return res.json();
}
// Sé explícito sobre tu estrategia de caché
export async function getArticles() {
const res = await fetch("https://api.example.com/articles", {
next: { revalidate: 60 }, // revalidar cada 60 segundos
});
return res.json();
}
// O deshabilitar el caché completamente para datos en tiempo real
export async function getLatestPrice() {
const res = await fetch("https://api.example.com/price", {
cache: "no-store",
});
return res.json();
}
Revalidación bajo demanda
Para invalidar el caché inmediatamente cuando el contenido cambia, usa la API de Revalidación.
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from "next/cache";
import { NextRequest } from "next/server";
export async function POST(request: NextRequest) {
const token = request.nextUrl.searchParams.get("token");
if (token !== process.env.REVALIDATION_TOKEN) {
return Response.json({ error: "Invalid token" }, { status: 401 });
}
const body = await request.json();
if (body.path) {
revalidatePath(body.path);
}
if (body.tag) {
revalidateTag(body.tag);
}
return Response.json({ revalidated: true });
}
Errores de CORS que solo aparecen en producción
Cuando tu frontend llama a una API y solo obtiene errores de CORS en producción:
Access to fetch at 'https://api.example.com' from origin 'https://32blog.com'
has been blocked by CORS policy
Agrega headers de CORS a tus API routes.
// app/api/data/route.ts
export async function GET() {
const response = await fetchData();
return Response.json(response, {
headers: {
"Access-Control-Allow-Origin": "https://32blog.com",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}
// Manejar solicitudes preflight
export async function OPTIONS() {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "https://32blog.com",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}
O configura headers globalmente en next.config.ts.
// next.config.ts
const nextConfig = {
async headers() {
return [
{
source: "/api/:path*",
headers: [
{ key: "Access-Control-Allow-Origin", value: "https://32blog.com" },
{ key: "Access-Control-Allow-Methods", value: "GET,POST,OPTIONS" },
{ key: "Access-Control-Allow-Headers", value: "Content-Type" },
],
},
];
},
};
export default nextConfig;
Access-Control-Allow-Origin: * permite todos los orígenes. Eso está bien para APIs públicas, pero las APIs que requieren autenticación deberían restringir el acceso solo a orígenes específicos.
Conclusión
Aquí tienes una referencia rápida para los errores de despliegue en Vercel más comunes:
| Error | Causa | Solución |
|---|---|---|
| Fallo de build | Variables de entorno faltantes, errores de tipos, errores de ESLint | Agrega variables a Vercel. Ejecuta tsc --noEmit antes de hacer push |
| Function Timeout | Procesamiento lento, bucles infinitos | maxDuration, cola asíncrona, Edge Runtime |
| Error OOM | Agotamiento de memoria | NODE_OPTIONS=--max-old-space-size=4096, streaming |
| 404 (solo producción) | Params estáticos incompletos, matcher incorrecto | Verifica dynamicParams, corrige el patrón del matcher |
| Caché obsoleto | force-cache implícito en fetch | Establece explícitamente revalidate o cache: "no-store" |
| Error de CORS | Faltan headers de CORS | Agrega headers a API routes o next.config.ts |
"Funciona localmente" no es "funciona en Vercel." Ejecutar tsc --noEmit y npm run lint antes de cada push eliminará la mitad de todos los errores relacionados con el build. Para el resto, los logs de Functions de Vercel y la checklist de variables de entorno te llevarán al resultado.