Beheers de Resource Timing API om frontend prestaties te diagnosticeren en optimaliseren. Leer hoe u de laadtijd van elke resource meet, van DNS-lookups tot contentdownloads.
Frontend Prestaties Ontgrendelen: Een Diepgaande Analyse van de Resource Timing API
In de wereld van webontwikkeling is snelheid niet zomaar een feature; het is een fundamentele vereiste voor een positieve gebruikerservaring. Een traag ladende website kan leiden tot hogere bounce rates, lagere gebruikersbetrokkenheid en uiteindelijk een negatieve impact op bedrijfsdoelstellingen. Hoewel tools zoals Lighthouse en WebPageTest van onschatbare waarde zijn voor diagnostiek op hoog niveau, vertegenwoordigen ze vaak een enkele, synthetische test. Om de prestaties voor een wereldwijd publiek echt te begrijpen en te optimaliseren, moeten we de ervaring van echte gebruikers meten, op hun apparaten en op hun netwerken. Dit is waar Real User Monitoring (RUM) een rol speelt, en een van de krachtigste tools daarvoor is de Resource Timing API.
Deze uitgebreide gids neemt u mee op een diepgaande verkenning van de Resource Timing API. We zullen onderzoeken wat het is, hoe u het kunt gebruiken en hoe u de gedetailleerde data kunt omzetten in bruikbare inzichten die de laadprestaties van uw applicatie drastisch kunnen verbeteren. Of u nu een ervaren frontend engineer bent of net begint aan uw reis naar prestatie-optimalisatie, dit artikel zal u voorzien van de kennis om de netwerkprestaties van elke afzonderlijke asset op uw pagina te ontleden en te begrijpen.
Wat is de Resource Timing API?
De Resource Timing API is een browsergebaseerde JavaScript API die gedetailleerde netwerktimingdata levert voor elke resource die een webpagina downloadt. Zie het als een microscopische lens voor de netwerkactiviteit van uw pagina. Voor elke afbeelding, script, stylesheet, lettertype en API-aanroep (via `fetch` of `XMLHttpRequest`) legt deze API een tijdstempel met hoge resolutie vast voor elke fase van de netwerkaanvraag.
Het maakt deel uit van een grotere suite van Performance API's, die samenwerken om een holistisch beeld te geven van de prestaties van uw applicatie. Terwijl de Navigation Timing API zich richt op de levenscyclus van het hoofddocument, zoomt de Resource Timing API in op alle afhankelijke resources die het hoofddocument opvraagt.
Waarom is het zo belangrijk?
- Granulariteit: Het gaat verder dan een enkele 'pagina laadtijd'-metriek. U kunt precies zien hoe lang de DNS-lookup, de TCP-verbinding en de contentdownload duurden voor een specifiek script van derden of een kritieke hero-afbeelding.
- Echte Gebruikersdata: In tegenstelling tot lab-gebaseerde tools, draait deze API in de browsers van uw gebruikers. Hierdoor kunt u prestatiegegevens verzamelen van een breed scala aan netwerkomstandigheden, apparaten en geografische locaties, wat u een waarheidsgetrouw beeld geeft van uw wereldwijde gebruikerservaring.
- Bruikbare Inzichten: Door deze gegevens te analyseren, kunt u specifieke knelpunten identificeren. Is een analysescript van een derde partij traag om verbinding te maken? Presteert uw CDN ondermaats in een bepaalde regio? Zijn uw afbeeldingen te groot? De Resource Timing API levert het bewijs dat nodig is om deze vragen met vertrouwen te beantwoorden.
De Anatomie van een Resource Lading: De Tijdlijn Ontleden
De kern van de Resource Timing API is het `PerformanceResourceTiming`-object. Voor elke geladen resource creƫert de browser een van deze objecten, dat een schat aan timing- en grootte-informatie bevat. Om deze objecten te begrijpen, is het nuttig om het laadproces te visualiseren als een watervalgrafiek, waarbij elke stap de vorige volgt.
Laten we de belangrijkste eigenschappen van een `PerformanceResourceTiming`-object uiteenzetten. Alle tijdwaarden zijn tijdstempels met hoge resolutie, gemeten in milliseconden vanaf het begin van de paginanavigatie (`performance.timeOrigin`).
startTime -> fetchStart -> domainLookupStart -> domainLookupEnd -> connectStart -> connectEnd -> requestStart -> responseStart -> responseEnd
Belangrijkste Timing-eigenschappen
name: De URL van de resource. Dit is uw primaire identificatie.entryType: Een string die het type performance-entry aangeeft. Voor onze doeleinden zal dit altijd "resource" zijn.initiatorType: Dit is ongelooflijk nuttig voor foutopsporing. Het vertelt u hoe de resource werd aangevraagd. Veelvoorkomende waarden zijn 'img', 'link' (voor CSS), 'script', 'css' (voor resources geladen vanuit CSS zoals `@import`), 'fetch' en 'xmlhttprequest'.duration: De totale tijd die de resource in beslag nam, berekend alsresponseEnd - startTime. Dit is de metriek op het hoogste niveau voor een enkele resource.startTime: Het tijdstempel direct voordat het ophalen van de resource begint.fetchStart: Het tijdstempel net voordat de browser de resource begint op te halen. Het kan caches controleren (HTTP-cache, Service Worker-cache) voordat het doorgaat naar het netwerk. Als de resource vanuit een cache wordt geleverd, zullen veel van de volgende timingwaarden nul zijn.domainLookupStart&domainLookupEnd: Deze markeren het begin en einde van de DNS (Domain Name System)-lookup. De duur (domainLookupEnd - domainLookupStart) is de tijd die nodig was om de domeinnaam om te zetten naar een IP-adres. Een hoge waarde hier kan wijzen op een trage DNS-provider.connectStart&connectEnd: Deze markeren het begin en einde van het opzetten van een verbinding met de server. Voor HTTP is dit de TCP three-way handshake. De duur (connectEnd - connectStart) is uw TCP-verbindingstijd.secureConnectionStart: Als de resource via HTTPS wordt geladen, markeert dit tijdstempel het begin van de SSL/TLS-handshake. De duur (connectEnd - secureConnectionStart) vertelt u hoe lang de encryptieonderhandeling duurde. Trage TLS-handshakes kunnen een teken zijn van een serverfoutconfiguratie of netwerklatentie.requestStart: Het tijdstempel net voordat de browser de daadwerkelijke HTTP-aanvraag voor de resource naar de server stuurt. De tijd tussenconnectEndenrequestStartwordt vaak de "request queuing"-tijd genoemd, waarin de browser wacht op een beschikbare verbinding.responseStart: Het tijdstempel waarop de browser de allereerste byte van het antwoord van de server ontvangt. De duur (responseStart - requestStart) is de beroemde Time to First Byte (TTFB). Een hoge TTFB is bijna altijd een indicator van een traag backend-proces of server-side latentie.responseEnd: Het tijdstempel waarop de laatste byte van de resource is ontvangen, waarmee de aanvraag succesvol wordt afgesloten. De duur (responseEnd - responseStart) vertegenwoordigt de downloadtijd van de content.
Resourcegrootte-eigenschappen
Het begrijpen van de resourcegrootte is net zo belangrijk als het begrijpen van de timing. De API biedt drie belangrijke metrieken:
transferSize: De grootte in bytes van de resource die over het netwerk is overgedragen, inclusief headers en de gecomprimeerde responsbody. Als de resource vanuit een cache werd geleverd, zal dit vaak 0 zijn. Dit is het getal dat direct van invloed is op het data-abonnement en de netwerktijd van de gebruiker.encodedBodySize: De grootte in bytes van de payload-body *na* compressie (bijv. Gzip of Brotli) maar *vóór* decompressie. Dit helpt u de grootte van de payload zelf te begrijpen, los van de headers.decodedBodySize: De grootte in bytes van de payload-body in zijn ongecomprimeerde, originele vorm. Het vergelijken hiervan metencodedBodySizeonthult de effectiviteit van uw compressiestrategie. Als deze twee getallen heel dicht bij elkaar liggen voor een op tekst gebaseerde asset (zoals JS, CSS of HTML), werkt uw compressie waarschijnlijk niet correct.
Server Timing
Een van de krachtigste integraties met de Resource Timing API is de `serverTiming`-eigenschap. Uw backend kan prestatiemetrieken verzenden in een speciale HTTP-header (`Server-Timing`), en deze metrieken verschijnen in de `serverTiming`-array van het corresponderende `PerformanceResourceTiming`-object. Dit overbrugt de kloof tussen frontend- en backend-prestatiemonitoring, waardoor u databasequerytijden of API-verwerkingsvertragingen direct in uw frontend-data kunt zien.
Een backend kan bijvoorbeeld deze header sturen:
Server-Timing: db;dur=53, api;dur=47.2, cache;desc="HIT"
Deze data zou beschikbaar zijn in de `serverTiming`-eigenschap, waardoor u een hoge TTFB kunt correleren met een specifiek traag proces op de backend.
Hoe u Resource Timing Data kunt benaderen in JavaScript
Nu we de beschikbare data begrijpen, laten we eens kijken naar de praktische manieren om deze te verzamelen met JavaScript. Er zijn twee primaire methoden.
Methode 1: `performance.getEntriesByType('resource')`
Dit is de eenvoudigste manier om te beginnen. Deze methode retourneert een array van alle `PerformanceResourceTiming`-objecten voor resources die op het moment van de aanroep al klaar zijn met laden op de pagina.
// Wacht tot de pagina is geladen om te zorgen dat de meeste resources worden vastgelegd
window.addEventListener('load', () => {
const resources = performance.getEntriesByType('resource');
resources.forEach((resource) => {
console.log(`Resource geladen: ${resource.name}`);
console.log(` - Totale tijd: ${resource.duration.toFixed(2)}ms`);
console.log(` - Initiator: ${resource.initiatorType}`);
console.log(` - Overdrachtsgrootte: ${resource.transferSize} bytes`);
});
});
Beperking: Deze methode is een momentopname. Als u deze te vroeg aanroept, mist u resources die nog niet geladen zijn. Als uw applicatie dynamisch resources laadt lang na de initiƫle paginalading, zou u deze methode herhaaldelijk moeten pollen, wat inefficiƫnt is.
Methode 2: `PerformanceObserver` (De aanbevolen aanpak)
De `PerformanceObserver` is een modernere, robuustere en performantere manier om performance-entries te verzamelen. In plaats van dat u data pollt, pusht de browser nieuwe entries naar uw observer-callback zodra ze beschikbaar komen.
Hier is waarom het beter is:
- Asynchroon: Het blokkeert de main thread niet.
- Uitgebreid: Het kan entries vastleggen vanaf het allereerste begin van de paginalading, waardoor racecondities worden vermeden waarbij een script wordt uitgevoerd nadat een resource al is geladen.
- Efficiƫnt: Het vermijdt de noodzaak van pollen met `setTimeout` of `setInterval`.
Hier is een standaard implementatie:
try {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
// Verwerk elke resource-entry zodra deze binnenkomt
if (entry.entryType === 'resource') {
console.log(`Resource geobserveerd: ${entry.name}`);
console.log(` - Time to First Byte (TTFB): ${(entry.responseStart - entry.requestStart).toFixed(2)}ms`);
}
});
});
// Begin met observeren voor 'resource'-entries.
// De 'buffered'-vlag zorgt ervoor dat we entries ontvangen die al geladen waren voordat onze observer werd aangemaakt.
observer.observe({ type: 'resource', buffered: true });
// U kunt het observeren later stoppen indien nodig
// observer.disconnect();
} catch (e) {
console.error('PerformanceObserver wordt niet ondersteund in deze browser.');
}
De optie buffered: true is cruciaal. Het vertelt de observer om onmiddellijk alle `resource`-entries die al in de performance entry buffer van de browser staan, te verzenden, zodat u vanaf het begin een volledige lijst krijgt.
De Performance Buffer Beheren
Browsers hebben een standaardlimiet voor het aantal resource timing-entries dat ze opslaan (meestal 150). Op zeer complexe pagina's kan deze buffer vol raken. Wanneer dit gebeurt, vuurt de browser een `resourcetimingbufferfull`-gebeurtenis af en worden er geen nieuwe entries meer toegevoegd.
U kunt dit beheren door:
- De buffergrootte te verhogen: Gebruik `performance.setResourceTimingBufferSize(limit)` om een hogere limiet in te stellen, bijvoorbeeld 300.
- De buffer te legen: Gebruik `performance.clearResourceTimings()` nadat u de entries hebt verwerkt om ruimte te maken voor nieuwe.
performance.addEventListener('resourcetimingbufferfull', () => {
console.warn('Resource Timing-buffer is vol. Wordt geleegd...');
// Verwerk eerst bestaande entries van uw observer
// Leeg vervolgens de buffer
performance.clearResourceTimings();
// U moet mogelijk de buffergrootte opnieuw aanpassen als dit vaak gebeurt
// performance.setResourceTimingBufferSize(500);
});
Praktische Gebruiksscenario's en Bruikbare Inzichten
Het verzamelen van data is slechts de eerste stap. De echte waarde ligt in het omzetten van die data in bruikbare verbeteringen. Laten we enkele veelvoorkomende prestatieproblemen onderzoeken en hoe de Resource Timing API u helpt deze op te lossen.
Gebruiksscenario 1: Trage Scripts van Derden Identificeren
Het Probleem: Scripts van derden voor analytics, advertenties, klantenservice-widgets en A/B-testen zijn beruchte prestatiedoders. Ze kunnen traag laden, de weergave blokkeren en zelfs instabiliteit veroorzaken.
De Oplossing: Gebruik de Resource Timing API om de impact van deze scripts op uw echte gebruikers te isoleren en te meten.
const observer = new PerformanceObserver((list) => {
const thirdPartyScripts = list.getEntries().filter(entry =>
entry.initiatorType === 'script' &&
!entry.name.startsWith(window.location.origin)
);
thirdPartyScripts.forEach(script => {
if (script.duration > 200) { // Stel een drempelwaarde in, bijv. 200ms
console.warn(`Traag script van derde partij gedetecteerd: ${script.name}`, {
duration: `${script.duration.toFixed(2)}ms`,
transferSize: `${script.transferSize} bytes`
});
// In een echte RUM-tool zou u deze data naar uw analytics backend sturen.
}
});
});
observer.observe({ type: 'resource', buffered: true });
Bruikbare Inzichten:
- Hoge Duur: Als een script consequent een lange duur heeft, overweeg dan of het echt noodzakelijk is. Kan de functionaliteit ervan worden vervangen door een performanter alternatief?
- Laadstrategie: Wordt het script synchroon geladen? Gebruik de attributen `async` of `defer` op de `<script>`-tag om te voorkomen dat het de weergave van de pagina blokkeert.
- Selectief Hosten: Kan het script voorwaardelijk worden geladen, alleen op pagina's waar het absoluut noodzakelijk is?
Gebruiksscenario 2: Optimaliseren van Afbeeldingslevering
Het Probleem: Grote, niet-geoptimaliseerde afbeeldingen zijn een van de meest voorkomende oorzaken van trage paginaladingen, vooral op mobiele apparaten met beperkte bandbreedte.
De Oplossing: Filter resource-entries op `initiatorType: 'img'` en analyseer hun grootte en laadtijden.
// ... binnen een PerformanceObserver-callback ...
list.getEntries()
.filter(entry => entry.initiatorType === 'img')
.forEach(image => {
const downloadTime = image.responseEnd - image.responseStart;
// Een grote afbeelding kan een hoge downloadtijd en een grote transferSize hebben
if (downloadTime > 500 || image.transferSize > 100000) { // 500ms of 100KB
console.log(`Mogelijk probleem met grote afbeelding: ${image.name}`, {
downloadTime: `${downloadTime.toFixed(2)}ms`,
transferSize: `${(image.transferSize / 1024).toFixed(2)} KB`
});
}
});
Bruikbare Inzichten:
- Hoge `transferSize` en `downloadTime`: Dit is een duidelijk signaal dat de afbeelding te groot is. Optimaliseer deze door moderne formaten zoals WebP of AVIF te gebruiken, deze correct te comprimeren en de afmetingen aan te passen aan de weergavegrootte.
- Gebruik `srcset`: Implementeer responsive afbeeldingen met het `srcset`-attribuut om verschillende afbeeldingsgroottes te serveren op basis van de viewport van de gebruiker.
- Lazy Loading: Voor afbeeldingen onder de vouw, gebruik `loading="lazy"` om het laden uit te stellen totdat de gebruiker ze in beeld scrolt.
Gebruiksscenario 3: Netwerkknelpunten Diagnosticeren
Het Probleem: Soms is het probleem niet de resource zelf, maar het netwerkpad ernaartoe. Trage DNS, latente verbindingen of overbelaste servers kunnen de prestaties allemaal verslechteren.
De Oplossing: Deel de `duration` op in zijn componentfasen om de bron van de vertraging te achterhalen.
function analyzeNetworkPhases(resource) {
const dnsTime = resource.domainLookupEnd - resource.domainLookupStart;
const tcpTime = resource.connectEnd - resource.connectStart;
const ttfb = resource.responseStart - resource.requestStart;
const downloadTime = resource.responseEnd - resource.responseStart;
console.log(`Analyse voor ${resource.name}`);
if (dnsTime > 50) console.warn(` - Hoge DNS-tijd: ${dnsTime.toFixed(2)}ms`);
if (tcpTime > 100) console.warn(` - Hoge TCP-verbindingstijd: ${tcpTime.toFixed(2)}ms`);
if (ttfb > 300) console.warn(` - Hoge TTFB (trage server): ${ttfb.toFixed(2)}ms`);
if (downloadTime > 500) console.warn(` - Trage contentdownload: ${downloadTime.toFixed(2)}ms`);
}
// ... roep analyzeNetworkPhases(entry) aan binnen uw observer ...
Bruikbare Inzichten:
- Hoge DNS-tijd: Uw DNS-provider is mogelijk traag. Overweeg over te stappen op een snellere, wereldwijde provider. U kunt ook `` gebruiken om de DNS voor kritieke domeinen van derden van tevoren op te lossen.
- Hoge TCP-tijd: Dit duidt op latentie bij het opzetten van de verbinding. Een Content Delivery Network (CDN) kan dit verminderen door assets te serveren vanaf een locatie die geografisch dichter bij de gebruiker ligt. Het gebruik van `` kan zowel de DNS-lookup als de TCP-handshake vroegtijdig uitvoeren.
- Hoge TTFB: Dit wijst op een trage backend. Werk samen met uw backend-team om databasequery's te optimaliseren, server-side caching te verbeteren of serverhardware te upgraden. De `Server-Timing`-header is hier uw beste vriend.
- Hoge Downloadtijd: Dit is een functie van de resourcegrootte en netwerkbandbreedte. Optimaliseer de asset (comprimeren, minificeren) of gebruik een CDN om de doorvoer te verbeteren.
Beperkingen en Overwegingen
Hoewel de Resource Timing API ongelooflijk krachtig is, heeft deze enkele belangrijke beperkingen waar u rekening mee moet houden.
Cross-Origin Resources en de `Timing-Allow-Origin`-Header
Om veiligheidsredenen beperken browsers de timingdetails die beschikbaar zijn voor resources die vanaf een andere origin (domein, protocol of poort) dan uw hoofdpagina worden geladen. Standaard zullen voor een cross-origin resource de meeste timing-eigenschappen zoals `redirectStart`, `domainLookupStart`, `connectStart`, `requestStart`, `responseStart`, en grootte-eigenschappen zoals `transferSize` nul zijn.
Om deze details bloot te leggen, moet de server die de resource host de `Timing-Allow-Origin` (TAO) HTTP-header meesturen. Bijvoorbeeld:
Timing-Allow-Origin: * (Staat elke origin toe om de timingdetails te zien)
Timing-Allow-Origin: https://www.your-website.com (Staat alleen uw website toe)
Dit is cruciaal wanneer u met uw eigen CDN's of API's op verschillende subdomeinen werkt. Zorg ervoor dat ze zijn geconfigureerd om de TAO-header te verzenden, zodat u volledige prestatie-inzichten kunt krijgen.
Browserondersteuning
De Resource Timing API, inclusief `PerformanceObserver`, wordt breed ondersteund door alle moderne browsers (Chrome, Firefox, Safari, Edge). Voor oudere browsers is deze mogelijk niet beschikbaar. Wikkel uw code altijd in een `try...catch`-blok of controleer op het bestaan van `window.PerformanceObserver` voordat u het gebruikt, om fouten op verouderde clients te voorkomen.
Conclusie: Van Data naar Beslissingen
De Resource Timing API is een essentieel instrument in de gereedschapskist van de moderne webontwikkelaar. Het demystificeert de netwerk-waterval en levert de ruwe, gedetailleerde data die nodig is om van vage klachten als "de site is traag" over te stappen op precieze, data-gedreven diagnoses zoals "onze chatwidget van derden heeft een TTFB van 400ms voor gebruikers in Zuidoost-Aziƫ."
Door `PerformanceObserver` te gebruiken om echte gebruikersdata te verzamelen en de volledige levenscyclus van elke resource te analyseren, kunt u:
- Derden verantwoordelijk houden voor hun prestaties.
- De effectiviteit van uw CDN- en caching-strategieƫn over de hele wereld valideren.
- Te grote afbeeldingen en niet-geoptimaliseerde assets vinden en herstellen.
- Frontend-vertragingen correleren met backend-verwerkingstijden.
De weg naar een sneller web is een continue reis. Begin vandaag nog. Open de ontwikkelaarsconsole van uw browser, voer de codefragmenten uit dit artikel uit op uw eigen site en begin met het verkennen van de rijke prestatiedata die al die tijd op u heeft gewacht. Door te meten wat ertoe doet, kunt u snellere, veerkrachtigere en aangenamere ervaringen bouwen voor al uw gebruikers, waar ter wereld ze zich ook bevinden.