Monitor de Web Vitals en la pestaña — LCP / CLS / INP en la Output Console sin DevTools
Los Web Vitals (LCP, CLS, INP) son 3 métricas que Google usa como factor de ranking SEO. La forma estándar de medirlos: Lighthouse en las DevTools — 30 segundos por audit, activación manual. Alternativa: la API PerformanceObserver integrada en los navegadores. Más una regla JS JustZix = un monitor pasivo que reporta en la Output Console para cada página visitada. Perfecto para una comprobación rápida de tus propios proyectos o para analizar a los competidores.
Regla JS — captura completa de los Web Vitals
Scope: *://*/* o por dominio si quieres monitorizar solo tus sitios.
// Regla JS «Monitor Web Vitals»
const vitals = { LCP: null, CLS: 0, FCP: null, TTFB: null, INP: null };
// === LCP (Largest Contentful Paint) ===
new PerformanceObserver((list) => {
const entries = list.getEntries();
const last = entries[entries.length - 1];
vitals.LCP = Math.round(last.startTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });
// === CLS (Cumulative Layout Shift) ===
let clsEntries = [];
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsEntries.push(entry);
vitals.CLS = clsEntries.reduce((s, e) => s + e.value, 0);
}
}
}).observe({ type: 'layout-shift', buffered: true });
// === FCP (First Contentful Paint) ===
new PerformanceObserver((list) => {
const fcp = list.getEntries().find(e => e.name === 'first-contentful-paint');
if (fcp) vitals.FCP = Math.round(fcp.startTime);
}).observe({ type: 'paint', buffered: true });
// === TTFB (Time to First Byte) ===
const nav = performance.getEntriesByType('navigation')[0];
if (nav) vitals.TTFB = Math.round(nav.responseStart - nav.requestStart);
// === INP (Interaction to Next Paint) ===
let worstINP = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > worstINP) {
worstINP = Math.round(entry.duration);
vitals.INP = worstINP;
}
}
}).observe({ type: 'event', buffered: true, durationThreshold: 16 });
// Reporte 5 segundos después de la carga + al unload (valores finales)
const report = () => {
const status = (val, good, poor) =>
val === null ? '?' : val <= good ? '✓' : val <= poor ? '⚠' : '✗';
JUSTZIX.log('=== Web Vitals ===');
JUSTZIX.log(`LCP: ${vitals.LCP ?? '?'}ms ${status(vitals.LCP, 2500, 4000)} (objetivo <2.5s)`);
JUSTZIX.log(`CLS: ${vitals.CLS.toFixed(3)} ${status(vitals.CLS, 0.1, 0.25)} (objetivo <0.1)`);
JUSTZIX.log(`FCP: ${vitals.FCP ?? '?'}ms ${status(vitals.FCP, 1800, 3000)} (objetivo <1.8s)`);
JUSTZIX.log(`TTFB: ${vitals.TTFB ?? '?'}ms ${status(vitals.TTFB, 800, 1800)} (objetivo <800ms)`);
JUSTZIX.log(`INP: ${vitals.INP ?? '?'}ms ${status(vitals.INP, 200, 500)} (objetivo <200ms)`);
};
setTimeout(report, 5000);
window.addEventListener('beforeunload', report);
JUSTZIX.info(`[Vitals] Monitor activo para ${location.hostname}`);
Qué ves en la Output Console
5 segundos después de la carga:
[INFO] [Vitals] Monitor activo para www.justzix.com
[LOG] === Web Vitals ===
[LOG] LCP: 1240ms ✓ (objetivo <2.5s)
[LOG] CLS: 0.024 ✓ (objetivo <0.1)
[LOG] FCP: 680ms ✓ (objetivo <1.8s)
[LOG] TTFB: 145ms ✓ (objetivo <800ms)
[LOG] INP: 85ms ✓ (objetivo <200ms)
Cada métrica con un estado visual:
- ✓ = en el intervalo bueno (el «Good» de Google)
- ⚠ = en el intervalo «Needs improvement»
- ✗ = en el intervalo «Poor»
Caso de uso 1 — auditar tu propio sitio sin Lighthouse
Clásico: deploy en staging, abres la pestaña, compruebas si los Web Vitals están verdes. Con Lighthouse: 30 s de espera. Con JustZix: 5 s, y reporta para cada página siguiente visitada en esa sesión.
Caso de uso 2 — comparar con los competidores
Visita a un competidor. La Output Console muestra sus LCP/CLS/INP. Tu sitio — comparación 1:1, mismas condiciones de red, mismo dispositivo.
Caso de uso 3 — monitor en vivo durante el desarrollo
Engancha un pane Output Console al lado derecho. Arranca tu dev server localhost. Cada F5 → un JUSTZIX.log con vitals frescos. Ves en vivo el efecto de tus cambios (p. ej. imagen lazy-load → LCP arriba, layout shift → CLS arriba).
Caso de uso 4 — export JSON con una acción BUTTON
// Acción BUTTON «📊 Exportar vitals»
const data = {
url: location.href,
timestamp: new Date().toISOString(),
vitals: window.JZ_LATEST_VITALS || {}
};
const json = JSON.stringify(data, null, 2);
navigator.clipboard.writeText(json);
JUSTZIX.log('Vitals exportados al portapapeles.');
En la regla JS añade una línea: window.JZ_LATEST_VITALS = vitals; tras cada actualización. El BUTTON tiene ahora el snapshot actual para exportarlo a la documentación / Slack.
Trampas
- LCP en las single-page apps. Los cambios de ruta SPA no activan una nueva medición LCP. Workaround: escucha los eventos popstate y resetea los observers.
- El CLS continúa después de la carga. Las páginas con imágenes lazy-load pueden aumentar el CLS durante horas. CLS final = valor al unload (beforeunload). Lighthouse deja de medir tras 3-5 s.
- El INP requiere una interacción real. Una página estática sin clics → INP = null. Abre la página + haz scroll/clic antes de leer las métricas.
- TTFB = responseStart - requestStart, SIN DNS/conexión. El TTFB completo es
responseStart - fetchStart. Las implementaciones varían. - PerformanceObserver buffered: true — captura los eventos ocurridos antes de la inicialización del observer. Sin él, las entradas pre-init se pierden.
Qué hacer después
- Output Console en detalle — dónde aterriza JUSTZIX.log
- Depurar GTM sin desarrolladores — patrón de monitorización gemelo
- window.JZ + JUSTZIX — API programática
Instala JustZix — un monitor de Web Vitals zero-config que NOTA el problema antes que el usuario.
Valora este artículo
Sin valoraciones — sé el primero.