Logs en Next.js en producción: dónde quedan y cómo no perder los errores
Por qué tu console.log no sale donde esperas, cómo escribir logs que se puedan buscar, cómo atrapar los errores del servidor y del navegador con instrumentation.ts, y lo que pasa con tus logs en Vercel. Para Next.js 15 y 16, con App Router.
1. ¿Dónde sale cada log?
En Next.js tu código corre en tres lugares, y cada uno escribe sus logs en un sitio distinto. Casi toda la confusión viene de aquí:
| Tu código | Corre en… | Su console.log sale en… |
|---|---|---|
Server Components, page.tsx y layout.tsx sin 'use client' | El servidor | La terminal de next dev; en producción, los logs de tu servidor o de Vercel |
Route Handlers (route.ts) y Server Actions | El servidor | Igual: terminal o logs del servidor |
proxy.ts (antes middleware.ts) | El servidor, antes de cada ruta | Logs del servidor; en Vercel, como «Routing Middleware» |
Componentes con 'use client' | El servidor (el primer render) y el navegador | La consola del navegador de cada visitante, que nadie más ve |
next build | Tu CI o Vercel | El log de la compilación (las páginas estáticas se renderizan aquí) |
En Next.js 16, middleware.ts se llama proxy.ts y corre en Node.js de entrada.
2. ¿Por qué no veo mi console.log?
Las tres razones más comunes:
- Lo buscas en el navegador y es un Server Component. Sin
'use client', el componente corre en el servidor: su log sale en la terminal, no en las herramientas del navegador. - La página es estática. Si la página no lee nada de la petición, Next.js la renderiza una sola vez en
next build. Su log salió en la compilación y no vuelve a salir con cada visita. - Es un componente de cliente en producción. El log sale en el navegador del visitante. Si le truena a un cliente, tú no lo ves. Para eso es el paso 5.
3. Logs que se pueden buscar, con pino
console.log escribe texto libre, sin nivel y sin decir de qué petición viene. Para el servidor, usa pino: escribe JSON y Next.js ya lo deja fuera de su bundle (pino y pino-pretty están en su lista de serverExternalPackages), así que funciona sin configurar nada.
npm install pino
npm install --save-dev pino-pretty
// lib/logger.ts
import pino from 'pino';
export const logger = pino({
level: process.env.LOG_LEVEL || 'info',
// legible en tu terminal; JSON puro en producción
transport: process.env.NODE_ENV === 'production' ? undefined : { target: 'pino-pretty' },
redact: ['*.password', '*.token', '*.tarjeta'],
});
// app/api/pedidos/route.ts
import { logger } from '@/lib/logger';
export async function POST(req: Request) {
const log = logger.child({ ruta: '/api/pedidos', peticion: req.headers.get('x-vercel-id') });
const pedido = await req.json();
log.info({ pedido: pedido.id, monto: pedido.total }, 'pedido recibido');
// …
}
Dos detalles propios de Next.js:
- Usa el logger solo en código de servidor. Si lo importas en un componente con
'use client', Next.js intenta mandarlo al navegador. Para que eso truene en la compilación y no en producción, ponimport 'server-only'al principio delib/logger.ts. - El id de cada petición. En Vercel, la cabecera
x-vercel-ididentifica la petición y es la que ves en su panel. En tu servidor, usa la de tu balanceador (x-request-id) o eltraceparentde W3C.
4. Los errores del servidor: onRequestError
En producción, cuando truena un Server Component, Next.js le manda al navegador un mensaje genérico y un digest, un código que identifica el error: no le muestra al visitante tu mensaje ni tu stack. El error completo solo queda en el servidor, una línea entre miles.
Desde Next.js 15, instrumentation.ts (en la raíz del proyecto, o en src/) tiene un gancho que recibe todos los errores del servidor: los de Server Components, Route Handlers, Server Actions y proxy.ts.
// instrumentation.ts
import type { Instrumentation } from 'next';
export function register() {
// corre una vez, al arrancar cada servidor
}
export const onRequestError: Instrumentation.onRequestError = async (err, request, context) => {
const { logger } = await import('./lib/logger');
logger.error({
err,
digest: (err as { digest?: string }).digest, // el mismo código que vio el navegador
ruta: context.routePath, // app/pedidos/[id]/page
tipo: context.routeType, // render, route, action o proxy
metodo: request.method,
}, 'error en el servidor');
};
Con el digest en el log, cuando un cliente te mande la captura de «Application error» con su código, lo buscas y llegas al error con su stack.
Si algo en onRequestError es asíncrono, espéralo con await: Next.js no espera lo que dejes suelto.
5. Los errores del navegador: error.tsx
Lo que truena en un componente de cliente no llega a onRequestError: pasa en el navegador. Next.js lo atrapa con el error.tsx más cercano (y global-error.tsx si truena el layout raíz), que es un componente de cliente. Ahí puedes reportarlo:
// app/error.tsx
'use client';
import { useEffect } from 'react';
export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
useEffect(() => {
// manda el error a donde lo vayas a ver (tu API, o un servicio de errores)
navigator.sendBeacon('/api/errores', JSON.stringify({
mensaje: error.message, digest: error.digest, pagina: location.pathname,
}));
}, [error]);
return <button onClick={reset}>Algo falló. Intentar de nuevo</button>;
}
Si el error vino del servidor, error.digest es el mismo código que quedó en tu log del paso 4. Si nació en el navegador, no hay digest y lo único que tienes es lo que mandes desde aquí.
Un detalle: error.tsx solo atrapa errores de render. Un error en un onClick o en un setTimeout no pasa por ahí; para esos hace falta escuchar window.onerror y unhandledrejection.
6. En Vercel: una hora o un día
Vercel junta tus logs por petición y te deja buscarlos en su panel (Logs) o con vercel logs. Lo que casi nadie revisa hasta que lo necesita es cuánto tiempo los guarda:
| Plan de Vercel | Tus logs duran |
|---|---|
| Hobby | 1 hora |
| Pro | 1 día |
| Pro con Observability Plus | 30 días |
| Enterprise | 3 días (30 con Observability Plus) |
Y por petición: hasta 256 líneas, de hasta 256 KB cada una, y 1 MB en total; si te pasas, solo quedan las más recientes. La búsqueda de texto libre mira el mensaje y la ruta, no los campos de tu JSON.
Así que si un cliente te escribe el lunes por algo que pasó el sábado, en Hobby y en Pro ese log ya no existe. Para guardarlos más tiempo hay que mandarlos a otro lado: con un log drain de Vercel o desde tu propio código.
Límites de la documentación de Runtime Logs de Vercel, consultada el 18 de octubre de 2026.
7. Que el log salga aunque ya respondiste: after()
En Vercel (y en cualquier plataforma serverless), tu función puede quedar congelada en cuanto responde. Lo que estuviera mandando en segundo plano, un log a otro servicio por ejemplo, se queda sin salir. Por eso muchos logs «se pierden» justo en producción.
Desde Next.js 15.1, after() programa trabajo para después de la respuesta, y la plataforma espera a que termine (en Vercel, con waitUntil). El visitante no espera, y el log sí sale:
import { after } from 'next/server';
export async function POST(req: Request) {
const pedido = await cobrar(await req.json());
after(() => mandarLog({ pedido: pedido.id, monto: pedido.total })); // después de responder
return Response.json({ ok: true });
}
after() sirve en Server Components, Route Handlers, Server Actions y proxy.ts, y corre aunque la respuesta haya terminado en error.
8. Lo que no va en tus logs
- Contraseñas, tokens, cookies y llaves. En Next.js, ojo con loguear
headers()ocookies()completos: ahí va la sesión. - Variables de entorno. Un
console.log(process.env)en el servidor deja todas tus llaves en el log. Y lo que empieza conNEXT_PUBLIC_llega al navegador: nunca pongas un secreto ahí. - Números de tarjeta y datos personales que no necesitas. Con el id del cliente basta.
9. Que alguien los lea por ti
Con todo lo anterior tus logs se pueden buscar, pero nadie los está mirando, y en Vercel duran una hora o un día. Lo que falta es enterarte sin abrirlos: el error nuevo, la ruta que empezó a fallar, el pedido pagado que nunca se surtió.
Con Estiaje es instrumentation.ts y una línea por ruta:
npm install @estiaje/sdk
// lib/estiaje.ts
import { estiajeFromEnv } from '@estiaje/sdk';
export const estiaje = estiajeFromEnv({ service: 'tienda-web' });
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { estiaje } = await import('./lib/estiaje');
estiaje.capturarConsola(); // tus console.log, tal cual
estiaje.capturarErrores(); // los que nadie atrapó
}
}
export async function onRequestError(err: unknown, request: { path: string }, context: { routePath: string; routeType: string }) {
if (process.env.NEXT_RUNTIME !== 'nodejs') return;
const { estiaje } = await import('./lib/estiaje');
estiaje.error('error en el servidor', { ruta: context.routePath, tipo: context.routeType }, err); // con su stack
await estiaje.flush();
}
// en una ruta: el proceso que debe terminar, y que salga aunque la función se congele
estiaje.event('pedido.pagado', { order_id: pedido.id, monto: pedido.total });
after(() => estiaje.flush());
Tus logs se buscan por texto y se filtran por servicio, nivel o cualquier campo, por más de un día. Los errores llegan agrupados con su stack, y los del navegador también, con una etiqueta <script> (la receta completa para Next.js). Si le dices que todo pedido.pagado debe terminar en pedido.surtido, te avisa al teléfono del que se quedó a medias, con el dinero en riesgo. Los logs vienen desde el plan Gratis.
Preguntas frecuentes
¿Dónde veo los logs de Next.js en producción?
Los del servidor (Server Components, Route Handlers, Server Actions y proxy) salen en la salida estándar de tu servidor: con Docker, en docker logs; con pm2, en pm2 logs; en Vercel, en la sección Logs de tu proyecto o con vercel logs. Los de los componentes de cliente salen en el navegador de cada visitante.
¿Por qué mi console.log no aparece en el navegador?
Porque el componente corre en el servidor. En App Router, todo componente sin 'use client' es un Server Component y su log sale en la terminal de next dev o en los logs del servidor. Si la página es estática, el log salió una sola vez, en next build.
¿Cuánto tiempo guarda Vercel los logs?
Una hora en Hobby, un día en Pro y tres días en Enterprise; 30 días con Observability Plus. Además, hasta 256 líneas por petición. Para guardarlos más tiempo, mándalos a otro servicio con un log drain o desde tu código.
¿Qué es el digest de un error de Next.js?
En producción, Next.js no le muestra al navegador el mensaje de un error del servidor: le manda un mensaje genérico y un código, el digest. El mismo código llega a onRequestError en instrumentation.ts; si lo guardas en tu log, puedes ir de la captura del cliente al error con su stack.
¿Puedo usar pino con Next.js?
Sí, en el código de servidor. Next.js ya deja a pino y pino-pretty fuera de su bundle, así que no hace falta configurar serverExternalPackages. No lo importes en componentes de cliente; import 'server-only' en tu archivo del logger lo evita.
Next.js 16.4 y documentación de Next.js y Vercel, consultadas el 18 de octubre de 2026.