Garantizar la fiabilidad de plataformas web empresariales construidas con Next.js 15 exige segmentar la observabilidad en cuatro pilares complementarios:
prom-client.En Next.js 15, la integración del SDK @sentry/nextjs se ve condicionada por la coexistencia de tres entornos de ejecución independientes. Para evitar fallos en tiempo de ejecución o contaminación de paquetes, la inicialización se divide estrictamente en tres archivos:
sentry.client.config.ts: se ejecuta en el navegador; captura excepciones de cliente, registra grabaciones de sesión (Session Replay) y mide métricas web.sentry.server.config.ts: se ejecuta en el entorno de Node.js; instrumenta Server Components (RSC), Server Actions y Route Handlers.sentry.edge.config.ts: se ejecuta en el Edge Runtime ligero; instrumenta la capa perimetral de middleware.ts.Un requisito indispensable de seguridad en plataformas corporativas es el cumplimiento estricto de regulaciones como GDPR, HIPAA y PCI-DSS. Por defecto, los gestores de excepciones pueden recopilar valores de campos de contraseña, cabeceras de autorización (Authorization: Bearer ...) y cookies de sesión. La configuración de producción debe aplicar filtros algorítmicos de saneamiento (PII Scrubbing) en el hook beforeSend antes de transmitir cualquier carga a la red.
La arquitectura de ingesta de telemetría a través de las distintas capas de Next.js 15 se representa a continuación:
[ Navegador del Usuario ]
│
├─► [ Client Errors / Replay / RUM ] ──► (HTTPS) ──► [ Sentry Cloud ]
│
▼ (Petición HTTP)
[ Edge Middleware ] ──► [ Spans / Errores Edge ] ───────────┘
│
▼
[ Clúster SSR Node.js (Next.js 15) ]
│
├─► [ Excepciones del Servidor ] ────────────────────► [ Sentry Cloud ]
│
├─► [ Flujo JSON Pino (stdout) ] ────► [ Logtail / Recolector Vector ]
│
└─► [ Registro prom-client ]
▲
│ (HTTP Pull Scrape cada 15s)
│
[ Prometheus / VictoriaMetrics ] ──────► [ Paneles de Grafana ]nodejs_heap_size_used_bytes y Event Loop Lag en Prometheus para detectar fugas de memoria en cachés de SSR.x-request-id entre los registros estructurados de Pino en Logtail y los eventos de error en Sentry.El despliegue de una infraestructura de observabilidad robusta integra la configuración segmentada de Sentry con depuración de PII, la carga de mapas de código fuente protegidos en CI, la exposición de un registro prom-client mediante la ruta segura /api/metrics y el registro de logs estructurados con Pino.
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
release: process.env.NEXT_PUBLIC_APP_VERSION,
// Tasa de muestreo de transacciones de rendimiento (20% en producción)
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.2 : 1.0,
// Saneamiento de datos personales antes de enviar el evento a Sentry
beforeSend(event, hint) {
// 1. Eliminar cabeceras de autorización y credenciales
if (event.request?.headers) {
delete event.request.headers['authorization'];
delete event.request.headers['cookie'];
delete event.request.headers['x-api-key'];
}
// 2. Seudonimizar la identidad del usuario
if (event.user) {
// Conservar exclusivamente un ID interno anonimizado para agrupar fallos
event.user = { id: event.user.id };
}
// 3. Ocultar números de tarjetas de crédito y contraseñas en mensajes
if (event.message) {
event.message = event.message.replace(/\b(?:\d[ -]*?){13,16}\b/g, '[REDACTED_CARD]');
}
return event;
},
// Ignorar redirecciones habituales y abortos de conexión
ignoreErrors: [
'NEXT_NOT_FOUND',
'NEXT_REDIRECT',
'ECONNRESET',
'ClientAbortException',
],
});Para salvaguardar la propiedad intelectual del código fuente en producción, los mapas Source Maps se cargan en Sentry durante la compilación en CI/CD y se excluyen del acceso público del navegador:
import { withSentryConfig } from '@sentry/nextjs';
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
productionBrowserSourceMaps: false, // Bloquear la distribución pública de archivos .map
};
export default withSentryConfig(nextConfig, {
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
silent: !process.env.CI,
// Ocultar source maps del alcance público en el cliente
hideSourceMaps: true,
// Eliminar trazas de log de Sentry del paquete de producción
disableLogger: true,
// Detección y subida automática de mapas de todos los chunks de Next.js
widenClientFileUpload: true,
});El manejador global de Sentry captura una excepción no controlada en un Server Action.
El hook beforeSend purga cabeceras de autorización, cookies y enmascara dígitos de tarjetas.
El SDK transmite el evento mediante una petición POST en segundo plano hacia el endpoint de ingesta.
Sentry vincula la versión del release con los mapas cargados en CI para reconstruir el código TypeScript.
Si la tasa de error excede el umbral fijado (10 fallos/min), se activa una alerta en PagerDuty y Slack.
A continuación se muestra un registro de métricas implementado con prom-client que recopila variables del runtime de Node.js, histogramas de latencia y nivel de concurrencia:
import { Registry, collectDefaultMetrics, Histogram, Counter, Gauge } from 'prom-client';
/**
* Registro de métricas singleton para evitar duplicidades durante Hot Module Replacement (HMR)
*/
class PrometheusMetricsRegistry {
private static instance: PrometheusMetricsRegistry;
public readonly registry: Registry;
// 1. Histograma para registrar la duración de solicitudes HTTP en segundos
public readonly httpRequestDuration: Histogram<string>;
// 2. Contador de volumen total de solicitudes HTTP segmentadas por código
public readonly httpRequestsTotal: Counter<string>;
// 3. Medidor instantáneo de solicitudes en vuelo actualmente activas
public readonly httpActiveRequests: Gauge<string>;
private constructor() {
this.registry = new Registry();
// Recolección de métricas base del runtime de Node.js (RSS/Heap, GC, Event Loop Lag, CPU)
collectDefaultMetrics({
register: this.registry,
prefix: 'nodejs_nextjs_',
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2],
});
this.httpRequestDuration = new Histogram({
name: 'nextjs_http_request_duration_seconds',
help: 'Duración de las peticiones HTTP en segundos',
labelNames: ['method', 'route', 'status_code'],
// Intervalos exponenciales diseñados para percentiles de latencia web p95/p99
buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
registers: [this.registry],
});
this.httpRequestsTotal = new Counter({
name: 'nextjs_http_requests_total',
help: 'Total acumulado de peticiones HTTP entrantes',
labelNames: ['method', 'route', 'status_code'],
registers: [this.registry],
});
this.httpActiveRequests = new Gauge({
name: 'nextjs_http_active_requests',
help: 'Número de peticiones procesándose concurrentemente',
registers: [this.registry],
});
}
public static getInstance(): PrometheusMetricsRegistry {
if (!PrometheusMetricsRegistry.instance) {
PrometheusMetricsRegistry.instance = new PrometheusMetricsRegistry();
}
return PrometheusMetricsRegistry.instance;
}
}
export const metrics = PrometheusMetricsRegistry.getInstance();La ruta /api/metrics suministra la información en formato OpenMetrics. El acceso se encuentra restringido mediante un token Bearer:
import { NextResponse } from 'next/server';
import { metrics } from '@/lib/observability/prometheus-metrics';
export const dynamic = 'force-dynamic';
export const runtime = 'nodejs';
export async function GET(request: Request) {
// Protección ante accesos no autorizados a la telemetría del sistema
const authHeader = request.headers.get('authorization');
const expectedSecret = process.env.METRICS_SCRAPE_SECRET;
if (expectedSecret && authHeader !== `Bearer ${expectedSecret}`) {
return new NextResponse('Unauthorized', { status: 401 });
}
try {
const rawMetrics = await metrics.registry.metrics();
return new NextResponse(rawMetrics, {
status: 200,
headers: {
'Content-Type': metrics.registry.contentType,
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
},
});
} catch (err) {
return new NextResponse('Internal Metric Generation Error', { status: 500 });
}
}import pino from 'pino';
export const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
base: {
service: 'nextjs-web',
environment: process.env.NODE_ENV,
version: process.env.NEXT_PUBLIC_APP_VERSION,
},
timestamp: pino.stdTimeFunctions.isoTime,
});
/**
* Función de registro enriquecida con identificadores de contexto
*/
export function logWithContext(
level: 'info' | 'warn' | 'error',
message: string,
contextData: { requestId: string; traceId?: string; [key: string]: unknown }
) {
logger[level](contextData, message);
}Si se habilita productionBrowserSourceMaps: true en next.config.mjs, el servidor expondrá los archivos .js.map a cualquier cliente HTTP. Cualquier usuario podría acceder al código fuente original en TypeScript con sus tipos y comentarios internos. Establezca siempre hideSourceMaps: true en la configuración de Sentry y bloquee la publicación de mapas.
Añadir parámetros dinámicos como userId, orderId o URLs sin normalizar /orders/9482 a las labelNames de Prometheus genera series temporales infinitas en memoria. Bajo tráfico real, la base TSDB colapsa por agotamiento de RAM (OOMKilled). Emplee exclusivamente patrones normalizados: /orders/[id].
El SDK de Sentry en el navegador intercepta de forma predeterminada las llamadas fetch y XMLHttpRequest, adjuntando los cuerpos de petición al historial de navegación. Al enviar formularios de autenticación, la contraseña puede quedar visible en el panel de Sentry. Configure siempre el hook beforeBreadcrumb para enmascarar campos con nombres como password, token o secret.
Las invocaciones estándar de console.log en Node.js realizan operaciones síncronas de I/O al escribir hacia stdout en la consola o en el canal de Docker. Ante cargas elevadas (>1000 RPS), el I/O síncrono satura el Event Loop, aumentando la latencia de renderizado SSR en varios segundos. Integre siempre loggers asíncronos como Pino.
Configurar de manera aislada sentry.client, sentry.server y sentry.edge incorporando filtros beforeSend.
Integrar @sentry/nextjs en el pipeline con la opción hideSourceMaps: true para simbolizar trazas sin exponer código.
Crear un registro singleton con prom-client para monitorear el estado del runtime y latencias de petición.
Implementar autenticación con Bearer token para evitar el rastreo no autorizado de métricas de infraestructura.
Reemplazar console.log por Pino asociando trace_id y correlation_id a cada evento para auditoría completa.