← All posts

API & helpers

Web Vitals monitor in the tab — LCP / CLS / INP in Output Console without DevTools

Web Vitals (LCP, CLS, INP) are 3 metrics Google uses as an SEO ranking factor. The standard way to measure them: Lighthouse inside DevTools — 30 seconds per audit, manual trigger. Alternative: the PerformanceObserver API built into browsers. Plus a JustZix JS rule = a passive monitor that reports to Output Console for every page you visit. Perfect for a quick check of your own projects or analysing competitors.

JS rule — full Web Vitals capture

Scope: *://*/* or per-domain if you only want to monitor your own sites.

// JS rule "Web Vitals monitor"
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 });

// Report after 5 seconds from load + on unload (final values)
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)} (target <2.5s)`);
  JUSTZIX.log(`CLS:  ${vitals.CLS.toFixed(3)}  ${status(vitals.CLS, 0.1, 0.25)} (target <0.1)`);
  JUSTZIX.log(`FCP:  ${vitals.FCP ?? '?'}ms  ${status(vitals.FCP, 1800, 3000)} (target <1.8s)`);
  JUSTZIX.log(`TTFB: ${vitals.TTFB ?? '?'}ms  ${status(vitals.TTFB, 800, 1800)} (target <800ms)`);
  JUSTZIX.log(`INP:  ${vitals.INP ?? '?'}ms  ${status(vitals.INP, 200, 500)} (target <200ms)`);
};

setTimeout(report, 5000);
window.addEventListener('beforeunload', report);
JUSTZIX.info(`[Vitals] Monitor active for ${location.hostname}`);

What you see in Output Console

5 seconds after load:

[INFO]  [Vitals] Monitor active for www.justzix.com
[LOG]   === Web Vitals ===
[LOG]   LCP:  1240ms  ✓ (target <2.5s)
[LOG]   CLS:  0.024   ✓ (target <0.1)
[LOG]   FCP:  680ms   ✓ (target <1.8s)
[LOG]   TTFB: 145ms   ✓ (target <800ms)
[LOG]   INP:  85ms    ✓ (target <200ms)

Each metric with a visual status:

Use case 1 — Audit your own site without Lighthouse

Classic: deploy to staging, open the tab, check if Web Vitals are green. With Lighthouse: 30s wait. With JustZix: 5s, and it reports for every subsequent page you visit in that session.

Use case 2 — Compare against competitors

Visit a competitor. Output Console shows their LCP/CLS/INP. Your site — 1:1 comparison, same network conditions, same device.

Use case 3 — Live monitor during development

Snap an Output Console pane to the right side. Run your localhost dev server. Each F5 → a JUSTZIX.log with fresh vitals. You see live how your changes affect (e.g. lazy load image → LCP up, layout shift → CLS up).

Use case 4 — JSON export with a BUTTON action

// Action BUTTON "📊 Export 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('Exported vitals to clipboard.');

In the JS rule add a line: window.JZ_LATEST_VITALS = vitals; after every update. The BUTTON now has the current snapshot to export to docs / Slack.

Pitfalls

What's next

Install JustZix — zero-config Web Vitals monitor that NOTICES the problem before the user does.

Rate this post

No ratings yet — be the first.

Try it yourself

Install JustZix and paste any snippet from this article. Two minutes from zero to a working rule across all your devices.

Get JustZix

Features · How it works · Examples · Use cases