JS
() =>
JavaScript · Performance API · Web Vitals
JavaScript Performance API
Präzise Laufzeitmessungen mit performance.now() und PerformanceObserver

Date.now() liefert Millisekunden mit einer Präzision, die für ernsthafte Performance-Messungen nicht ausreicht, und tappt in die Falle der Systemuhr-Synchronisation. Die JavaScript Performance API liefert Sub-Millisekunden-Zeitstempel, strukturierte Marks und Measures, einen nicht blockierenden Observer-Mechanismus – und direkten Zugang zu Web Vitals ohne externe Bibliothek.

12 Min. Lesezeit performance.now · Mark · Measure · PerformanceObserver · Long Tasks Alle modernen Browser · Node.js 16+

1. Warum Date.now() für Performance-Messungen ungeeignet ist

Der häufigste Ansatz für Laufzeitmessungen in JavaScript ist die Differenz zweier Date.now()-Aufrufe. Das Problem: Date.now() gibt die Anzahl der Millisekunden seit dem Unix-Epoch zurück und ist an die Systemuhr gebunden. Die Systemuhr kann sich ändern – durch NTP-Synchronisation, manuelle Anpassung oder Sommerzeit-Umstellung. Das bedeutet, dass ein Zeitintervall, das mit Date.now() gemessen wird, negativ sein kann, wenn die Systemuhr während der Messung zurückgestellt wird. Für kurze Zeitspannen liefert Date.now() außerdem nur ganzzahlige Millisekunden ohne Sub-Millisekunden-Auflösung.

Die JavaScript Performance API löst beide Probleme. performance.now() basiert auf einer monotonen Uhr, die garantiert niemals rückwärts läuft, unabhängig von Systemuhr-Änderungen. Die zurückgegebene Zahl ist ein Fließkomma-Wert mit Sub-Millisekunden-Präzision – in Browsern aus Sicherheitsgründen auf 0,1 ms gerundet, in Nicht-Sicherheitskontexten (z. B. Node.js) mit voller CPU-Auflösung. Der Startzeitpunkt ist der Navigationsstart der Seite, nicht das Unix-Epoch, was die Werte kleiner und lesbarer macht.

2. performance.now(): Sub-Millisekunden-Präzision und Monotonie

performance.now() gibt die Anzahl der Millisekunden seit dem Navigationsstart der aktuellen Seite zurück, als Fließkomma-Zahl mit bis zu fünf Dezimalstellen. Der Wert ist monoton: Er nimmt niemals ab, auch nicht bei Systemuhr-Änderungen. Das macht ihn zur zuverlässigen Basis für alle Performance API-Messungen. Der Unterschied zwischen zwei performance.now()-Aufrufen ist immer nicht-negativ und spiegelt die tatsächlich verstrichene Rechen- und Wartezeit wider.

Ein häufiger Missverständnis: performance.now() misst nicht CPU-Zeit, sondern Wanduhrenzeit – einschließlich Wartezeiten auf I/O, Netzwerk und Timer. Wer nur CPU-Zeit messen will, braucht Worker Threads oder server-seitige Messungen. Im Browser ist Wanduhrenzeit in der Regel das, was man messen will: die Zeit, die ein Nutzer tatsächlich auf eine Reaktion wartet. Die Performance API liefert genau diese Zahl, präzise und ohne Systemuhr-Abhängigkeit.


// performance.now() — monotonic, sub-millisecond, navigation-relative
const start = performance.now();

// Simulate some work
for (let i = 0; i < 1_000_000; i++) { Math.sqrt(i); }

const end   = performance.now();
const delta = end - start; // always >= 0, sub-millisecond precision

console.log(`Loop took ${delta.toFixed(3)} ms`);

// Compare with Date.now() — integer ms, wall-clock dependent
const t1 = Date.now();
for (let i = 0; i < 1_000_000; i++) { Math.sqrt(i); }
const t2 = Date.now();
console.log(`Date.now delta: ${t2 - t1} ms`); // integer, could be 0 for fast code

// Safe for cross-frame timing — origin-relative, not epoch-relative
// performance.timeOrigin gives the absolute epoch offset
console.log(`Page loaded at: ${new Date(performance.timeOrigin).toISOString()}`);
console.log(`Current offset: ${performance.now().toFixed(3)} ms after load`);

3. User Timing: performance.mark() und performance.measure()

Die User Timing API ist die ausdrucksstärkste Schicht der Performance API für Anwendungscode. Mit performance.mark("name") wird ein benannter Zeitstempel im Performance-Buffer gespeichert. Mit performance.measure("name", "start-mark", "end-mark") wird die Differenz zwischen zwei Marks als PerformanceMeasure-Eintrag gespeichert und steht automatisch in Browser-DevTools sichtbar unter dem Timeline-Abschnitt "Timings". Das bedeutet, dass eigene Messungen direkt neben Browser-internen Timings wie Navigation und Resource Loading visualisiert werden.

Ein wesentlicher Vorteil gegenüber einem manuellen performance.now()-Differenz-Ansatz: Marks und Measures werden in einem separaten Buffer gespeichert und können jederzeit mit performance.getEntriesByType("mark") und performance.getEntriesByType("measure") abgerufen werden – auch nach dem gemessenen Code-Abschnitt, etwa in einem Error-Handler oder einem Berichterstattungs-Callback. Mit performance.clearMarks() und performance.clearMeasures() können veraltete Einträge aus dem Buffer entfernt werden, um Speicherlecks bei lang laufenden Anwendungen zu vermeiden.


// User Timing API — named marks and measures visible in DevTools Timeline
async function loadUserData(userId) {
  performance.mark("loadUserData:start");

  // Phase 1: fetch user
  performance.mark("fetchUser:start");
  const user = await fetch(`/api/users/${userId}`).then(r => r.json());
  performance.mark("fetchUser:end");
  performance.measure("Fetch User", "fetchUser:start", "fetchUser:end");

  // Phase 2: fetch user's orders
  performance.mark("fetchOrders:start");
  const orders = await fetch(`/api/orders?userId=${userId}`).then(r => r.json());
  performance.mark("fetchOrders:end");
  performance.measure("Fetch Orders", "fetchOrders:start", "fetchOrders:end");

  performance.mark("loadUserData:end");
  performance.measure("Total loadUserData", "loadUserData:start", "loadUserData:end");

  // Retrieve measures for reporting
  const measures = performance.getEntriesByType("measure");
  measures.forEach(m => console.log(`${m.name}: ${m.duration.toFixed(2)} ms`));

  // Clean up to avoid buffer bloat in long-running apps
  performance.clearMarks();
  performance.clearMeasures();

  return { user, orders };
}

4. PerformanceObserver: asynchrone und nicht blockierende Messungen

Der PerformanceObserver ist die moderne, reaktive Schnittstelle zur Performance API. Statt einen Buffer abzufragen (getEntriesByType()), registriert man einen Callback, der asynchron aufgerufen wird, sobald neue Performance-Einträge eines bestimmten Typs verfügbar sind. Das ist besonders wichtig für Metriken, die kontinuierlich anfallen – Resource Timing, Long Tasks, Layout Shifts – wo ein polling-basierter Ansatz entweder zu spät käme oder unnötig CPU-Zeit verbrauchen würde.

Der PerformanceObserver blockiert den Haupt-Thread nicht: Der Callback wird als Mikrotask nach dem aktuellen Event-Loop-Tick ausgeführt. Das macht ihn ideal für Produktions-Monitoring, das die Nutzererfahrung nicht beeinträchtigen darf. Ein einzelner Observer kann mehrere Eintragstypen gleichzeitig beobachten (observe({ type: "mark" }), observe({ type: "measure" })). Mit observer.disconnect() wird der Observer beendet, wenn keine weiteren Einträge erwartet werden.

5. Resource Timing: Netzwerk-Ladezeiten analysieren

Die Resource Timing API ist Teil der Performance API und erfasst automatisch detaillierte Timing-Informationen für alle Ressourcen, die eine Seite lädt: Scripts, Stylesheets, Bilder, Fonts und XHR/Fetch-Anfragen. Für jeden Eintrag stehen Zeitstempel zur Verfügung: startTime, fetchStart, domainLookupStart, connectStart, requestStart, responseStart, responseEnd. Aus diesen Timestamps lassen sich DNS-Lookup-Zeit, TCP-Verbindungszeit, TTFB (Time to First Byte) und Download-Zeit berechnen.

Besonders wertvoll ist die Resource Timing API für die Identifikation von Netzwerk-Bottlenecks ohne externe Monitoring-Tools. Wenn ein Script 800 ms lädt, zeigt die Resource Timing API, ob das Problem in der DNS-Auflösung, der TCP-Verbindung, dem TTFB oder dem eigentlichen Download liegt. Das ermöglicht gezielte Optimierungen: CDN-Setup, Preconnect-Hints, HTTP/2-Multiplexing oder Caching-Strategien – datengetrieben, auf Basis echter Nutzerdaten, nicht Laborwerte.


// PerformanceObserver for Resource Timing — non-blocking, reactive
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Only analyze resources slower than 200 ms
    if (entry.duration < 200) continue;

    const dns      = entry.domainLookupEnd  - entry.domainLookupStart;
    const tcp      = entry.connectEnd       - entry.connectStart;
    const ttfb     = entry.responseStart    - entry.requestStart;
    const download = entry.responseEnd      - entry.responseStart;

    console.group(`Slow resource: ${entry.name}`);
    console.log(`Total:    ${entry.duration.toFixed(0)} ms`);
    console.log(`DNS:      ${dns.toFixed(0)} ms`);
    console.log(`TCP:      ${tcp.toFixed(0)} ms`);
    console.log(`TTFB:     ${ttfb.toFixed(0)} ms`);
    console.log(`Download: ${download.toFixed(0)} ms`);
    console.groupEnd();
  }
});

// buffered: true catches entries that happened before observer was created
observer.observe({ type: "resource", buffered: true });

// Long Tasks API: detect main-thread blocking > 50 ms
const longTaskObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.warn(`Long Task detected: ${entry.duration.toFixed(0)} ms`);
    // entry.attribution gives the script/frame responsible
  }
});
longTaskObserver.observe({ type: "longtask", buffered: true });

6. Long Tasks API: Jank und Blocking-Zeit erkennen

Ein „Long Task" ist definiert als jede JavaScript-Ausführung, die den Haupt-Thread länger als 50 ms blockiert. Lange Tasks verhindern, dass der Browser auf Benutzereingaben reagiert, Animationen flüssig läuft und der Scroll-Interaktion reibungslos ist – das Phänomen wird als "Jank" bezeichnet. Die Long Tasks API innerhalb der Performance API ermöglicht es, solche blockierenden Codeabschnitte automatisch zu erkennen und zu protokollieren, ohne dass die Entwickler jeden Codeabschnitt manuell instrumentieren müssen.

Jeder Long-Task-Eintrag enthält einen attribution-Array, der Informationen über den Container (Dokument, iframe, Worker) liefert, in dem der Long Task ausgeführt wurde. In Kombination mit User Timing Marks kann man genaue Aussagen treffen: "Dieser Long Task von 120 ms wurde während unserer loadUserData-Measure ausgeführt." Das ist das Fundament für evidenzbasiertes Performance-Optimieren statt Raten nach DevTools-Screenshots.

7. Web Vitals direkt mit der Performance API messen

Web Vitals – LCP (Largest Contentful Paint), FID (First Input Delay), CLS (Cumulative Layout Shift) – sind die von Google definierten Core-Metriken für die Nutzererfahrung im Web. Alle drei sind direkt über die Performance API messbar, ohne die offizielle web-vitals-Bibliothek einbinden zu müssen. LCP wird über den PerformanceObserver mit type: "largest-contentful-paint" erfasst. CLS mit type: "layout-shift". FID mit type: "first-input". Das ermöglicht Real-User-Monitoring (RUM) direkt im eigenen JavaScript-Code.

Ein vollständiges RUM-Setup mit der Performance API sendet diese Metriken asynchron an ein Analyse-Backend – über navigator.sendBeacon(), das auch beim Seitenverlassen garantiert abgesendet wird. Das ist der Kern von Google Search Console, Lighthouse und Chrome UX Report: Echte Nutzerdaten, gemessen im echten Browser, über die nativ eingebaute Performance API. Wer die web-vitals-Bibliothek einsetzt, kann das Bundle-Gewicht durch eine eigene, schlanke Implementierung auf Basis der Performance API deutlich reduzieren.

API Zweck Eintrag-Typ Schlüssel-Eigenschaft
performance.now() Monotoner Zeitstempel Sub-ms, nie negativ
User Timing Code-Abschnitte benennen mark, measure DevTools-Integration, Buffer-Abfrage
Resource Timing Netzwerk-Ladezeiten resource DNS, TCP, TTFB, Download
Long Tasks API Jank erkennen longtask duration > 50 ms, attribution
Layout Instability CLS messen layout-shift value, hadRecentInput

8. Performance API in Node.js: perf_hooks und Worker Threads

In Node.js ist die Performance API über das eingebaute Modul node:perf_hooks verfügbar. performance.now() und PerformanceObserver funktionieren dort mit derselben Semantik wie im Browser, aber mit höherer Präzision – ohne die Sicherheitsbegrenzung auf 0,1 ms, die Browser aus Timing-Attack-Schutz einführen. Das macht Node.js-seitige Messungen mit der Performance API besonders präzise für Mikro-Benchmarks und Algorithmus-Vergleiche.

In Worker Threads steht die Performance API vollständig zur Verfügung. Jeder Worker hat seinen eigenen Zeitursprung, relativ zum Start des Workers. Das ermöglicht unabhängige Laufzeitmessungen in jedem Thread, ohne dass sich Messungen aus dem Haupt-Thread und Worker-Threads gegenseitig beeinflussen. Für Benchmarks in Node.js empfiehlt sich außerdem performance.timerify(fn), das eine Funktion in eine gemessene Variante transformiert und automatisch für jeden Aufruf einen function-Eintrag im Performance-Buffer erstellt.


// Node.js Performance API via perf_hooks (same API as browsers)
import { performance, PerformanceObserver } from "node:perf_hooks";

// Wrap function to auto-instrument with User Timing
function withTiming(name, fn) {
  return async function (...args) {
    performance.mark(`${name}:start`);
    try {
      return await fn.apply(this, args);
    } finally {
      performance.mark(`${name}:end`);
      performance.measure(name, `${name}:start`, `${name}:end`);
    }
  };
}

// Observer reports all measures to console
const obs = new PerformanceObserver((items) => {
  items.getEntries().forEach(entry => {
    console.log(`[perf] ${entry.name}: ${entry.duration.toFixed(3)} ms`);
  });
  obs.disconnect();
});
obs.observe({ type: "measure", buffered: true });

// timerify: auto-instrumented function (Node.js specific)
import { createHistogram } from "node:perf_hooks";
const histogram = createHistogram();
const timerified = performance.timerify(
  function computeHash(data) { /* … */ },
  { histogram }
);
// After multiple calls: histogram.mean, histogram.percentile(99), etc.

Mironsoft

Web Performance Optimierung und Real-User-Monitoring

Web Vitals und Ladezeiten systematisch verbessern?

Wir implementieren Real-User-Monitoring mit der nativen Performance API, identifizieren Long Tasks und Netzwerk-Bottlenecks und liefern konkrete Optimierungsmaßnahmen für LCP, CLS und FID.

RUM-Setup

Natives Performance API RUM ohne externe Bibliothek – Web Vitals, Long Tasks, Resource Timing

Performance-Audit

Analyse von LCP, CLS, TTFB und Long Tasks auf Basis echter Nutzerdaten

Optimierung

Konkrete Maßnahmen: Preconnect, Code-Splitting, CDN, Caching und Bundle-Optimierung

10. Zusammenfassung

Die JavaScript Performance API ist das vollständigste native Werkzeug für Laufzeitmessungen im Browser und in Node.js. performance.now() liefert monotone Sub-Millisekunden-Zeitstempel ohne Systemuhr-Abhängigkeit. User Timing mit performance.mark() und performance.measure() macht eigene Code-Abschnitte sichtbar – direkt in den DevTools. Der PerformanceObserver ermöglicht reaktive, nicht blockierende Erfassung von Resource Timing, Long Tasks und Web Vitals. Die Long Tasks API identifiziert Jank-Quellen ohne manuelles Instrumentieren.

Wer diese Werkzeuge kombiniert, baut ein vollständiges Real-User-Monitoring-System ohne externe Bibliothek, das echte Nutzerdaten für LCP, CLS und FID liefert. Das ist die Grundlage für evidenzbasiertes Performance-Optimieren – nicht auf Basis von Laborwerten aus Lighthouse, sondern auf Basis der Performance API-Daten echter Nutzer in echten Netzwerkbedingungen.

JavaScript Performance API — Das Wichtigste auf einen Blick

performance.now()

Monoton, Sub-Millisekunden, navigationRelativ. Nie negativ, keine Systemuhr-Abhängigkeit. Basis aller Performance-Messungen.

User Timing

performance.mark() und performance.measure() machen Code-Abschnitte in DevTools sichtbar und im Buffer abrufbar.

PerformanceObserver

Reaktiv, nicht blockierend. Beobachtet Resource Timing, Long Tasks, Web Vitals asynchron – ideal für RUM in der Produktion.

Long Tasks API

Erkennt Jank: JS-Ausführung > 50 ms. Attribution-Array zeigt, welches Script blockiert. Keine manuelle Instrumentierung nötig.

11. FAQ: JavaScript Performance API

1Was ist die JavaScript Performance API?
Native API für Laufzeitmessungen: performance.now(), User Timing, Resource Timing, Long Tasks API und PerformanceObserver – ohne externe Bibliothek.
2Warum performance.now() statt Date.now()?
Monoton, Sub-ms-Präzision, unabhängig von Systemuhr. Date.now() kann sich bei NTP-Sync ändern und liefert nur ganzzahlige Millisekunden.
3Was ist PerformanceObserver?
Reaktiver, nicht blockierender Observer für Performance-Einträge. Beobachtet resource, longtask, measure, layout-shift und mehr asynchron.
4Was ist ein Long Task?
JS-Ausführung > 50 ms auf dem Haupt-Thread. Blockiert Animationen und Eingaben (Jank). Long Tasks API meldet automatisch via PerformanceObserver.
5Web Vitals ohne Bibliothek?
Ja. LCP, CLS, FID direkt über PerformanceObserver mit den entsprechenden Typen. web-vitals-Bibliothek ist komfortabel, aber keine Pflicht.
6Was ist User Timing?
performance.mark() setzt Zeitstempel, performance.measure() berechnet Differenz. Beide in DevTools sichtbar und im Buffer abrufbar.
7Wie präzise ist performance.now() im Browser?
0,1 ms im Standard-Kontext (Spectre-Schutz). Volle Mikrosekunden-Präzision in Cross-Origin-Isolated-Kontexten (COOP + COEP). Node.js: Nanosekunden.
8Was ist Resource Timing?
Automatische Timing-Daten für alle Ressourcen: DNS, TCP, TTFB, Download. Kein manuelles Instrumentieren nötig.
9Performance API in Node.js?
import { performance, PerformanceObserver } from 'node:perf_hooks'. Gleiche API, höhere Präzision. Zusätzlich: timerify() und createHistogram().
10navigator.sendBeacon für RUM?
sendBeacon garantiert die Übertragung auch beim Seitenverlassen. Für RUM-Systeme, die Performance-API-Daten senden, zuverlässiger als fetch im unload-Event.