Meistern Sie die Resource Timing API zur Diagnose und Optimierung der Frontend-Performance. Erfahren Sie, wie Sie jede Ressourcen-Ladezeit messen, von DNS-Lookups bis zum Content-Download.
Frontend-Performance entschlüsseln: Ein tiefer Einblick in die Resource Timing API
In der Welt der Webentwicklung ist Geschwindigkeit nicht nur ein Feature, sondern eine grundlegende Voraussetzung für eine positive Benutzererfahrung. Eine langsam ladende Website kann zu höheren Absprungraten, geringerem Nutzerengagement und letztendlich zu negativen Auswirkungen auf die Geschäftsziele führen. Während Tools wie Lighthouse und WebPageTest unschätzbare Diagnosen auf hoher Ebene liefern, stellen sie oft nur einen einzigen, synthetischen Test dar. Um die Performance für ein globales Publikum wirklich zu verstehen und zu optimieren, müssen wir die Erfahrung echter Nutzer auf ihren Geräten und in ihren Netzwerken messen. Hier kommt das Real User Monitoring (RUM) ins Spiel, und eines seiner leistungsstärksten Werkzeuge ist die Resource Timing API.
Dieser umfassende Leitfaden führt Sie tief in die Resource Timing API ein. Wir werden untersuchen, was sie ist, wie man sie verwendet und wie man ihre granularen Daten in umsetzbare Erkenntnisse umwandelt, die die Ladeleistung Ihrer Anwendung drastisch verbessern können. Egal, ob Sie ein erfahrener Frontend-Entwickler sind oder gerade erst Ihre Reise zur Performance-Optimierung beginnen, dieser Artikel wird Sie mit dem Wissen ausstatten, um die Netzwerkleistung jedes einzelnen Assets auf Ihrer Seite zu analysieren und zu verstehen.
Was ist die Resource Timing API?
Die Resource Timing API ist eine browserbasierte JavaScript-API, die detaillierte Netzwerk-Timing-Daten für jede Ressource liefert, die eine Webseite herunterlädt. Stellen Sie sie sich wie eine mikroskopische Linse für die Netzwerkaktivität Ihrer Seite vor. Für jedes Bild, Skript, Stylesheet, jede Schriftart und jeden API-Aufruf (über `fetch` oder `XMLHttpRequest`) erfasst diese API einen hochauflösenden Zeitstempel für jede Phase der Netzwerkanfrage.
Sie ist Teil einer größeren Suite von Performance-APIs, die zusammenarbeiten, um eine ganzheitliche Sicht auf die Leistung Ihrer Anwendung zu bieten. Während sich die Navigation Timing API auf den Lebenszyklus des Hauptdokuments konzentriert, zoomt die Resource Timing API auf alle abhängigen Ressourcen, die das Hauptdokument anfordert.
Warum ist sie so wichtig?
- Granularität: Sie geht über eine einzelne Metrik der "Seitenladezeit" hinaus. Sie können genau sehen, wie lange der DNS-Lookup, die TCP-Verbindung und der Content-Download für ein bestimmtes Drittanbieter-Skript oder ein kritisches Hero-Image gedauert haben.
- Echte Benutzerdaten: Im Gegensatz zu laborbasierten Tools läuft diese API in den Browsern Ihrer Nutzer. Dies ermöglicht es Ihnen, Leistungsdaten von einer Vielzahl von Netzwerkbedingungen, Geräten und geografischen Standorten zu sammeln und so ein wahres Bild Ihrer globalen Benutzererfahrung zu erhalten.
- Umsetzbare Erkenntnisse: Durch die Analyse dieser Daten können Sie spezifische Engpässe lokalisieren. Ist ein Drittanbieter-Analyse-Skript langsam beim Verbinden? Ist Ihr CDN in einer bestimmten Region leistungsschwach? Sind Ihre Bilder zu groß? Die Resource Timing API liefert die notwendigen Beweise, um diese Fragen mit Zuversicht zu beantworten.
Die Anatomie eines Ressourcen-Ladevorgangs: Eine Dekonstruktion der Zeitachse
Der Kern der Resource Timing API ist das `PerformanceResourceTiming`-Objekt. Für jede geladene Ressource erstellt der Browser eines dieser Objekte, das eine Fülle von Timing- und Größeninformationen enthält. Um diese Objekte zu verstehen, ist es hilfreich, sich den Ladevorgang als Wasserfalldiagramm vorzustellen, bei dem jeder Schritt auf den vorherigen folgt.
Lassen Sie uns die wichtigsten Eigenschaften eines `PerformanceResourceTiming`-Objekts aufschlüsseln. Alle Zeitwerte sind hochauflösende Zeitstempel, gemessen in Millisekunden vom Beginn der Seitennavigation (`performance.timeOrigin`).
startTime -> fetchStart -> domainLookupStart -> domainLookupEnd -> connectStart -> connectEnd -> requestStart -> responseStart -> responseEnd
Wichtige Timing-Eigenschaften
name: Die URL der Ressource. Dies ist Ihr primärer Identifikator.entryType: Ein String, der den Typ des Performance-Eintrags angibt. Für unsere Zwecke wird dies immer "resource" sein.initiatorType: Dies ist unglaublich nützlich für das Debugging. Es sagt Ihnen, wie die Ressource angefordert wurde. Gängige Werte sind 'img', 'link' (für CSS), 'script', 'css' (für Ressourcen, die aus CSS geladen werden, wie `@import`), 'fetch' und 'xmlhttprequest'.duration: Die Gesamtzeit, die für die Ressource benötigt wurde, berechnet alsresponseEnd - startTime. Dies ist die übergeordnete Metrik für eine einzelne Ressource.startTime: Der Zeitstempel unmittelbar bevor der Abruf der Ressource beginnt.fetchStart: Der Zeitstempel kurz bevor der Browser beginnt, die Ressource abzurufen. Er kann Caches (HTTP-Cache, Service Worker-Cache) überprüfen, bevor er zum Netzwerk übergeht. Wenn die Ressource aus einem Cache bereitgestellt wird, sind viele der nachfolgenden Zeitwerte null.domainLookupStart&domainLookupEnd: Diese markieren den Anfang und das Ende des DNS (Domain Name System)-Lookups. Die Dauer (domainLookupEnd - domainLookupStart) ist die Zeit, die benötigt wurde, um den Domainnamen in eine IP-Adresse aufzulösen. Ein hoher Wert hier könnte auf einen langsamen DNS-Provider hinweisen.connectStart&connectEnd: Diese markieren den Anfang und das Ende des Verbindungsaufbaus zum Server. Bei HTTP ist dies der TCP-Drei-Wege-Handshake. Die Dauer (connectEnd - connectStart) ist Ihre TCP-Verbindungszeit.secureConnectionStart: Wenn die Ressource über HTTPS geladen wird, markiert dieser Zeitstempel den Beginn des SSL/TLS-Handshakes. Die Dauer (connectEnd - secureConnectionStart) gibt an, wie lange die Verschlüsselungsverhandlung gedauert hat. Langsame TLS-Handshakes können ein Zeichen für eine Server-Fehlkonfiguration oder Netzwerklatenz sein.requestStart: Der Zeitstempel kurz bevor der Browser die eigentliche HTTP-Anfrage für die Ressource an den Server sendet. Die Zeit zwischenconnectEndundrequestStartwird oft als "Request Queuing"-Zeit bezeichnet, in der der Browser auf eine verfügbare Verbindung wartet.responseStart: Der Zeitstempel, zu dem der Browser das allererste Byte der Antwort vom Server empfängt. Die Dauer (responseStart - requestStart) ist die berühmte Time to First Byte (TTFB). Eine hohe TTFB ist fast immer ein Indikator für einen langsamen Backend-Prozess oder serverseitige Latenz.responseEnd: Der Zeitstempel, zu dem das letzte Byte der Ressource empfangen wurde, wodurch die Anfrage erfolgreich abgeschlossen wird. Die Dauer (responseEnd - responseStart) stellt die Content-Download-Zeit dar.
Eigenschaften der Ressourcengröße
Das Verständnis der Ressourcengröße ist genauso wichtig wie das Verständnis des Timings. Die API bietet drei wichtige Metriken:
transferSize: Die Größe der über das Netzwerk übertragenen Ressource in Bytes, einschließlich Header und des komprimierten Antwortkörpers. Wenn die Ressource aus einem Cache bereitgestellt wurde, ist dieser Wert oft 0. Dies ist die Zahl, die sich direkt auf den Datentarif und die Netzwerkzeit des Benutzers auswirkt.encodedBodySize: Die Größe des Payload-Körpers in Bytes *nach* der Komprimierung (z.B. Gzip oder Brotli), aber *vor* der Dekomprimierung. Dies hilft Ihnen, die Größe der Payload selbst, getrennt von den Headern, zu verstehen.decodedBodySize: Die Größe des Payload-Körpers in seiner unkomprimierten, ursprünglichen Form in Bytes. Der Vergleich dieses Wertes mitencodedBodySizezeigt die Wirksamkeit Ihrer Komprimierungsstrategie. Wenn diese beiden Zahlen für ein textbasiertes Asset (wie JS, CSS oder HTML) sehr nahe beieinander liegen, funktioniert Ihre Komprimierung wahrscheinlich nicht richtig.
Server-Timing
Eine der leistungsstärksten Integrationen mit der Resource Timing API ist die `serverTiming`-Eigenschaft. Ihr Backend kann Leistungsmetriken in einem speziellen HTTP-Header (`Server-Timing`) senden, und diese Metriken erscheinen im `serverTiming`-Array des entsprechenden `PerformanceResourceTiming`-Objekts. Dies schließt die Lücke zwischen Frontend- und Backend-Performance-Monitoring und ermöglicht es Ihnen, Datenbankabfragezeiten oder API-Verarbeitungsverzögerungen direkt in Ihren Frontend-Daten zu sehen.
Zum Beispiel könnte ein Backend diesen Header senden:
Server-Timing: db;dur=53, api;dur=47.2, cache;desc="HIT"
Diese Daten wären in der `serverTiming`-Eigenschaft verfügbar, sodass Sie eine hohe TTFB mit einem spezifischen langsamen Prozess auf dem Backend korrelieren können.
Wie man auf Resource-Timing-Daten in JavaScript zugreift
Nachdem wir nun die verfügbaren Daten verstanden haben, schauen wir uns die praktischen Methoden an, um sie mit JavaScript zu sammeln. Es gibt zwei primäre Methoden.
Methode 1: `performance.getEntriesByType('resource')`
Dies ist der einfachste Weg, um anzufangen. Diese Methode gibt ein Array aller `PerformanceResourceTiming`-Objekte für Ressourcen zurück, die zum Zeitpunkt des Aufrufs bereits vollständig geladen waren.
// Warten, bis die Seite geladen ist, um sicherzustellen, dass die meisten Ressourcen erfasst werden
window.addEventListener('load', () => {
const resources = performance.getEntriesByType('resource');
resources.forEach((resource) => {
console.log(`Ressource geladen: ${resource.name}`);
console.log(` - Gesamtzeit: ${resource.duration.toFixed(2)}ms`);
console.log(` - Initiator: ${resource.initiatorType}`);
console.log(` - Übertragungsgröße: ${resource.transferSize} Bytes`);
});
});
Einschränkung: Diese Methode ist eine Momentaufnahme. Wenn Sie sie zu früh aufrufen, verpassen Sie Ressourcen, die noch nicht geladen sind. Wenn Ihre Anwendung Ressourcen dynamisch lange nach dem ursprünglichen Seitenladen lädt, müssten Sie diese Methode wiederholt abfragen, was ineffizient ist.
Methode 2: `PerformanceObserver` (Der empfohlene Ansatz)
Der `PerformanceObserver` ist eine modernere, robustere und performantere Methode zur Sammlung von Performance-Einträgen. Anstatt dass Sie nach Daten fragen, pusht der Browser neue Einträge an Ihren Observer-Callback, sobald sie verfügbar werden.
Hier sind die Gründe, warum er besser ist:
- Asynchron: Er blockiert nicht den Haupt-Thread.
- Umfassend: Er kann Einträge vom allerersten Beginn des Seitenladens erfassen und vermeidet so Race Conditions, bei denen ein Skript ausgeführt wird, nachdem eine Ressource bereits geladen wurde.
- Effizient: Er vermeidet die Notwendigkeit des Pollings mit `setTimeout` oder `setInterval`.
Hier ist eine Standardimplementierung:
try {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
// Jeden Ressourceneintrag verarbeiten, sobald er eingeht
if (entry.entryType === 'resource') {
console.log(`Ressource beobachtet: ${entry.name}`);
console.log(` - Time to First Byte (TTFB): ${(entry.responseStart - entry.requestStart).toFixed(2)}ms`);
}
});
});
// Beobachtung für 'resource'-Einträge starten.
// Das 'buffered'-Flag stellt sicher, dass wir Einträge erhalten, die geladen wurden, bevor unser Observer erstellt wurde.
observer.observe({ type: 'resource', buffered: true });
// Sie können die Beobachtung später bei Bedarf beenden
// observer.disconnect();
} catch (e) {
console.error('PerformanceObserver wird in diesem Browser nicht unterstützt.');
}
Die Option buffered: true ist entscheidend. Sie weist den Observer an, sofort alle `resource`-Einträge zu übermitteln, die sich bereits im Performance-Eintrags-Puffer des Browsers befinden, und stellt so sicher, dass Sie von Anfang an eine vollständige Liste erhalten.
Verwaltung des Performance-Puffers
Browser haben ein Standardlimit für die Anzahl der Resource-Timing-Einträge, die sie speichern (typischerweise 150). Auf sehr komplexen Seiten kann dieser Puffer voll werden. Wenn das passiert, löst der Browser ein `resourcetimingbufferfull`-Ereignis aus, und es werden keine neuen Einträge hinzugefügt.
Sie können dies verwalten durch:
- Erhöhen der Puffergröße: Verwenden Sie `performance.setResourceTimingBufferSize(limit)`, um ein höheres Limit festzulegen, zum Beispiel 300.
- Leeren des Puffers: Verwenden Sie `performance.clearResourceTimings()`, nachdem Sie die Einträge verarbeitet haben, um Platz für neue zu schaffen.
performance.addEventListener('resourcetimingbufferfull', () => {
console.warn('Resource Timing Puffer ist voll. Leere...');
// Verarbeiten Sie zuerst die vorhandenen Einträge aus Ihrem Observer
// Dann leeren Sie den Puffer
performance.clearResourceTimings();
// Möglicherweise müssen Sie die Puffergröße neu anpassen, wenn dies häufig vorkommt
// performance.setResourceTimingBufferSize(500);
});
Praktische Anwendungsfälle und umsetzbare Erkenntnisse
Das Sammeln von Daten ist nur der erste Schritt. Der eigentliche Wert liegt darin, diese Daten in umsetzbare Verbesserungen umzuwandeln. Lassen Sie uns einige häufige Leistungsprobleme untersuchen und wie die Resource Timing API Ihnen bei deren Lösung hilft.
Anwendungsfall 1: Identifizierung langsamer Drittanbieter-Skripte
Das Problem: Drittanbieter-Skripte für Analysen, Werbung, Kundensupport-Widgets und A/B-Tests sind berüchtigte Performance-Killer. Sie können langsam laden, das Rendern blockieren und sogar Instabilität verursachen.
Die Lösung: Verwenden Sie die Resource Timing API, um die Auswirkungen dieser Skripte auf Ihre echten Benutzer zu isolieren und zu messen.
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) { // Setzen Sie einen Schwellenwert, z.B. 200ms
console.warn(`Langsames Drittanbieter-Skript erkannt: ${script.name}`, {
duration: `${script.duration.toFixed(2)}ms`,
transferSize: `${script.transferSize} bytes`
});
// In einem echten RUM-Tool würden Sie diese Daten an Ihr Analytics-Backend senden.
}
});
});
observer.observe({ type: 'resource', buffered: true });
Umsetzbare Erkenntnisse:
- Hohe Dauer: Wenn ein Skript durchweg eine lange Dauer hat, überlegen Sie, ob es wirklich notwendig ist. Kann seine Funktionalität durch eine performantere Alternative ersetzt werden?
- Ladestrategie: Wird das Skript synchron geladen? Verwenden Sie die Attribute `async` oder `defer` im `<script>`-Tag, um zu verhindern, dass es das Rendern der Seite blockiert.
- Selektives Hosten: Kann das Skript bedingt geladen werden, nur auf Seiten, auf denen es absolut notwendig ist?
Anwendungsfall 2: Optimierung der Bildauslieferung
Das Problem: Große, unoptimierte Bilder sind eine der häufigsten Ursachen für langsame Seitenladezeiten, insbesondere auf mobilen Geräten mit begrenzter Bandbreite.
Die Lösung: Filtern Sie Ressourceneinträge nach `initiatorType: 'img'` und analysieren Sie deren Größe und Ladezeiten.
// ... innerhalb eines PerformanceObserver-Callbacks ...
list.getEntries()
.filter(entry => entry.initiatorType === 'img')
.forEach(image => {
const downloadTime = image.responseEnd - image.responseStart;
// Ein großes Bild könnte eine hohe Download-Zeit und eine große transferSize haben
if (downloadTime > 500 || image.transferSize > 100000) { // 500ms oder 100KB
console.log(`Mögliches Problem mit großem Bild: ${image.name}`, {
downloadTime: `${downloadTime.toFixed(2)}ms`,
transferSize: `${(image.transferSize / 1024).toFixed(2)} KB`
});
}
});
Umsetzbare Erkenntnisse:
- Hohe `transferSize` und `downloadTime`: Dies ist ein klares Signal, dass das Bild zu groß ist. Optimieren Sie es, indem Sie moderne Formate wie WebP oder AVIF verwenden, es entsprechend komprimieren und auf seine angezeigten Abmessungen verkleinern.
- `srcset` verwenden: Implementieren Sie responsive Bilder mit dem `srcset`-Attribut, um je nach Viewport des Benutzers unterschiedliche Bildgrößen bereitzustellen.
- Lazy Loading: Für Bilder unterhalb des sichtbaren Bereichs verwenden Sie `loading="lazy"`, um deren Laden zu verzögern, bis der Benutzer sie in den sichtbaren Bereich scrollt.
Anwendungsfall 3: Diagnose von Netzwerkengpässen
Das Problem: Manchmal liegt das Problem nicht an der Ressource selbst, sondern am Netzwerkpfad dorthin. Langsames DNS, latente Verbindungen oder überlastete Server können die Leistung beeinträchtigen.
Die Lösung: Zerlegen Sie die `duration` in ihre Komponentenphasen, um die Quelle der Verzögerung zu lokalisieren.
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 für ${resource.name}`);
if (dnsTime > 50) console.warn(` - Hohe DNS-Zeit: ${dnsTime.toFixed(2)}ms`);
if (tcpTime > 100) console.warn(` - Hohe TCP-Verbindungszeit: ${tcpTime.toFixed(2)}ms`);
if (ttfb > 300) console.warn(` - Hohe TTFB (langsamer Server): ${ttfb.toFixed(2)}ms`);
if (downloadTime > 500) console.warn(` - Langsamer Content-Download: ${downloadTime.toFixed(2)}ms`);
}
// ... rufen Sie analyzeNetworkPhases(entry) in Ihrem Observer auf ...
Umsetzbare Erkenntnisse:
- Hohe DNS-Zeit: Ihr DNS-Provider könnte langsam sein. Erwägen Sie einen Wechsel zu einem schnelleren globalen Anbieter. Sie können auch `` verwenden, um das DNS für kritische Drittanbieter-Domains im Voraus aufzulösen.
- Hohe TCP-Zeit: Dies deutet auf Latenz beim Verbindungsaufbau hin. Ein Content Delivery Network (CDN) kann dies reduzieren, indem es Assets von einem geografisch näher am Benutzer gelegenen Standort ausliefert. Die Verwendung von `` kann sowohl den DNS-Lookup als auch den TCP-Handshake frühzeitig durchführen.
- Hohe TTFB: Dies deutet auf ein langsames Backend hin. Arbeiten Sie mit Ihrem Backend-Team zusammen, um Datenbankabfragen zu optimieren, das serverseitige Caching zu verbessern oder die Server-Hardware aufzurüsten. Der `Server-Timing`-Header ist hier Ihr bester Freund.
- Hohe Download-Zeit: Dies ist eine Funktion der Ressourcengröße und der Netzwerkbandbreite. Optimieren Sie das Asset (komprimieren, minifizieren) oder verwenden Sie ein CDN, um den Durchsatz zu verbessern.
Einschränkungen und Überlegungen
Obwohl unglaublich leistungsstark, hat die Resource Timing API einige wichtige Einschränkungen, die man beachten sollte.
Cross-Origin-Ressourcen und der `Timing-Allow-Origin`-Header
Aus Sicherheitsgründen beschränken Browser die Timing-Details, die für Ressourcen verfügbar sind, die von einem anderen Ursprung (Domain, Protokoll oder Port) als Ihre Hauptseite geladen werden. Standardmäßig sind für eine Cross-Origin-Ressource die meisten Timing-Eigenschaften wie `redirectStart`, `domainLookupStart`, `connectStart`, `requestStart`, `responseStart` und Größeneigenschaften wie `transferSize` null.
Um diese Details offenzulegen, muss der Server, der die Ressource hostet, den `Timing-Allow-Origin` (TAO) HTTP-Header enthalten. Zum Beispiel:
Timing-Allow-Origin: * (Erlaubt jedem Ursprung, die Timing-Details zu sehen)
Timing-Allow-Origin: https://www.your-website.com (Erlaubt nur Ihrer Website)
Dies ist entscheidend, wenn Sie mit Ihren eigenen CDNs oder APIs auf verschiedenen Subdomains arbeiten. Stellen Sie sicher, dass sie so konfiguriert sind, dass sie den TAO-Header senden, damit Sie volle Leistungstransparenz erhalten.
Browser-Unterstützung
Die Resource Timing API, einschließlich `PerformanceObserver`, wird von allen modernen Browsern (Chrome, Firefox, Safari, Edge) weitgehend unterstützt. Bei älteren Browsern ist sie jedoch möglicherweise nicht verfügbar. Umschließen Sie Ihren Code immer mit einem `try...catch`-Block oder prüfen Sie die Existenz von `window.PerformanceObserver`, bevor Sie ihn verwenden, um Fehler bei älteren Clients zu vermeiden.
Fazit: Von Daten zu Entscheidungen
Die Resource Timing API ist ein wesentliches Instrument im Werkzeugkasten moderner Webentwickler. Sie entmystifiziert den Netzwerk-Wasserfall und liefert die rohen, granularen Daten, die erforderlich sind, um von vagen Beschwerden wie "die Seite ist langsam" zu präzisen, datengestützten Diagnosen wie "unser Drittanbieter-Chat-Widget hat eine TTFB von 400ms für Nutzer in Südostasien" zu gelangen.
Durch die Nutzung des `PerformanceObserver` zur Sammlung echter Benutzerdaten und die Analyse des gesamten Lebenszyklus jeder Ressource können Sie:
- Drittanbieter für ihre Leistung zur Rechenschaft ziehen.
- Die Wirksamkeit Ihrer CDN- und Caching-Strategien weltweit validieren.
- Überdimensionierte Bilder und unoptimierte Assets finden und korrigieren.
- Frontend-Verzögerungen mit Backend-Verarbeitungszeiten korrelieren.
Der Weg zu einem schnelleren Web ist ein kontinuierlicher Prozess. Beginnen Sie noch heute. Öffnen Sie die Entwicklerkonsole Ihres Browsers, führen Sie die Code-Schnipsel aus diesem Artikel auf Ihrer eigenen Website aus und beginnen Sie, die reichen Leistungsdaten zu erkunden, die die ganze Zeit auf Sie gewartet haben. Indem Sie messen, was zählt, können Sie schnellere, widerstandsfähigere und angenehmere Erlebnisse für all Ihre Nutzer schaffen, egal wo auf der Welt sie sich befinden.