Monitoreo de Usuarios Reales (RUM) con web-vitals
Monitorea experiencias reales de usuarios con Core Web Vitals, session replay y análisis de performance para identificar cuellos de botella del mundo real.
Visión General
Real User Monitoring (RUM) captura datos de performance de sesiones reales de navegadores — no de tests sintéticos ni métricas del lado del servidor. Revela cómo los Core Web Vitals, los errores de JavaScript y las latencias de API varían entre dispositivos, redes y geografías. A diferencia de los tests de laboratorio que corren en condiciones ideales, RUM expone la experiencia de usuarios en redes 3G, dispositivos de gama baja y navegadores antiguos.
Esta receta monta un pipeline de RUM mínimo: recolectar vitals con la librería web-vitals de Google, enviarlos con sendBeacon y añadir session replay encima. Cubre lo mismo por lo que cobran las herramientas comerciales, y se monta en una tarde.
Cuándo Usar
Usa este recurso cuando:
- Los scores de Lighthouse en laboratorio no coinciden con las quejas de performance del mundo real
- Necesitas correlacionar métricas de negocio (conversión, bounce rate) con la velocidad de página
- Estás depurando problemas de performance que solo afectan a navegadores o regiones concretas
- Quieres priorizar el trabajo de optimización según el impacto real en usuarios, no suposiciones
Solución
Recolectar Core Web Vitals (web-vitals v4)
import { onCLS, onINP, onFCP, onLCP, onTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
rating: metric.rating, // 'good', 'needs-improvement', 'poor'
delta: metric.delta,
navigationType: metric.navigationType,
page: window.location.pathname,
});
// sendBeacon sobrevive al unload de la página — un fetch puede cancelarse
const queued = navigator.sendBeacon?.('/analytics/vitals', body);
if (!queued) {
fetch('/analytics/vitals', { body, method: 'POST', keepalive: true });
}
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics); // INP reemplazó a FID en web-vitals v4
onFCP(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);
Dos detalles que conviene saber: web-vitals v4 reporta INP en lugar de FID (FID se retiró de Core Web Vitals en marzo de 2024), y sendBeacon es lo que mantiene vivos los eventos cuando el usuario cierra la pestaña en medio de una medición.
Session replay encima (Sentry)
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'https://abc@sentry.io/1',
integrations: [
Sentry.replayIntegration({
maskAllText: true, // enmascara texto sensible en las repeticiones
blockAllMedia: true, // no captura imágenes ni vídeo
}),
],
tracesSampleRate: 0.1, // 10% de las transacciones
replaysSessionSampleRate: 0.01, // 1% de las sesiones normales
replaysOnErrorSampleRate: 1.0, // 100% de las sesiones con error
});
El paquete independiente @sentry/replay está deprecado — desde el SDK v7, replay viene dentro de @sentry/browser como replayIntegration().
PerformanceObserver directo para métricas personalizadas
Cuando necesitas algo que la librería no reporta — recursos lentos, long tasks, tiempos de ruta — ve directo a PerformanceObserver:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'resource' && entry.duration > 1000) {
sendToAnalytics({
name: 'slow-resource',
value: entry.duration,
id: entry.name,
rating: 'poor',
});
}
}
});
observer.observe({ entryTypes: ['resource', 'longtask'] });
Reutiliza tu sendToAnalytics en lugar de console.log — una métrica que se queda en la consola del usuario nunca llega a tu dashboard.
Explicación
RUM vs. monitoreo sintético
| Aspecto | RUM | Sintético |
|---|---|---|
| Fuente de datos | Usuarios reales | Bots programados |
| Red | Variable (3G a 5G) | Controlada (rápida) |
| Diversidad de dispositivos | Rango completo | Usualmente desktop |
| Geografía | Ubicaciones reales de usuarios | Data center |
| Caso de uso | Entender la realidad | Detección de regresiones base |
Necesitas los dos, por razones distintas: el sintético detecta “¿este deploy rompió la performance?” y el RUM responde “¿los usuarios reales están sufriendo?”.
Los datos de RUM solo se vuelven útiles cuando los segmentas. Un P75 global de 3s puede significar que todo va un poco lento, o que los usuarios móviles en 3G están en 8s mientras desktop va a 1,2s — el mismo número, un problema completamente distinto. Etiqueta cada evento con clase de dispositivo, tipo de conexión efectiva y geografía desde el día uno; añadir dimensiones sobre datos ya recolectados es imposible.
El flujo de datos es este:
Las métricas que importan
- LCP (Largest Contentful Paint): cuándo renderiza el elemento visible más grande — hero image, heading principal. Bueno ≤ 2,5s.
- INP (Interaction to Next Paint): latencia desde el click o tecla hasta la siguiente actualización visual. Reemplazó a FID; bueno ≤ 200ms.
- CLS (Cumulative Layout Shift): cuánto salta el layout — los culpables habituales son ads, imágenes y fuentes. Bueno ≤ 0,1.
- TTFB (Time to First Byte): tiempo de respuesta del servidor — hosting más backend. Mira server-side rendering si el tuyo va lento.
- FCP (First Contentful Paint): primer texto o imagen en pantalla. Ya no es un Core Web Vital, pero sigue siendo una señal temprana útil.
Las SPAs necesitan trabajo extra
web-vitals mide la carga inicial de la página. En una SPA de React/Vue, cada cambio de ruta del lado del cliente es invisible para la librería — no hay una nueva navegación. Mide las transiciones de ruta tú mismo: el tiempo desde la llamada navigate del router hasta el primer paint de la nueva ruta (un requestAnimationFrame tras montar el componente de la ruta te acerca bastante), y repórtalo como métrica personalizada. Si no, tu dashboard “rápido” solo está midiendo la página de aterrizaje.
El nombrado de rutas importa tanto como la propia medición. Reporta el patrón de ruta (/products/:id), no la URL cruda (/products/4821) — si no, cada página de ítem se convierte en una serie distinta y tus percentiles se fragmentan en ruido. Agrupa estas métricas personalizadas junto al payload de vitals en lugar de disparar un beacon por navegación; un usuario que recorre diez rutas no debería costarte diez peticiones.
Variantes
| Herramienta | Tipo | Capacidades destacadas |
|---|---|---|
| Google CrUX | Gratis, solo Chrome | El dataset de mundo real más grande; alimenta el field data de PageSpeed |
| web-vitals.js | Open source | Implementación de referencia de Google — la que usa esta receta |
| Sentry | Open source / SaaS | Errores + performance + replay en un solo SDK |
| Datadog RUM | Comercial | Correlación con APM; session replay integrado |
| New Relic Browser | Comercial | Session traces; tracking de errores JS |
| SpeedCurve | Comercial | Benchmarking competitivo; vistas filmstrip |
Móntalo por tu cuenta cuando quieras control total de los datos y puedas mantener un endpoint de ingesta. Ve a lo comercial cuando prefieras pagar antes que operar un pipeline de métricas — el precio suele salir razonable comparado con las horas de ingeniería.
Lo que Funciona
Algunos hábitos que convierten los datos de RUM en algo accionable en lugar de decorativo:
- Muestrea con cabeza: el sampling al 100% satura tu backend y tu factura; 5–10% suele ser suficiente.
- Captura contexto: tipo de dispositivo, velocidad de conexión y país explican la mayor parte de la varianza — adjúntalos a cada evento.
- Alerta en percentiles, no en promedios: el P95 es lo que experimentan los usuarios frustrados; una media de 2s puede ocultar que el 20% de usuarios ve 8s.
- Correlaciona con métricas de negocio: grafica la tasa de conversión contra LCP para justificar presupuestos de optimización. Mira recolección de métricas.
- Respeta la privacidad: enmascara PII en el session replay, anonimiza IPs y cumple el consentimiento de telemetría bajo GDPR/CCPA.
Errores Comunes
- Monitorear solo la homepage — las páginas de producto y el checkout suelen rendir peor, y son donde está el dinero.
- Ignorar las navegaciones de SPA — las single-page apps necesitan medición propia para los cambios de ruta, o la mayor parte de la sesión queda sin medir.
- No correlacionar con errores — una página lenta que además lanza errores JS necesita otra priorización. Mira manejo de errores.
- Alertar sobre promedios — un LCP medio de 2s oculta a la quinta parte de tus usuarios mirando cargas de más de 8s.
- Recolectar datos que nadie usa — RUM sin sprints de optimización programados es logging caro.
Lectura Adicional
- web.dev — Web Vitals — definiciones de métricas y umbrales
- web-vitals en GitHub — la librería de esta receta, incluido el attribution reporting
- Documentación de Sentry Session Replay — configuración de replay y controles de privacidad
- Chrome UX Report (CrUX) — el dataset público de mundo real
- Repositorio companion — el colector de vitals, el setup de replay y el performance observer de esta página como archivos ejecutables
Preguntas frecuentes
¿RUM ralentiza mi sitio?
De forma despreciable. La librería web-vitals pesa alrededor de 1KB y los beacons salen después de que la página es interactiva. El session replay sí pesa más — por eso se muestrea al 1% y no al 100%.
¿Debo usar RUM o monitoreo sintético?
Los dos. El sintético detecta regresiones en cada deploy; el RUM te dice lo que los usuarios reales experimentan en sus dispositivos y redes. Uno sin el otro deja un punto ciego.
¿Cómo gestiono los ad blockers?
Sirve el script de RUM y el endpoint de ingesta desde tu propio dominio (first-party). Los ad blockers apuntan a dominios de analytics conocidos, así que rum.tudominio.com suele sobrevivir — aun así, cuenta con que un pequeño porcentaje de sesiones se escape.
¿Cómo elijo una herramienta de RUM?
Según lo que ya uses y lo que necesites responder. Datadog RUM si quieres correlación full-stack; Sentry si tu preocupación principal son los errores; CrUX si solo necesitas benchmarks de Core Web Vitals; o tu propio pipeline con web-vitals si quieres los datos crudos. Elijas lo que elijas, verifica en staging que los números coinciden con lo que reportan las herramientas de laboratorio.
¿Con qué tasa de sampling empiezo?
5–10% de las sesiones para vitals, 1% para replay, 100% para sesiones con error. Sube la tasa de vitals en sitios con poco tráfico (necesitas volumen para percentiles con sentido) y bájala cuando veas la factura.
Recursos Relacionados
Optimización de Performance Web
Mejora Core Web Vitals, reduce tamaños de bundle y optimiza performance frontend con lazy loading, code splitting y herramientas de build modernas.
RecipeRecoleccion de metricas
Recolecta, agrega y expone métricas de aplicación e infraestructura con Prometheus, StatsD y OpenTelemetry para monitoreo y alertado.
RecipeDashboards de Grafana para Observabilidad con Prometheus
Construye dashboards de Grafana que visualizan métricas de Prometheus. Usá paneles, variables de template, provisioning y alertas para observabilidad del equipo.
GuideWeb Performance y Core Web Vitals
Optimizar Core Web Vitals. Cubre LCP, INP, CLS measurement y improvement, image optimization, font loading, render-blocking resources, lazy loading, caching strategies y performance monitoring con ejemplos practicos de codigo.
GuideSentry: Triage y Resolución de Errores
Dominá Sentry para el seguimiento de errores en producción. Cubre integración del SDK en Python, Node.js y Java, release tracking, source maps, performance monitoring y alertas.
DocChecklist de Auditoria de Core Web Vitals
Checklist para auditar Core Web Vitals por pagina: LCP optimization steps, INP interaction tuning, CLS layout stability fixes, field data vs lab data analysis, CrUX integration y remediation tracking con ejemplos de codigo para imagenes, fuentes, JavaScript y CSS.