Cambiar tema

Guía completa de monitorización en producción con Next.js: integración de Sentry, gestión de logs y alertas

Easton editorial illustration: deployment dock

Son las 9:17 de la noche del viernes y el móvil vibra.

El grupo de chat ya arde: «La página de pago no abre», «El pedido falló», «Pantalla en blanco». En local, todo funciona perfecto. Revisas los logs del servidor y solo aparece una línea: «Internal Server Error». El usuario dice que la página se congela al pulsar pagar, pero no logras reproducirlo.

Ese fin de semana repasaste los logs de despliegue en Vercel, redeployaste una versión de prueba y al final descubriste que el SDK de pago de terceros hacía timeout de forma intermitente en producción. Todo el proceso fue como buscar una aguja en la oscuridad.

Tu app Next.js puede ir como seda en desarrollo, pero en producción es otra historia — SSR que a veces devuelve 500, funciones edge que fallan sin motivo aparente, tiempos de respuesta de la API que se disparan sin saber dónde está el cuello de botella.

La raíz es simple: te falta un sistema completo de monitorización en producción. En este artículo montamos paso a paso un sistema para Next.js: desde Sentry hasta logs estructurados, rendimiento y alertas. Nada de teoría vacía: configuraciones que puedes copiar y pegar. Cuando leas esto, la próxima incidencia la sabrás antes que tus usuarios.

Por qué Next.js necesita un plan de monitorización específico

La triple naturaleza de Next.js

Una app frontend tradicional solo corre en el navegador; los errores los ves en DevTools. Next.js es distinto: la misma aplicación corre en tres entornos completamente diferentes:

  • Cliente (Browser): componentes React en el navegador del usuario
  • Servidor (Node.js): renderizado SSR, API Routes, Server Actions
  • Red perimetral (Edge Runtime): middleware, funciones edge

Un flujo de pago puede pasar por: validación del formulario en cliente → autenticación en middleware → Server Action → API Route que consulta la base de datos → respuesta al cliente. Si falla cualquier eslabón, la monitorización clásica del navegador no ve el panorama completo.

El año pasado tuve un bug muy raro: un usuario reportó «la página carga muy lento y luego muestra 500». El panel Network mostraba peticiones lentas, pero no dónde. Tras integrar el rastreo distribuido de Sentry, descubrimos que en el SSR se llamaba a una API de terceros que pasó de 200 ms habituales a 8 segundos. La monitorización del cliente nunca habría detectado eso.

El efecto caja negra del SSR

Cuando falla el renderizado en servidor, el usuario suele ver solo una página 500 desnuda. Sin stack trace, sin contexto, nada.

Peor aún: los errores de hidratación. Puede que hayas visto esta advertencia:

Warning: Expected server HTML to contain a matching <div> in <div>

En desarrollo puede pasar desapercibida; en producción puede dejar toda la página inutilizable. Sin monitorización, solo te enteras cuando un usuario dice «la página no responde al clic».

Según datos de Vercel, los errores relacionados con SSR representan alrededor del 35% de los problemas en producción con Next.js. Y eso solo son errores, no rendimiento: un componente cuyo SSR se alarga de repente hace que la página «se sienta lenta» sin que sepas dónde está el cuello de botella.

Los cuatro pilares de una monitorización completa

Un plan fiable para Next.js debe cubrir:

Seguimiento de errores
No basta con capturar excepciones: hay que saber quién la disparó (usuario), en qué entorno (dispositivo, navegador, red), qué hacía (breadcrumbs) y qué parámetros tenía la petición.

Monitorización de rendimiento
¿LCP supera 2,5 s? ¿La API responde más lento? ¿Qué consulta a la base de datos frena toda la petición?

Gestión de logs
Logs estructurados, búsqueda rápida por tiempo, usuario o ID de petición. Salida legible en desarrollo; en producción, envío a una plataforma centralizada.

Configuración de alertas
Notificar al equipo cuando la tasa de error supere un umbral; avisar en Slack ante un error nuevo; alertar automáticamente ante regresiones de rendimiento.

Con estos cuatro pilares dejas de ir a ciegas. Vamos uno por uno.

Integración de Sentry en la práctica: de la instalación a la configuración avanzada

Integración rápida en 5 minutos

Sentry tiene soporte maduro para Next.js y un asistente de configuración automática. En serio, 5 minutos:

# Instalar el SDK
npm install @sentry/nextjs

# Ejecutar el asistente
npx @sentry/wizard@latest -i nextjs

El asistente pregunta por el DSN del proyecto Sentry, si subir Source Maps, etc., y crea tres archivos:

  • sentry.client.config.ts — entorno del navegador
  • sentry.server.config.ts — servidor Node.js
  • sentry.edge.config.ts — Edge Runtime

También modifica next.config.js con el plugin webpack de Sentry. Tras el asistente, la monitorización básica ya funciona.

Pero eso es solo el inicio. En producción hace falta afinar más.

Puntos clave de captura de errores con App Router

Si usas App Router, hay varios detalles importantes:

Manejo global de errores

Crea app/global-error.tsx, la última línea de defensa en App Router:

'use client';

import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Enviar a Sentry
    Sentry.captureException(error);
  }, [error]);

  return (
    <html>
      <body>
        <div style={{ padding: '2rem', textAlign: 'center' }}>
          <h2>Algo salió mal</h2>
          <p>Hemos registrado el error y lo corregiremos pronto</p>
          <button onClick={() => reset()}>Reintentar</button>
        </div>
      </body>
    </html>
  );
}

Captura de errores en Server Actions

Las Server Actions son una joya de App Router, pero a menudo se olvida el manejo de errores:

'use server';

import * as Sentry from '@sentry/nextjs';

export async function createOrder(formData: FormData) {
  return await Sentry.withServerActionInstrumentation(
    'createOrder', // nombre visible en Sentry
    {
      recordResponse: true, // registrar respuesta
    },
    async () => {
      // tu lógica de negocio
      const productId = formData.get('productId');
      const order = await db.order.create({
        data: { productId, userId: getCurrentUserId() },
      });
      return order;
    }
  );
}

Así, cualquier error en la Server Action se reporta automáticamente y puedes medir el tiempo de ejecución.

Configuración optimizada para producción

Tras integrar Sentry, la factura del primer mes puede asustar: la configuración por defecto envía todos los eventos. Hay que ajustar el muestreo:

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,

  // Tasa de muestreo de trazas
  // 100% en desarrollo, 10% en producción (ajusta según tráfico)
  tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,

  // Session Replay
  replaysSessionSampleRate: 0.1,      // 10% de sesiones normales
  replaysOnErrorSampleRate: 1.0,      // 100% de sesiones con error

  // Identificador de entorno
  environment: process.env.NEXT_PUBLIC_VERCEL_ENV || 'development',

  // Ignorar errores concretos
  ignoreErrors: [
    // Errores de extensiones del navegador
    'ResizeObserver loop limit exceeded',
    // Scripts de terceros
    /chrome-extension/,
    /^Non-Error promise rejection/,
  ],
});

Guía de tracesSampleRate:

  • UV diarias < 10 000: 0,2 - 0,5
  • UV diarias 10 000 - 100 000: 0,1 - 0,2
  • UV diarias > 100 000: 0,05 - 0,1

En nuestro proyecto (~30 000 UV/día) usamos 0,15: unos 60% de la cuota mensual de Sentry, muestra suficiente y no nos pasamos de presupuesto.

Source Maps: depurables sin filtrar código

El JavaScript en producción suele estar minificado y ofuscado; el stack parece:

at r.render (app.js:1:23456)

Ilegible. Los Source Maps mapean el código ofuscado al original, pero exponerlos filtra el código fuente.

Sentry sube los Source Maps a sus servidores: el navegador del usuario no los recibe; Sentry los usa internamente para reconstruir el stack.

Configuración en CI/CD (ejemplo con GitHub Actions):

# .github/workflows/deploy.yml
- name: Upload Source Maps to Sentry
  env:
    SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
    SENTRY_ORG: your-org
    SENTRY_PROJECT: your-project
  run: npm run build

El plugin de Sentry en next.config.js gestiona la subida. Añade SENTRY_AUTH_TOKEN a GitHub Secrets; no lo commitees.

Funciones avanzadas: Session Replay y rastreo distribuido

Session Replay es de mis favoritas: reproduces la sesión del usuario como un vídeo.

Un usuario reportó «el botón de pago no responde». En su Session Replay vi que usaba iPad en horizontal y el teclado virtual tapaba el botón. Solo con logs de error nunca lo habríamos visto.

Para activarlo, en la config del cliente:

import * as Sentry from '@sentry/nextjs';
import { Replay } from '@sentry/nextjs';

Sentry.init({
  integrations: [
    new Replay({
      maskAllText: false,           // ocultar todo el texto
      blockAllMedia: true,          // bloquear medios
      maskAllInputs: true,          // ocultar inputs (evitar datos sensibles)
    }),
  ],
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
});

El rastreo distribuido sigue el ciclo de vida completo de una petición: clic → petición frontend → API Route → consulta a BD → renderizado. Ves el tiempo de cada paso.

La configuración es sencilla: misma config Sentry.init en cliente y servidor; el SDK propaga sentry-trace en las cabeceras.

Contexto personalizado: errores con más sentido

Por defecto Sentry solo sabe que «ocurrió un error». Con contexto personalizado, el informe vale mucho más:

import * as Sentry from '@sentry/nextjs';

// Información del usuario
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.username,
  // ¡No pongas contraseñas ni datos sensibles!
});

// Contexto de negocio
Sentry.setContext('purchase', {
  orderId: '12345',
  amount: 99.99,
  paymentMethod: 'credit_card',
});

// Etiquetas (para filtrar)
Sentry.setTag('feature', 'checkout');
Sentry.setTag('ab_test', 'variant_b');

Cuando ocurre el error, sabes de inmediato qué usuario y en qué escenario de negocio.

Caso real: detectamos un error de pago más frecuente de lo esperado; en Sentry vimos que eran todos usuarios con ab_test: variant_b. El nuevo flujo de pago del A/B test tenía un bug; cerramos esa variante a tiempo.

Gestión de logs: que trabajen para ti

console.log ya no basta

Al principio también usaba console.log por todas partes. En depuración va bien; en producción te deja colgado:

  • Sin filtrar: ¿buscar la petición de un usuario entre 100 000 líneas?
  • Sin agregar: ¿cuántas consultas a BD tardaron más de 1 s en la última hora?
  • Sin alertas: ¿«Payment failed» en los logs? Nadie se entera.

Los logs estructurados lo resuelven: no imprimes strings sueltos, sino objetos JSON con timestamp, nivel, ID de petición, ID de usuario, etc. Luego buscas y agregas por cualquier campo.

Pino vs Winston: cómo elegir

Dos grandes librerías de logs en Node.js:

CaracterísticaPinoWinston
RendimientoMuy rápido, casi sin coste asyncUn poco más lento, pero suficiente
FacilidadConfig sencilla, listo para usarMás funciones, buen ecosistema de plugins
ExtensibilidadVía TransportTransports integrados
ComunidadRecomendado en docs de Next.jsClásico, documentación amplia

Mi recomendación:

  • Alto tráfico (QPS > 1000): Pino, ventaja clara de rendimiento
  • Procesamiento complejo (múltiples formatos/destinos): Winston
  • Si dudas: Pino; la documentación oficial de Next.js lo usa

Configuración práctica con Pino

Instalación:

npm install pino
npm install pino-pretty --save-dev  # salida legible en desarrollo

Logger global:

// lib/logger.ts
import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',

  // Formato del nivel
  formatters: {
    level: (label) => ({ level: label.toUpperCase() }),
  },

  // pino-pretty en desarrollo
  transport: process.env.NODE_ENV === 'development'
    ? {
        target: 'pino-pretty',
        options: {
          colorize: true,
          translateTime: 'HH:MM:ss',
          ignore: 'pid,hostname',
        },
      }
    : undefined,
});

export { logger };

En desarrollo: salida coloreada y legible. En producción: JSON para la plataforma de logs.

Uso en API Route

Lo clave: asignar un correlationId (ID de correlación) a cada petición y encadenar todos los logs:

// app/api/products/route.ts
import { logger } from '@/lib/logger';
import { randomUUID } from 'crypto';

export async function GET(request: Request) {
  // ID único de la petición
  const correlationId = request.headers.get('x-correlation-id') || randomUUID();

  // Logger hijo con correlationId
  const log = logger.child({ correlationId });

  try {
    log.info({ url: request.url }, 'Processing product request');

    const products = await db.product.findMany();

    log.info({ count: products.length }, 'Products fetched successfully');

    return Response.json(products);
  } catch (error) {
    log.error({ error: error.message, stack: error.stack }, 'Failed to fetch products');
    throw error;
  }
}

Todos los logs con el mismo correlationId quedan vinculados; al depurar, buscas ese ID y ves toda la cadena.

Buenas prácticas de niveles de log

Pocos logs no sirven; demasiados enterran lo importante. Mi criterio:

ERROR — requiere acción inmediata

  • Fallo de conexión a base de datos
  • Fallo de API de pago
  • Excepción en lógica crítica
log.error({ error, userId, orderId }, 'Payment processing failed');

WARN — anomalía pero recuperable

  • Reintento de API exitoso
  • Lógica de degradación activada
  • Cerca del límite de cuota
log.warn({ retryCount: 3 }, 'External API retry succeeded');

INFO — hitos de negocio

  • Login/logout
  • Creación/cierre de pedido
  • Cambios de configuración importantes
log.info({ userId, ip }, 'User logged in');

DEBUG — detalle para depuración

  • Parámetros y retornos de funciones
  • Estados intermedios
  • Mediciones de tiempo
log.debug({ params }, 'Calling external API');

En producción, nivel INFO por defecto; sube a DEBUG solo cuando investigas un incidente.

Agregación y análisis de logs

En local, pino-pretty basta. En producción, una plataforma centralizada. Opciones habituales:

Vercel Logs
Si despliegas en Vercel, logs integrados sin configuración. Solo 7 días de retención y búsqueda limitada.

Datadog
Solución enterprise: APM + logs + monitorización. Configuración:

import { datadogLogs } from '@datadog/browser-logs';

datadogLogs.init({
  clientToken: process.env.NEXT_PUBLIC_DATADOG_CLIENT_TOKEN,
  site: 'datadoghq.com',
  forwardErrorsToLogs: true,
  sampleRate: 100,
});

Logtail/BetterStack
Buena relación calidad-precio, centrado en análisis de logs. Búsqueda en tiempo real, alertas, paneles personalizados.

En proyectos personales uso Logtail (1 GB/mes gratis). En equipo, Datadog: caro pero potente.

Diseño de campos clave en logs

Un buen log debería incluir:

{
  "timestamp": "2025-12-20T15:00:06.123Z",  // timestamp
  "level": "INFO",                          // nivel
  "correlationId": "abc-123-def",           // ID de correlación
  "userId": "user_456",                     // ID de usuario
  "action": "create_order",                 // acción de negocio
  "duration": 234,                          // duración (ms)
  "status": "success",                      // estado
  "metadata": {                             // metadatos extra
    "orderId": "order_789",
    "amount": 99.99
  }
}

Así respondes: quién hizo qué, cuándo y con qué resultado.

Monitorización de rendimiento: optimización basada en datos

Core Web Vitals: las métricas que le importan a Google

Google usa Core Web Vitals en el ranking de búsqueda; tú también deberías:

  • LCP (Largest Contentful Paint): tiempo de pintado del contenido principal, ideal < 2,5 s
  • FID (First Input Delay) / INP (Interaction to Next Paint): respuesta a interacción, < 100 ms / < 200 ms
  • CLS (Cumulative Layout Shift): desplazamiento acumulado de layout, < 0,1

Next.js incluye reporte de Web Vitals; en app/layout.tsx añade unas líneas:

'use client';

import { useReportWebVitals } from 'next/web-vitals';

export function WebVitalsReporter() {
  useReportWebVitals((metric) => {
    // Enviar a Sentry
    if (window.Sentry) {
      window.Sentry.captureMessage(`Web Vital: ${metric.name}`, {
        level: 'info',
        tags: {
          web_vital: metric.name,
        },
        contexts: {
          web_vitals: {
            value: metric.value,
            rating: metric.rating,
          },
        },
      });
    }

    // O a tu plataforma de analytics
    fetch('/api/analytics/web-vitals', {
      method: 'POST',
      body: JSON.stringify(metric),
    });
  });

  return null;
}

Luego impórtalo en el layout raíz:

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <WebVitalsReporter />
        {children}
      </body>
    </html>
  );
}

Seguimiento de rendimiento de API

El frontend es la mitad; si la API va lenta, el usuario también espera. Sentry Performance Monitoring rastrea cada petición:

// app/api/products/[id]/route.ts
import * as Sentry from '@sentry/nextjs';

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  // Crear un span
  return await Sentry.startSpan(
    {
      op: 'api.request',
      name: 'GET /api/products/[id]',
    },
    async () => {
      // Rastrear consulta a BD
      const product = await Sentry.startSpan(
        {
          op: 'db.query',
          name: 'Fetch product from database',
        },
        async () => {
          return await db.product.findUnique({
            where: { id: params.id },
            include: { reviews: true },
          });
        }
      );

      if (!product) {
        return Response.json({ error: 'Not found' }, { status: 404 });
      }

      // Rastrear API externa
      const pricing = await Sentry.startSpan(
        {
          op: 'http.client',
          name: 'Fetch pricing from external API',
        },
        async () => {
          const res = await fetch(`https://pricing-api.com/product/${params.id}`);
          return res.json();
        }
      );

      return Response.json({ ...product, pricing });
    }
  );
}

En el panel de Sentry verás:

  • Petición total: 450 ms
    • Consulta BD: 120 ms
    • API externa: 300 ms
    • Resto: 30 ms

De un vistazo: el cuello de botella es la API externa.

Alertas por consultas lentas

La base de datos suele ser el cuello de botella. Puedes monitorizar en middleware de Prisma:

// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
import { logger } from './logger';

const prisma = new PrismaClient();

// Monitorizar consultas lentas
prisma.$use(async (params, next) => {
  const before = Date.now();
  const result = await next(params);
  const after = Date.now();
  const duration = after - before;

  // Más de 1 s → WARN
  if (duration > 1000) {
    logger.warn({
      model: params.model,
      action: params.action,
      duration,
      args: params.args,
    }, 'Slow database query detected');

    // También a Sentry
    Sentry.captureMessage('Slow database query', {
      level: 'warning',
      tags: { model: params.model, action: params.action },
      extra: { duration, args: params.args },
    });
  }

  return result;
});

export { prisma };

Así ninguna consulta lenta se escapa.

Monitorización real de usuarios vs monitorización sintética

RUM (Real User Monitoring)
Sentry, Datadog, etc. recogen datos de usuarios reales. Refleja escenarios reales (red, dispositivo); es reactivo: te enteras cuando ya pasó.

Monitorización sintética (Synthetic Monitoring)
Checkly, Pingdom, etc. simulan visitas periódicas desde varias regiones. Proactiva; no cubre todos los escenarios de usuario.

Lo ideal: combinar ambas:

  • RUM para métricas de experiencia
  • Sintética para disponibilidad y flujos críticos (login, pago)

Uso Checkly cada 5 minutos desde 5 ubicaciones contra la home y el login; cualquier timeout o fallo dispara alerta.

Configuración de alertas: detectar problemas al instante

Integración con Slack: que el equipo lo sepa de inmediato

Integrar Sentry con Slack es sencillo: Settings → Integrations → Slack, autorizas y eliges el canal.

La config por defecto envía todos los errores y pronto se convierte en ruido. Hay que definir reglas de alerta:

En la configuración del proyecto Sentry:

  1. Alerts → Create Alert Rule
  2. Condiciones:
    • Tasa de error: «más de 50 errores en 10 minutos»
    • Error nuevo: «notificar al aparecer por primera vez»
    • Regresión de rendimiento: «P95 de API > 1 s»
  3. Action: Send a notification via Slack

Formato del mensaje en Slack:

🚨 Production Error Spike

Project: my-nextjs-app
Environment: production
Error: TypeError: Cannot read property 'id' of undefined
Events: 127 events in 10 minutes

View in Sentry: https://sentry.io/...

Un clic y vas directo al detalle y al stack en Sentry.

Niveles de alerta: evitar la fatiga

No todo tiene la misma urgencia. Mi estrategia:

P0 — crítico (acción inmediata)

  • Servicio completamente caído
  • Fallo en pagos
  • Pérdida de conexión a base de datos

Canal: teléfono (PagerDuty) + Slack @channel

P1 — importante (respuesta en 1 hora)

  • Funcionalidad core degradada
  • Pico de errores (> 100 en 10 min)
  • P95 de API > 3 s

Canal: Slack en canal de desarrollo

P2 — normal (en horario laboral)

  • Errores acotados (< 10/hora)
  • Funciones no críticas
  • Scripts de terceros

Canal: resumen diario por email

Ejemplo de reglas (Sentry Alert Rule):

// Alerta P0: fallo de pago
{
  conditions: [
    { type: 'event.tag', key: 'feature', value: 'payment' },
    { type: 'event.level', value: 'error' }
  ],
  frequency: 'every event',  // notificar siempre
  actions: [
    { type: 'slack', channel: '#critical-alerts', mention: '@channel' },
    { type: 'pagerduty', service: 'payments' }
  ]
}

// Alerta P1: pico de errores
{
  conditions: [
    { type: 'event.count', value: 100, interval: '10m' }
  ],
  frequency: 'once per issue',  // una vez por issue
  actions: [
    { type: 'slack', channel: '#alerts-dev' }
  ]
}

Técnicas para reducir ruido en alertas

Al principio las alertas pueden abrumarte. Algunos trucos:

1. Ignorar problemas conocidos

Errores de hot reload en desarrollo, scripts de terceros: filtrarlos:

// sentry.client.config.ts
Sentry.init({
  ignoreErrors: [
    // Extensiones del navegador
    /chrome-extension/,
    /moz-extension/,
    // Scripts de terceros
    /google-analytics/,
    // Hot reload en desarrollo
    /HMR/,
  ],
  denyUrls: [
    // Ignorar errores de scripts de dominios concretos
    /extensions\//i,
    /^chrome:\/\//i,
  ],
});

2. Agrupar alertas repetidas

El mismo error solo una vez cada 10 minutos. Sentry «Issue Grouping» agrupa errores similares.

3. Período de silencio

Durante despliegues puede haber picos breves; «Mute for 10 minutes».

4. Fingerprint

Reglas personalizadas de agrupación:

Sentry.captureException(error, {
  fingerprint: ['database-connection-error', databaseName],
});

Errores de conexión a distintas bases quedan separados para localizar mejor.

Caso práctico: despliegue de un plan de monitorización completo

Arquitectura de monitorización para un ecommerce

El año pasado ayudé a un ecommerce a renovar su monitorización. El esquema completo:

Contexto:

  • ~80 000 UV/día
  • QPS > 3000 en picos
  • Problemas: fallos intermitentes en pago, home lenta

Arquitectura:

┌─────────────┐
│   Next.js   │
│  frontend/SSR │
└──────┬──────┘

       ├─ Sentry (errores + rendimiento)
       ├─ Pino (logs estructurados) → Datadog
       ├─ Web Vitals → Sentry
       └─ Checkly (monitorización sintética)

Configuraciones clave:

  1. Seguimiento de comportamiento del usuario
// lib/tracking.ts
import * as Sentry from '@sentry/nextjs';

export function trackCheckoutStep(step: string, data: any) {
  Sentry.addBreadcrumb({
    category: 'checkout',
    message: `Checkout step: ${step}`,
    data,
    level: 'info',
  });
}

// En el flujo de compra
trackCheckoutStep('add_to_cart', { productId, price });
trackCheckoutStep('proceed_to_payment', { cartTotal });
trackCheckoutStep('payment_submitted', { method: 'credit_card' });

Cuando falla un pago, ves el recorrido completo del usuario.

  1. Monitorización de pagos
// app/api/payment/route.ts
export async function POST(request: Request) {
  const log = logger.child({ action: 'payment' });

  try {
    const result = await processPayment(data);

    log.info({ orderId, amount, method }, 'Payment succeeded');

    return Response.json({ success: true, orderId });
  } catch (error) {
    log.error({ error, orderId, userId }, 'Payment failed');

    // Alerta P0
    Sentry.captureException(error, {
      tags: { feature: 'payment', severity: 'critical' },
      level: 'fatal',
    });

    return Response.json({ error: 'Payment failed' }, { status: 500 });
  }
}

Cualquier fallo de pago avisa al equipo al instante.

  1. Línea base de rendimiento

Con Sentry Performance Monitoring:

  • LCP home < 2 s
  • LCP ficha de producto < 2,5 s
  • API /api/products P95 < 500 ms

Superar la línea base dispara alerta.

Resultados:

  • Tiempo medio de detección de incidentes: de 40 min a 3 min
  • Tasa de fallo en pago: de 0,8% a 0,2%
  • LCP de home: de 3,2 s a 1,8 s tras optimización

Checklist de monitorización

Lista final para revisar tu proyecto:

**Seguimiento de errores**
- [ ] Sentry configurado y probado
- [ ] Source Maps subidos correctamente
- [ ] global-error.tsx creado (App Router)
- [ ] Server Actions con manejo de errores
- [ ] Reglas de ignorado configuradas (filtrar ruido)

**Gestión de logs**
- [ ] Librería de logs integrada (Pino/Winston)
- [ ] Producción en formato JSON
- [ ] Logs incluyen correlationId
- [ ] Nivel correcto (INFO en producción)
- [ ] Logs en plataforma de agregación

**Monitorización de rendimiento**
- [ ] Web Vitals activos
- [ ] Core Web Vitals dentro de objetivo (LCP<2.5s, INP<200ms, CLS<0.1)
- [ ] APIs críticas con trazas de rendimiento
- [ ] Monitorización de consultas lentas
- [ ] Monitorización sintética (opcional)

**Alertas**
- [ ] Slack/email probados
- [ ] Reglas por prioridad
- [ ] Reglas anti-ruido
- [ ] El equipo conoce el flujo de respuesta
- [ ] Responsable definido para P0

**Mejora continua**
- [ ] Revisión semanal de datos
- [ ] Análisis de tendencia de errores
- [ ] Detección de regresiones de rendimiento
- [ ] Optimización periódica de reglas de alerta

Conclusión

De apagar fuegos a detectar problemas antes: la monitorización te devuelve el control del entorno de producción.

Resumen del sistema que montamos:

  • Sentry para errores y rendimiento, con replay de sesiones
  • Pino para logs estructurados, encadenados por correlationId
  • Web Vitals para métricas de experiencia y SEO
  • Alertas en Slack para avisar al equipo al instante, con niveles para evitar fatiga

Sobre todo, un cambio de mentalidad: la monitorización no es un «extra nice to have», es el airbag de producción. No esperas a un accidente para instalar el airbag; tampoco deberías esperar a un incidente para montar monitorización.

Empieza hoy. Si tu proyecto aún no tiene nada:

  1. Este fin de semana, 2 horas para Sentry y seguimiento básico de errores
  2. La semana que viene, logs estructurados y correlationId
  3. La siguiente, alertas en Slack y línea base de rendimiento

No busques la perfección de golpe: primero la monitorización básica de errores, luego mejora paso a paso. Tras cada incidente pregúntate: «¿podría haberlo detectado antes?» Con el tiempo, la monitorización será tu mejor aliada.

Si este artículo te ayudó, compártelo con el equipo. La monitorización es cosa de todos, no de una sola persona.

Que tu app Next.js vaya estable como una roca. (Aunque la realidad a veces dice lo contrario — por eso la monitorización importa tanto 😄)

Flujo completo de configuración de monitorización en producción con Next.js

Pasos completos desde la integración de Sentry hasta logs, rendimiento y alertas

⏱️ Estimated time: 3 hr

  1. 1

    Step 1: Integrar Sentry para seguimiento de errores

    Instalación:
    ```bash
    npm install @sentry/nextjs
    ```

    Inicialización:
    ```bash
    npx @sentry/wizard@latest -i nextjs
    ```

    Configuración del cliente:
    ```ts
    // sentry.client.config.ts
    import * as Sentry from '@sentry/nextjs'

    Sentry.init({
    dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
    environment: process.env.NODE_ENV,
    tracesSampleRate: 1.0,
    })
    ```

    Configuración del servidor:
    ```ts
    // sentry.server.config.ts
    import * as Sentry from '@sentry/nextjs'

    Sentry.init({
    dsn: process.env.SENTRY_DSN,
    environment: process.env.NODE_ENV,
    tracesSampleRate: 1.0,
    })
    ```

    Puntos clave:
    • Cliente y servidor se configuran por separado
    • Ajusta tracesSampleRate para controlar la tasa de muestreo
    • Configura las variables de entorno
  2. 2

    Step 2: Configurar logs estructurados

    Usa correlationId para vincular peticiones:
    ```ts
    // middleware.ts
    import { v4 as uuidv4 } from 'uuid'

    export function middleware(request: NextRequest) {
    const correlationId = request.headers.get('x-correlation-id') || uuidv4()

    const response = NextResponse.next()
    response.headers.set('x-correlation-id', correlationId)

    return response
    }
    ```

    En los logs:
    ```ts
    import { headers } from 'next/headers'

    export async function handler() {
    const headersList = headers()
    const correlationId = headersList.get('x-correlation-id')

    console.log({
    correlationId,
    message: 'User action',
    timestamp: new Date().toISOString(),
    })
    }
    ```

    Puntos clave:
    • Genera un ID único por petición
    • Incluye correlationId en todos los logs
    • Facilita rastrear toda la cadena de la petición
  3. 3

    Step 3: Configurar monitorización de rendimiento

    Sentry APM:
    ```ts
    Sentry.init({
    tracesSampleRate: 1.0, // 100% de muestreo
    integrations: [
    new Sentry.Integrations.Http({ tracing: true }),
    ],
    })
    ```

    Monitorización personalizada:
    ```ts
    const transaction = Sentry.startTransaction({
    op: 'http.server',
    name: 'API Route',
    })

    try {
    // lógica de negocio
    await processRequest()
    } finally {
    transaction.finish()
    }
    ```

    Puntos clave:
    • Ajusta tracesSampleRate para controlar el muestreo
    • Monitoriza tiempos de respuesta de la API
    • Identifica cuellos de botella
  4. 4

    Step 4: Configurar alertas

    Alertas en Sentry:
    • Configura reglas de alerta en el Sentry Dashboard
    • Define umbrales de error
    • Configura canales de notificación (Slack, email, etc.)

    Integración con Slack:
    ```ts
    // Configurar en el Sentry Dashboard
    // Webhook URL: https://hooks.slack.com/services/...
    ```

    Alertas por email:
    • Configurar en el Sentry Dashboard
    • Definir destinatarios
    • Configurar condiciones de alerta

    Puntos clave:
    • Umbrales razonables
    • Evita la fatiga de alertas
    • Responde a tiempo

FAQ

¿Por qué Next.js necesita un plan de monitorización específico?
Razón: la triple naturaleza de Next.js.

La misma aplicación corre en tres lugares distintos:
• Cliente (Browser): componentes React en el navegador del usuario
• Servidor (Node.js): renderizado SSR, API Routes, Server Actions
• Red perimetral (Edge Runtime): middleware, funciones edge

La monitorización frontend tradicional solo ve errores del cliente, no del servidor ni del edge.

Efecto caja negra del SSR:
• Cuando falla el renderizado en servidor, el usuario solo ve una página 500
• Sin stack trace ni contexto
• Hace falta el rastreo distribuido de Sentry para detectar el problema

Caso real:
• Un usuario reporta: la página carga muy lento y luego muestra 500
• El panel Network del navegador muestra peticiones lentas, pero no dónde
• Tras integrar Sentry se descubrió que una API de terceros pasó de 200 ms a 8 segundos en el servidor

Solución: un sistema de monitorización completo que cubra cliente, servidor y edge.
¿Cómo integrar Sentry?
Instalación:
```bash
npm install @sentry/nextjs
```

Inicialización:
```bash
npx @sentry/wizard@latest -i nextjs
```

Configuración del cliente:
```ts
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
```

Configuración del servidor:
```ts
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
```

Puntos clave:
• Cliente y servidor por separado
• tracesSampleRate controla el muestreo
• Variables de entorno (NEXT_PUBLIC_SENTRY_DSN, SENTRY_DSN)
¿Cómo configurar logs estructurados?
Usa correlationId para vincular peticiones:

Generar en middleware:
```ts
import { v4 as uuidv4 } from 'uuid'

export function middleware(request: NextRequest) {
const correlationId = request.headers.get('x-correlation-id') || uuidv4()

const response = NextResponse.next()
response.headers.set('x-correlation-id', correlationId)

return response
}
```

En los logs:
```ts
import { headers } from 'next/headers'

export async function handler() {
const headersList = headers()
const correlationId = headersList.get('x-correlation-id')

console.log({
correlationId,
message: 'User action',
timestamp: new Date().toISOString(),
})
}
```

Ventajas:
• ID único por petición
• Todos los logs llevan correlationId
• Rastreo de toda la cadena
• Localización rápida de problemas

Clave: generar en middleware, usar en logs, facilitar la correlación.
¿Cómo configurar monitorización de rendimiento?
Sentry APM:
```ts
Sentry.init({
tracesSampleRate: 1.0, // 100% de muestreo (en producción se recomienda 0.1)
integrations: [
new Sentry.Integrations.Http({ tracing: true }),
],
})
```

Monitorización personalizada:
```ts
const transaction = Sentry.startTransaction({
op: 'http.server',
name: 'API Route',
})

try {
// lógica de negocio
await processRequest()
} finally {
transaction.finish()
}
```

Métricas:
• Tiempo de respuesta de la API
• Tiempo de consultas a base de datos
• Tiempo de llamadas a APIs de terceros
• Tiempo de carga de página

Puntos clave:
• tracesSampleRate (en producción se recomienda 0.1)
• Monitoriza rutas críticas
• Identifica cuellos de botella
¿Cómo configurar alertas?
Alertas en Sentry:
• Reglas de alerta en el Sentry Dashboard
• Umbrales de error (p. ej.: más de 10 errores en 5 minutos)
• Canales de notificación (Slack, email, etc.)

Integración con Slack:
• Webhook URL en el Sentry Dashboard
• Condiciones de alerta
• Probar alertas

Alertas por email:
• Configurar en el Sentry Dashboard
• Destinatarios
• Condiciones de alerta

Reglas recomendadas:
• Tasa de error por encima del umbral
• Tiempo de respuesta por encima del umbral
• Tipos de error concretos
• Errores nuevos

Puntos clave:
• Umbrales razonables
• Evita fatiga de alertas
• Responde a tiempo

Consejo: primero la monitorización básica de errores, luego ve mejorando.
¿Cuáles son las mejores prácticas de monitorización?
Implementación gradual:
1. Este fin de semana, 2 horas para integrar Sentry y seguimiento básico de errores
2. La semana que viene, logs estructurados y correlationId
3. La siguiente, alertas en Slack y línea base de rendimiento

No busques hacerlo todo de golpe; primero la monitorización básica de errores.

Mejora continua:
• Tras cada incidente: ¿podría la monitorización haberlo detectado antes?
• Ajusta umbrales según la realidad
• Revisa la configuración periódicamente

Métricas clave:
• Tasa de error
• Tiempo de respuesta
• Alcance del impacto en usuarios
• Tiempo de recuperación

Recomendaciones:
• La monitorización es cosa del equipo, no de una sola persona
• Comparte datos de monitorización con regularidad
• Mejora el sistema de forma continua

Recuerda: la monitorización no es una tarea puntual, sino un proceso continuo.

18 min de lectura · Publicado el: 20 dic 2025 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog