Blog · 18 de octubre de 2026

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ódigoCorre en…Su console.log sale en…
Server Components, page.tsx y layout.tsx sin 'use client'El servidorLa terminal de next dev; en producción, los logs de tu servidor o de Vercel
Route Handlers (route.ts) y Server ActionsEl servidorIgual: terminal o logs del servidor
proxy.ts (antes middleware.ts)El servidor, antes de cada rutaLogs del servidor; en Vercel, como «Routing Middleware»
Componentes con 'use client'El servidor (el primer render) y el navegadorLa consola del navegador de cada visitante, que nadie más ve
next buildTu CI o VercelEl 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:

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:

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.

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 VercelTus logs duran
Hobby1 hora
Pro1 día
Pro con Observability Plus30 días
Enterprise3 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

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.

Prueba 14 días de Pro, sin tarjeta Al terminar, sigues en Gratis con tus logs y tus errores.

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.

Sigue leyendo