Uvod: Zašto je praćenje performansi kritično u 2026. godini
Hajde da budemo iskreni — web performanse odavno nisu samo "tehnički detalj". One su izravno povezane s korisničkim iskustvom, stopama konverzije i SEO rangiranjem. Google je još 2021. godine uveo Core Web Vitals kao službene signale rangiranja, a od tada su se metrike konstantno razvijale.
U 2026. godini, tri ključne metrike — Largest Contentful Paint (LCP), Interaction to Next Paint (INP) i Cumulative Layout Shift (CLS) — definiraju kvalitetu korisničkog iskustva na webu. I tu nema kompromisa.
No, optimizacija bez mjerenja je poput vožnje bez instrumentne ploče. Možete se nadati da sve radi kako treba, ali zapravo ne znate. Da biste stvarno poboljšali performanse svog web-mjesta, trebate sustavno pratiti te metrike, razumjeti njihove uzroke i reagirati na promjene u stvarnom vremenu.
U ovom vodiču detaljno ćemo obraditi sve alate i tehnike za praćenje web performansi — od nativnog Performance Observer API-ja, preko biblioteke web-vitals.js, do izgradnje vlastite RUM (Real User Monitoring) infrastrukture. Dakle, krenimo redom.
Performance API: Temelj modernog mjerenja performansi
Preglednici danas nude bogat skup API-ja za mjerenje performansi, poznat pod zajedničkim nazivom Performance API. Obuhvaća nekoliko specijaliziranih sučelja, i svako od njih ima svoju ulogu.
Navigation Timing API
Navigation Timing API pruža detaljne vremenske oznake za svaku fazu učitavanja stranice — od početka navigacije, preko DNS rezolucije i TLS rukovanja, do potpunog učitavanja DOM-a. Ove informacije su ključne za razumijevanje TTFB (Time to First Byte) i ukupnog vremena učitavanja.
// Pristup Navigation Timing podacima
const navigationEntry = performance.getEntriesByType('navigation')[0];
console.log('DNS lookup:', navigationEntry.domainLookupEnd - navigationEntry.domainLookupStart, 'ms');
console.log('TLS handshake:', navigationEntry.connectEnd - navigationEntry.secureConnectionStart, 'ms');
console.log('TTFB:', navigationEntry.responseStart - navigationEntry.requestStart, 'ms');
console.log('DOM Interactive:', navigationEntry.domInteractive, 'ms');
console.log('DOM Complete:', navigationEntry.domComplete, 'ms');
console.log('Ukupno učitavanje:', navigationEntry.loadEventEnd - navigationEntry.startTime, 'ms');
Svaka od ovih faza može otkriti specifične probleme. Visok DNS lookup? Trebate DNS prefetching. Visok TTFB? Vjerojatno imate problem sa serverom ili vam treba CDN.
Resource Timing API
Resource Timing API bilježi detaljne vremenske podatke za svaki resurs koji stranica učitava — skripte, stilove, slike, fontove i ostale resurse. Bez ovog API-ja, praktički je nemoguće identificirati uska grla u učitavanju.
// Analiza svih učitanih resursa
const resources = performance.getEntriesByType('resource');
// Pronađi najsporije resurse
const slowResources = resources
.map(r => ({
name: r.name,
duration: r.duration,
size: r.transferSize,
type: r.initiatorType
}))
.sort((a, b) => b.duration - a.duration)
.slice(0, 10);
console.table(slowResources);
// Ukupna veličina prenesenih podataka
const totalTransfer = resources.reduce((sum, r) => sum + r.transferSize, 0);
console.log('Ukupni prijenos:', (totalTransfer / 1024).toFixed(2), 'KB');
Paint Timing API
Paint Timing API bilježi kada preglednik prvi put iscrtava piksele na zaslon. Dvije ključne metrike ovdje su First Paint (FP) i First Contentful Paint (FCP).
// Dohvaćanje paint metrika
const paintEntries = performance.getEntriesByType('paint');
paintEntries.forEach(entry => {
console.log(`${entry.name}: ${entry.startTime.toFixed(2)} ms`);
});
// Ispisuje: "first-paint: 245.30 ms" i "first-contentful-paint: 312.50 ms"
PerformanceObserver: Asinkrono praćenje u stvarnom vremenu
Dok metode poput performance.getEntriesByType() dohvaćaju podatke u određenom trenutku, PerformanceObserver omogućuje asinkrono praćenje performansnih unosa kako se pojavljuju. To je ključna razlika — ne morate stalno prozivati (pollati) API, već se jednostavno registrirate za obavijesti.
Iskreno, kad sam prvi put počeo koristiti PerformanceObserver umjesto ručnog pollinga, razlika u čistoći koda bila je ogromna.
Osnovna upotreba PerformanceObservera
// Kreiranje observera za praćenje LCP-a
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
// Zadnji unos je konačni LCP kandidat
const lastEntry = entries[entries.length - 1];
console.log('LCP element:', lastEntry.element);
console.log('LCP vrijeme:', lastEntry.startTime.toFixed(2), 'ms');
console.log('LCP veličina:', lastEntry.size);
console.log('LCP URL:', lastEntry.url);
});
// Registracija za largest-contentful-paint s buffered zastavicom
lcpObserver.observe({
type: 'largest-contentful-paint',
buffered: true
});
Parametar buffered: true je iznimno važan — on omogućuje pristup performansnim unosima koji su se pojavili prije nego što je observer registriran. Bez te zastavice, propustili biste sve unose koji su se dogodili prije učitavanja vaše skripte za praćenje. A to se događa češće nego što biste mislili.
Praćenje INP (Interaction to Next Paint) metrike
INP mjeri odzivnost stranice na korisničke interakcije. Za razliku od bivše FID metrike koja je mjerila samo prvi unos, INP prati sve interakcije tijekom cijelog životnog vijeka stranice i izvještava o najgoroj od njih (uz izuzeće statističkih outliera). To čini INP puno relevantnijim pokazateljem stvarnog korisničkog iskustva.
// Praćenje svih korisničkih interakcija za INP
const inpObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
// Fokus na interakcije koje imaju interactionId
if (entry.interactionId) {
const inputDelay = entry.processingStart - entry.startTime;
const processingTime = entry.processingEnd - entry.processingStart;
const presentationDelay = entry.duration - (entry.processingEnd - entry.startTime);
console.log('Interakcija:', entry.name);
console.log(' Kašnjenje unosa:', inputDelay.toFixed(2), 'ms');
console.log(' Vrijeme obrade:', processingTime.toFixed(2), 'ms');
console.log(' Kašnjenje prezentacije:', presentationDelay.toFixed(2), 'ms');
console.log(' Ukupno trajanje:', entry.duration, 'ms');
}
});
});
inpObserver.observe({
type: 'event',
buffered: true,
durationThreshold: 16 // Prati sve interakcije dulje od 16ms
});
Razlika između tri komponente INP-a je ključna za dijagnostiku problema:
- Input Delay (kašnjenje unosa) — vrijeme od korisničke akcije do početka obrade event handlera. Ako je ovo visoko, glavna nit (main thread) je blokirana nečim drugim.
- Processing Time (vrijeme obrade) — trajanje samog event handlera. Visoka vrijednost znači da handler jednostavno radi previše posla.
- Presentation Delay (kašnjenje prezentacije) — vrijeme od završetka handlera do iscrtavanja na zaslon. Ovo ukazuje na skupe stilske kalkulacije ili layout operacije.
Praćenje CLS (Cumulative Layout Shift) metrike
CLS je metrika koju korisnici možda ne znaju po imenu, ali je definitivno osjećaju — to je onaj frustrirajući pomak sadržaja dok pokušavate nešto kliknuti.
// Praćenje pomaka rasporeda (layout shifts)
let clsValue = 0;
let sessionValue = 0;
let sessionEntries = [];
const clsObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
// Ignoriraj pomake uzrokovane korisničkim unosom
if (!entry.hadRecentInput) {
const firstEntry = sessionEntries[0];
const lastEntry = sessionEntries[sessionEntries.length - 1];
// Provjeri je li ovo dio trenutne sesije (gap < 1s, ukupno < 5s)
if (
sessionValue &&
entry.startTime - lastEntry.startTime < 1000 &&
entry.startTime - firstEntry.startTime < 5000
) {
sessionValue += entry.value;
sessionEntries.push(entry);
} else {
sessionValue = entry.value;
sessionEntries = [entry];
}
if (sessionValue > clsValue) {
clsValue = sessionValue;
console.log('Novi CLS:', clsValue.toFixed(4));
// Analiza elemenata koji uzrokuju pomake
entry.sources?.forEach(source => {
console.log(' Element:', source.node);
console.log(' Prethodni okvir:', source.previousRect);
console.log(' Trenutni okvir:', source.currentRect);
});
}
}
});
});
clsObserver.observe({ type: 'layout-shift', buffered: true });
Long Animation Frames (LoAF) API: Nova generacija dijagnostike
Evo nečeg što će vas oduševiti ako ste se ikad mučili s dijagnostikom sporih interakcija. Long Animation Frames API (LoAF) predstavlja značajan napredak u odnosu na stariji Long Tasks API. Dok je Long Tasks API mogao samo reći "hej, postojao je dugačak zadatak duži od 50 ms", LoAF pruža detaljnu atribuciju — točno koja skripta je uzrokovala dugi okvir, koliko je vremena potrošeno na stiliziranje, layout i iscrtavanje.
LoAF je isporučen u Chrome 123 i ključan je za dijagnostiku INP problema. U 2026. godini, kroz inicijativu Interop 2026, očekuje se šira podrška preglednika.
// Praćenje dugačkih animacijskih okvira
const loafObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log('Dugački okvir:', entry.duration.toFixed(2), 'ms');
console.log(' Vrijeme blokiranja:', entry.blockingDuration.toFixed(2), 'ms');
console.log(' Početak renderiranja:', entry.renderStart.toFixed(2), 'ms');
console.log(' Trajanje stila/layouta:', entry.styleAndLayoutStart.toFixed(2), 'ms');
// Atribucija skripti koje su uzrokovale dugački okvir
entry.scripts?.forEach(script => {
console.log(' Skripta:', script.sourceURL);
console.log(' Tip:', script.invokerType);
console.log(' Pozivatelj:', script.invoker);
console.log(' Trajanje:', script.duration.toFixed(2), 'ms');
console.log(' Prisilni stil/layout:', script.forcedStyleAndLayoutDuration.toFixed(2), 'ms');
});
});
});
loafObserver.observe({ type: 'long-animation-frame', buffered: true });
Posebno obratite pažnju na svojstvo forcedStyleAndLayoutDuration — ono ukazuje na layout thrashing. To je situacija u kojoj JavaScript čita layout svojstva i zatim modificira DOM, prisiljavajući preglednik na ponovni izračun layouta unutar istog okvira. Ovo je jedan od najčešćih uzroka loših INP rezultata, a bez LoAF-a bilo ga je gotovo nemoguće precizno dijagnosticirati.
Biblioteka web-vitals.js: Pojednostavljeno mjerenje
Okej, pisanje vlastitih PerformanceObservera za svaku metriku može biti prilično zamorno. Srećom, Googleova biblioteka web-vitals.js (trenutna verzija 5.x) pruža elegantno sučelje za mjerenje svih Core Web Vitals metrika. Biblioteka interno koristi PerformanceObserver API s ispravnim zastavicama i pokriva sve rubne slučajeve za koje biste inače trebali sati da ih pronađete.
Osnovna integracija
// Instalacija: npm install web-vitals
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals';
// Osnovno mjerenje s izvještavanjem
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // "good", "needs-improvement", ili "poor"
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType,
entries: metric.entries,
});
// Koristite navigator.sendBeacon za pouzdano slanje
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/analytics', body);
} else {
fetch('/api/analytics', {
body,
method: 'POST',
keepalive: true,
headers: { 'Content-Type': 'application/json' }
});
}
}
// Registracija callback-ova za sve metrike
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
Napredna upotreba s atribucijom
Verzija 4 biblioteke web-vitals uvela je podršku za atribucijske podatke, uključujući LoAF podatke za INP. Ovo je stvarno bilo revolucionarno za dijagnostiku — umjesto da samo znate da je INP visok, sada točno vidite koji element, handler i skripta su odgovorni.
// Korištenje atribucijskog modula za detaljnu dijagnostiku
import { onINP } from 'web-vitals/attribution';
onINP((metric) => {
console.log('INP vrijednost:', metric.value, 'ms');
console.log('Ocjena:', metric.rating);
const attribution = metric.attribution;
console.log('Tip interakcije:', attribution.interactionType);
console.log('Ciljni element:', attribution.interactionTarget);
console.log('Kašnjenje unosa:', attribution.inputDelay, 'ms');
console.log('Vrijeme obrade:', attribution.processingDuration, 'ms');
console.log('Kašnjenje prezentacije:', attribution.presentationDelay, 'ms');
// LoAF atribucija
if (attribution.longAnimationFrameEntries) {
attribution.longAnimationFrameEntries.forEach(loaf => {
loaf.scripts?.forEach(script => {
console.log('Skripta:', script.sourceURL);
console.log('Pozivatelj:', script.invoker);
});
});
}
});
RUM vs. sintetičko testiranje: Razumijevanje razlika
Postoje dva fundamentalno različita pristupa mjerenju web performansi. I da, za sveobuhvatnu strategiju trebate oba.
Sintetičko testiranje (Lab Data)
Sintetičko testiranje koristi kontrolirane uvjete — unaprijed definirani uređaj, mreža i lokacija — za pokretanje testova. Alati poput Lighthouse, WebPageTest i PageSpeed Insights spadaju u ovu kategoriju.
Prednosti:
- Ponovljivi rezultati za usporedbu prije i poslije optimizacija
- Testiranje prije puštanja u produkciju (staging okolina)
- Automatizacija u CI/CD pipelineu
- Detaljni dijagnostički podaci (waterfalli, traceovi)
Ograničenja:
- Ne reflektira stvarno korisničko iskustvo
- Simulira samo jedan profil uređaja/mreže
- Ne može uhvatiti INP jer nema stvarnih korisničkih interakcija
- Ne pokriva geografsku distribuciju korisnika
Real User Monitoring (RUM) — praćenje stvarnih korisnika
RUM prikuplja podatke od stvarnih korisnika koji posjećuju vaše web-mjesto. Stvarni uređaji, pravi preglednici, realne mrežne veze. Ovo je jedini način da vidite prave performanse koje vaši korisnici zaista doživljavaju.
Prednosti:
- Podaci iz stvarnog svijeta s pravim uređajima i mrežama
- Pokriva INP jer bilježi stvarne korisničke interakcije
- Geografska distribucija i demografska segmentacija
- Korelacija s poslovnim metrikama (konverzije, bounce rate)
Ograničenja:
- Potreban je promet za prikupljanje podataka
- Varijabilnost rezultata zbog različitih uvjeta
- Privatnost korisnika i GDPR usklađenost
- Dodatno opterećenje mreže za slanje podataka
Izgradnja vlastitog RUM sustava
Dok komercijalni alati poput DebugBear, SpeedCurve ili Datadog RUM nude gotova rješenja, ponekad želite nešto prilagođeno vašim specifičnim potrebama. Izgradnja vlastitog RUM sustava zapravo nije tako komplicirano kao što zvuči. Evo kako to napraviti korak po korak.
Korak 1: Klijentska skripta za prikupljanje
// rum-collector.js - Lagana skripta za prikupljanje metrika
(function() {
'use strict';
const ENDPOINT = '/api/rum/collect';
const SESSION_ID = crypto.randomUUID();
const PAGE_URL = window.location.pathname;
const metricsBuffer = [];
// Pomoćna funkcija za slanje podataka
function flushMetrics() {
if (metricsBuffer.length === 0) return;
const payload = {
sessionId: SESSION_ID,
pageUrl: PAGE_URL,
userAgent: navigator.userAgent,
connectionType: navigator.connection?.effectiveType || 'unknown',
deviceMemory: navigator.deviceMemory || null,
timestamp: new Date().toISOString(),
metrics: [...metricsBuffer]
};
metricsBuffer.length = 0;
if (navigator.sendBeacon) {
navigator.sendBeacon(ENDPOINT, JSON.stringify(payload));
} else {
fetch(ENDPOINT, {
method: 'POST',
body: JSON.stringify(payload),
keepalive: true
}).catch(() => {});
}
}
// Dodaj metriku u buffer
function recordMetric(name, value, extra = {}) {
metricsBuffer.push({ name, value, ...extra });
}
// Praćenje LCP-a
try {
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries[entries.length - 1];
recordMetric('LCP', lcp.startTime, {
element: lcp.element?.tagName,
url: lcp.url
});
}).observe({ type: 'largest-contentful-paint', buffered: true });
} catch (e) {}
// Praćenje CLS-a
try {
let clsValue = 0;
new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
recordMetric('CLS', clsValue);
}).observe({ type: 'layout-shift', buffered: true });
} catch (e) {}
// Praćenje INP-a
try {
let worstINP = 0;
new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.interactionId && entry.duration > worstINP) {
worstINP = entry.duration;
recordMetric('INP', worstINP, {
target: entry.target?.tagName,
type: entry.name
});
}
});
}).observe({ type: 'event', buffered: true, durationThreshold: 16 });
} catch (e) {}
// Praćenje TTFB-a i navigacijskih podataka
try {
new PerformanceObserver((list) => {
const nav = list.getEntries()[0];
recordMetric('TTFB', nav.responseStart - nav.requestStart);
recordMetric('DOMContentLoaded', nav.domContentLoadedEventEnd);
recordMetric('PageLoad', nav.loadEventEnd);
}).observe({ type: 'navigation', buffered: true });
} catch (e) {}
// Pošalji podatke kad korisnik napusti stranicu
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
flushMetrics();
}
});
// Fallback: pošalji nakon 30 sekundi
setTimeout(flushMetrics, 30000);
})();
Korak 2: Serverski endpoint za primanje podataka
// server/rum-endpoint.js (Node.js / Express primjer)
const express = require('express');
const router = express.Router();
router.post('/api/rum/collect', express.json(), (req, res) => {
const { sessionId, pageUrl, userAgent, connectionType, metrics, timestamp } = req.body;
// Validacija ulaznih podataka
if (!sessionId || !pageUrl || !Array.isArray(metrics)) {
return res.status(400).send('Invalid payload');
}
// Pohrana u bazu podataka (primjer s PostgreSQL)
metrics.forEach(metric => {
db.query(
`INSERT INTO rum_metrics (session_id, page_url, metric_name, metric_value,
user_agent, connection_type, extra_data, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
[sessionId, pageUrl, metric.name, metric.value,
userAgent, connectionType, JSON.stringify(metric), timestamp]
);
});
res.status(204).send();
});
module.exports = router;
Korak 3: Analiza i vizualizacija prikupljenih podataka
Ključni aspekt svakog RUM sustava je analiza prikupljenih podataka. I ovdje mnogi griješe — fokusiraju se na prosječne vrijednosti. Nemojte to raditi. Umjesto toga, fokusirajte se na percentile, posebno P75 (75. percentil) koji Google koristi za Core Web Vitals evaluaciju u CrUX izvješćima.
-- SQL upit za izračun percentila Core Web Vitals metrika
SELECT
metric_name,
COUNT(*) as samples,
ROUND(PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY metric_value)::numeric, 2) AS p50,
ROUND(PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY metric_value)::numeric, 2) AS p75,
ROUND(PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY metric_value)::numeric, 2) AS p90,
ROUND(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY metric_value)::numeric, 2) AS p95,
ROUND(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY metric_value)::numeric, 2) AS p99
FROM rum_metrics
WHERE created_at >= NOW() - INTERVAL '7 days'
AND metric_name IN ('LCP', 'INP', 'CLS', 'TTFB')
GROUP BY metric_name
ORDER BY metric_name;
Soft Navigations API: Mjerenje performansi u SPA aplikacijama
Jedna od najvećih praznina u praćenju web performansi dugo je bila mjerenje performansi u Single Page Application (SPA) aplikacijama. Kad korisnik klikne na link u SPA-u, ne dolazi do "tvrde" navigacije — JavaScript dinamički ažurira sadržaj i URL bez punog osvježavanja stranice. Te "meke navigacije" bile su potpuno nevidljive alatima poput PageSpeed Insights i CrUX-a.
Chrome 139, objavljen u srpnju 2025., donio je Soft Navigations API kao eksperimentalnu značajku (origin trial). Konačno možemo detektirati meke navigacije i mjeriti Core Web Vitals za svaku od njih.
Kako funkcionira Soft Navigations API
Chrome definira meku navigaciju kroz tri kriterija:
- Navigacija je pokrenuta korisničkom akcijom (klik, tipka)
- Navigacija rezultira vidljivom promjenom URL-a i promjenom u povijesti preglednika
- Navigacija rezultira promjenom DOM-a
// Praćenje mekih navigacija
const softNavObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log('Meka navigacija detektirana!');
console.log(' URL:', entry.name);
console.log(' Navigation ID:', entry.navigationId);
console.log(' Početak:', entry.startTime);
});
});
softNavObserver.observe({ type: 'soft-navigation', buffered: true });
Najvažnije od svega — svaki performansni unos (LCP, CLS, INP) sada sadrži atribut navigationId koji ga povezuje s odgovarajućom navigacijom. To znači da možete izračunati Core Web Vitals za svaku "stranicu" unutar SPA aplikacije. Za one koji rade s React, Angular ili Vue aplikacijama, ovo je bila dugo očekivana značajka.
Napomena: Soft Navigations origin trial traje do ožujka 2026. i trenutno nema utjecaja na CrUX podatke. No, rano usvajanje praćenja mekih navigacija daje vam prednost u razumijevanju performansi vaše SPA aplikacije.
Podrška preglednika za metrike performansi u 2026. godini
Značajan pomak u ekosustavu web performansi u 2025. i 2026. godini je proširenje podrške preglednika izvan Chromea. I to je odlična vijest. Kroz projekt Interop 2025, Firefox i Safari počeli su implementirati Core Web Vitals metrike:
- Firefox 144 (listopad 2025.) — dodana podrška za INP (Interaction to Next Paint)
- Safari — LCP i INP dolaze u 2026. godini
- Interop 2026 — LoAF API i fetchLater API su među prioritetnim temama za međupregledničku interoperabilnost
Za RUM implementacije ovo je izuzetno važno jer znači da ćete moći prikupljati Core Web Vitals podatke od svih korisnika, ne samo onih koji koriste Chrome ili Chromium preglednike.
Strategija praćenja performansi: Praktični okvir
Na temelju svega što smo prošli, evo praktičnog okvira za implementaciju sveobuhvatnog sustava praćenja performansi. Ovo je pristup koji se u praksi pokazao kao najučinkovitiji.
1. Definirajte ciljeve i pragove
Postavite jasne pragove za svaku metriku, temeljene na Googleovim preporukama:
- LCP: Dobar < 2.5s, Potrebno poboljšanje 2.5-4.0s, Loš > 4.0s
- INP: Dobar < 200ms, Potrebno poboljšanje 200-500ms, Loš > 500ms
- CLS: Dobar < 0.1, Potrebno poboljšanje 0.1-0.25, Loš > 0.25
- TTFB: Dobar < 800ms
- FCP: Dobar < 1.8s
2. Implementirajte višestruki pristup praćenju
// Konfiguracija sustava za praćenje performansi
const performanceConfig = {
// RUM konfiguracija
rum: {
enabled: true,
sampleRate: 0.1, // Prikupljaj od 10% korisnika
endpoint: '/api/rum/collect',
metrics: ['LCP', 'INP', 'CLS', 'TTFB', 'FCP'],
dimensions: ['page', 'device', 'connection', 'country']
},
// Sintetičko testiranje u CI/CD
synthetic: {
tool: 'lighthouse-ci',
budgets: {
LCP: 2500,
INP: 200,
CLS: 0.1,
TotalBlockingTime: 300,
SpeedIndex: 3400
}
},
// Alarmi
alerts: {
channels: ['slack', 'email'],
conditions: [
{ metric: 'LCP_p75', threshold: 3000, window: '1h' },
{ metric: 'INP_p75', threshold: 300, window: '1h' },
{ metric: 'CLS_p75', threshold: 0.15, window: '1h' },
{ metric: 'error_rate', threshold: 5, window: '15m' }
]
}
};
3. Segmentirajte podatke za dublje uvide
Agregirane metrike skrivaju stvarnu sliku. Morate segmentirati podatke po:
- Tipu stranice — početna stranica, stranica proizvoda, blog post
- Tipu uređaja — desktop, mobilni, tablet
- Tipu mreže — 4G, 3G, WiFi
- Geografskoj lokaciji — različite regije imaju različite latencije
- Pregledniku — Chrome, Firefox, Safari
-- Primjer segmentirane analize performansi po tipu uređaja
SELECT
CASE
WHEN user_agent LIKE '%Mobile%' THEN 'Mobilni'
WHEN user_agent LIKE '%Tablet%' THEN 'Tablet'
ELSE 'Desktop'
END AS device_type,
metric_name,
COUNT(*) as samples,
ROUND(PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY metric_value)::numeric, 2) AS p75
FROM rum_metrics
WHERE created_at >= NOW() - INTERVAL '24 hours'
AND metric_name IN ('LCP', 'INP', 'CLS')
GROUP BY device_type, metric_name
ORDER BY device_type, metric_name;
4. Uspostavite performansne budgete
Performansni budgeti su pragovi koji se provjeravaju u CI/CD pipelineu i sprečavaju regresije prije nego što dođu do produkcije. Ovo je nešto što bi svaki tim trebao implementirati — jer bez toga, performanse se neizbježno degradiraju s vremenom.
// lighthouserc.js - Lighthouse CI konfiguracija s budgetima
module.exports = {
ci: {
collect: {
url: [
'https://www.example.com/',
'https://www.example.com/products',
'https://www.example.com/blog'
],
numberOfRuns: 3,
settings: {
preset: 'desktop'
}
},
assert: {
assertions: {
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'total-blocking-time': ['error', { maxNumericValue: 300 }],
'speed-index': ['warn', { maxNumericValue: 3400 }],
'resource-summary:script:size': ['error', { maxNumericValue: 300000 }],
'resource-summary:image:size': ['error', { maxNumericValue: 500000 }]
}
},
upload: {
target: 'temporary-public-storage'
}
}
};
Privatnost i GDPR razmatranja
Ovo je tema o kojoj se premalo govori, ali je izuzetno važna. Pri implementaciji RUM sustava morate uzeti u obzir privatnost korisnika i regulatorne zahtjeve poput GDPR-a:
- Minimizirajte podatke — prikupljajte samo metrike performansi, ne osobne podatke
- Anonimizirajte IP adrese — ne pohranjujte pune IP adrese
- Uzorkovanje (sampling) — ne trebate podatke od 100% korisnika; 10-25% daje statistički značajne rezultate
- Pristanak korisnika — RUM skripte mogu biti klasificirane kao analitički kolačići i trebaju pristanak prema GDPR-u
- Politika zadržavanja podataka — definirajte koliko dugo čuvate RUM podatke (30-90 dana je obično sasvim dovoljno)
Napredni savjeti i česte pogreške
Korištenje fetchLater API-ja za pouzdanije slanje podataka
Jedan od frustrirajućih problema s RUM prikupljanjem je gubitak podataka kad korisnik zatvori tab. Koliko puta ste se pitali zašto vam fali dio metrika? API fetchLater(), koji je u procesu standardizacije kroz Interop 2026, omogućuje zakazivanje mrežnog zahtjeva koji će se izvršiti čak i nakon što se stranica zatvori.
// fetchLater API - pouzdano slanje metrika
if ('fetchLater' in window) {
// Zakaži slanje podataka kad se stranica zatvori
fetchLater('/api/rum/collect', {
method: 'POST',
body: JSON.stringify(metricsPayload),
activateAfter: 0 // Pošalji odmah kad se stranica zatvori
});
}
Česte pogreške pri praćenju performansi
Nakon godina rada s web performansama, evo grešaka koje vidim iznova i iznova:
- Korištenje prosjeka umjesto percentila — prosjek skriva distribuciju. Uvijek koristite P75 ili P90.
- Ignoriranje mobilnih korisnika — mobilni uređaji često imaju 3-5x lošije performanse od desktopa. A obično čine većinu vašeg prometa.
- Praćenje samo početnog učitavanja — korisnici provode većinu vremena na stranici nakon učitavanja. Pratite INP i LoAF za razumijevanje iskustva tijekom interakcije.
- Pretjerano velika skripta za praćenje — ironično, skripta za praćenje performansi može sama pogoršati performanse. Držite je ispod 5 KB gzipiranom.
- Nedostatak konteksta — sama metrika bez konteksta (stranica, uređaj, mreža) je beskorisna za dijagnostiku.
- Ignoriranje CrUX podataka — Chrome User Experience Report (CrUX) pruža besplatne RUM podatke od Chrome korisnika. Usporedite svoje RUM podatke s CrUX-om za validaciju.
Zaključak: Od mjerenja do akcije
Praćenje web performansi u 2026. godini zahtijeva višestruki pristup koji kombinira sintetičko testiranje, Real User Monitoring i duboku dijagnostiku pomoću modernih API-ja preglednika. Evo ključnih stvari koje trebate zapamtiti:
- Koristite PerformanceObserver API za granularno, asinkrono praćenje metrika bez blokiranja glavne niti.
- Implementirajte web-vitals.js s atribucijskim modulom za brzi početak i detaljnu dijagnostiku INP problema putem LoAF podataka.
- Kombinirajte RUM i sintetičko testiranje — RUM za razumijevanje stvarnog korisničkog iskustva, sintetičko testiranje za sprečavanje regresija u CI/CD-u.
- Pratite širenje podrške preglednika — Firefox i Safari usvajaju Core Web Vitals metrike, što će povećati pokrivenost vaših RUM podataka.
- Eksperimentirajte sa Soft Navigations API-jem za mjerenje performansi u SPA aplikacijama.
- Segmentirajte podatke po stranici, uređaju, mreži i geografiji za dublje uvide.
- Postavite performansne budgete u CI/CD pipelineu za sprečavanje regresija.
Na kraju, zapamtite — podaci bez akcije su beskorisni. Uspostavite redoviti proces pregleda performansnih podataka, definirajte vlasništvo nad metrikama unutar tima i osigurajte da svaki uvid iz praćenja rezultira konkretnom optimizacijom. Jer na kraju dana, cilj nije samo imati lijepe grafove — cilj je da vaši korisnici imaju bolje iskustvo.