Web Vitals monitor w karcie — LCP / CLS / INP w Output Console bez DevTools
Web Vitals (LCP, CLS, INP) to 3 metryki które Google używa jako ranking factor SEO. Standardowy sposób mierzenia: Lighthouse w DevTools — 30 sekund per audit, manual trigger. Alternatywa: PerformanceObserver API wbudowany w przeglądarki. Plus JustZix JS rule = passive monitor który raportuje do Output Console dla każdej strony którą odwiedzasz. Idealne do quick check'u własnych projektów albo analyse competition.
JS rule — full Web Vitals capture
Scope: *://*/* albo per-domain jeśli chcesz tylko monitorować swoje strony.
// 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 });
// Raport po 5 sekundach od load + przy 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}`);
Co widzisz w Output Console
Po 5 sekundach od 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)
Każda metryka z visual status'em:
- ✓ = w good range (Google's "Good")
- ⚠ = w "Needs improvement" range
- ✗ = w "Poor" range
Use case 1 — Audit własnej strony bez Lighthouse
Klasyk: deploy na staging, otwórz tab, sprawdź czy Web Vitals są zielone. Z Lighthouse: 30s wait. Z JustZix: 5s, plus raportuje dla każdej kolejnej strony którą odwiedzisz w tej sesji.
Use case 2 — Compare against competition
Odwiedź konkurenta. Output Console pokazuje ich LCP/CLS/INP. Twoja strona — porównanie 1:1, same network conditions, same device.
Use case 3 — Monitor live podczas dev'a
Snap Output Console pane do prawego boku. Włącz dev server localhost. Każde F5 → JUSTZIX.log z nowymi vitals. Widzisz live jak twoje zmiany wpływają (np. lazy load image → LCP up, layout shift → CLS up).
Use case 4 — JSON export z BUTTON action
// Akcja 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.');
W reguły JS dodaj line: window.JZ_LATEST_VITALS = vitals; po każdym update. BUTTON ma teraz current snapshot do export do dokumentacji / Slack'a.
Pułapki
- LCP w Single-page apps. SPAs route change nie triggeruje nowego LCP measurement. Workaround: nasłuchuj popstate event i resetuj observers.
- CLS continues po load. Strony z lazy-load images mogą zwiększać CLS godzinami. Final CLS = wartość przy unload (beforeunload). Lighthouse zatrzymuje pomiar po 3-5s.
- INP wymaga rzeczywistej interakcji. Strona statyczna bez clicks → INP = null. Open page + scroll/click before reading metrics.
- TTFB = responseStart - requestStart, NIE includes DNS/connect. Full TTFB to
responseStart - fetchStart. Implementations vary. - PerformanceObserver buffered: true — łapie events zaszłe before observer init. Bez tego rejstr pre-init entries jest lost.
Co dalej
- Output Console deep-dive — gdzie JUSTZIX.log ląduje
- Debug GTM bez devów — siostrzany monitoring pattern
- window.JZ + JUSTZIX — programmatic API
Zainstaluj JustZix — zero-config Web Vitals monitor który ZAUWAŻY problem zanim user się zauważy.
Oceń ten wpis
Brak ocen — oceń jako pierwszy.