Bei Standardmesswerten für das Rendern und die Reaktionsfähigkeit gibt es nach dem ersten Seitenaufbau eine Messlücke:
LargestContentfulPaint(largest-contentful-paint) misst nur das anfängliche („harte“) Laden des Dokuments und gibt keine Einträge mehr aus, sobald der Nutzer zum ersten Mal mit der Seite interagiert.PerformanceEventTimingundevent/ Interaction to Next Paint (INP) misst die Reaktionsfähigkeit auf Eingaben bis zum nächsten Frame (z. B. ein aktiver Button oder ein Ladesymbol), erfasst aber keine nachfolgenden asynchronen Paint-Vorgänge, z. B. neue Inhalte, die nach der Auflösung einesfetch()gerendert werden, oder den Abschluss eines clientseitigen Routenübergangs.- Das Beobachten von Routenänderungen in einer Single-Page-Anwendung (SPA) kann außerhalb des Framework-Codes schwierig sein. Wenn Sie nur URL-Aktualisierungen beobachten, werden möglicherweise zu viele URL-Änderungen gemeldet, die nicht mit Routenänderungen zusammenhängen (z. B. Änderungen an Ankerlinks, mehrere „Weiterleitungen“ für eine einzelne Routenänderung).
Die Leistungseintragstypen InteractionContentfulPaint (interaction-contentful-paint) und PerformanceSoftNavigation (soft-navigation) schließen diese Lücke. Sie geben die Kausalität von Interaktionen über asynchrone Aufgaben (fetch, await, setTimeout) weiter, um neue Contentful Paints und Routenübergänge in Single-Page-Anwendungen (SPA) direkt der Nutzerinteraktion zuzuordnen, die sie ausgelöst hat.
Implementierung
1. Durch Interaktionen ausgelöste Contentful Paints beobachten
Verwenden Sie einen PerformanceObserver mit type: 'interaction-contentful-paint' und buffered: true, um zunehmend größere Contentful Paints zu beobachten, die auf Nutzerinteraktionen zurückzuführen sind.
Jeder InteractionContentfulPaint-Eintrag enthält:
interactionId: Die eindeutige Kennung der initiierenden Nutzerinteraktion (entsprichtPerformanceEventTiming.interactionIdundPerformanceSoftNavigation.interactionId).startTime: Der Zeitstempel, der angibt, wann die initiierende Nutzerinteraktion begonnen hat, relativ zum ursprünglichen Hard-Navigation-Zeitursprung.paintTimeundpresentationTime: Wann die Renderingphase endete und wann die gerenderten Pixel auf dem Bildschirm angezeigt wurden.duration: Die verstrichene Zeit vom Beginn der Interaktion bis zur Darstellung des Paints (presentationTime - startTime).largestContentfulPaint: Ein Snapshot-LargestContentfulPaint-Objekt mit Details zum größten gerenderten Element für diese Interaktion bis jetzt (element,size,url,id,renderTime,loadTime).
/**
* Observes contentful paints caused by user interactions and invokes a callback
* whenever an interaction produces a larger contentful paint.
*
* @param {(entry: PerformanceEntry) => void} onInteractionPaint
*/
export function observeInteractionPaints(onInteractionPaint) {
// Feature-detect support before calling observe() so unsupported browsers
// do not throw or log console warnings.
if (
typeof PerformanceObserver === 'undefined' ||
!PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint')
) {
return null;
}
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// entry.duration is already calculated as (entry.presentationTime - entry.startTime),
// giving the total time from the user interaction to this contentful paint.
onInteractionPaint(entry);
}
});
// MANDATORY: Pass buffered: true to capture interaction paints that occurred
// before this observer was registered.
observer.observe({ type: 'interaction-contentful-paint', buffered: true });
return observer;
}
2. Largest Contentful Paint (LCP) für Soft-Navigationen in SPAs messen
Eine Soft-Navigation wird vom Browser ausgegeben, wenn eine vertrauenswürdige Nutzerinteraktion sowohl eine sichtbare URL-Änderung als auch ein Contentful Paint verursacht. So berechnen Sie den LCP für jede weiche Navigation:
- Beobachten Sie sowohl
'soft-navigation'- als auch'interaction-contentful-paint'-Einträge mitbuffered: true. - OBLIGATORISCH: Wenn ein
'soft-navigation'-Eintrag empfangen wird, wird der erste LCP-Kandidat durch Aufrufen vonsoftNavEntry.getLargestInteractionContentfulPaint()festgelegt. Wenn die Anwendung Inhalte vor der Aktualisierung der URL gerendert hat (z. B. eine Ansicht gerendert und dannhistory.pushState()aufgerufen hat), wurden diese früheninteraction-contentful-paint-Einträge vor demsoft-navigation-Eintrag ausgegeben und enthalten noch den vorherigennavigationId. - OBLIGATORISCH: Korreliere nachfolgende
'interaction-contentful-paint'-Einträge mit der aktiven weichen Navigation, indem duentry.interactionId === softNavEntry.interactionIdabgleichst. NICHT nachentry.navigationIdabgleichen. MitinteractionIdwerden Paint-Ereignisse berücksichtigt, die vor der URL-Aktualisierung aufgetreten sind, und Paint-Ereignisse von späteren, nicht damit zusammenhängenden Interaktionen auf der neuen Route ausgeschlossen.
/**
* Tracks FCP and LCP for each client-side soft navigation and reports finalized
* route metrics when the user navigates to a new route or hides the page.
*
* @param {(report: {
* url: string,
* navigationId: number,
* interactionId: number,
* fcpDuration: number,
* lcpDuration: number | null,
* lcpElement: Element | null,
* lcpSize: number | null
* }) => void} onSoftNavReport
*/
export function trackSoftNavigationLCP(onSoftNavReport) {
const supported =
typeof PerformanceObserver !== 'undefined' &&
PerformanceObserver.supportedEntryTypes?.includes('soft-navigation') &&
PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint');
if (!supported) {
return null;
}
let activeSoftNav = null;
let activeLcpEntry = null;
function finalizeCurrentSoftNav() {
if (!activeSoftNav) return;
onSoftNavReport({
// activeSoftNav.name holds the destination URL of the soft navigation.
url: activeSoftNav.name,
navigationId: activeSoftNav.navigationId,
interactionId: activeSoftNav.interactionId,
// activeSoftNav.duration is (activeSoftNav.presentationTime - activeSoftNav.startTime),
// which represents First Contentful Paint (FCP) relative to interaction start.
fcpDuration: activeSoftNav.duration,
// activeLcpEntry.duration is (activeLcpEntry.presentationTime - activeSoftNav.startTime),
// representing the soft navigation's LCP duration relative to interaction start.
lcpDuration: activeLcpEntry ? activeLcpEntry.duration : null,
lcpElement: activeLcpEntry?.largestContentfulPaint?.element ?? null,
lcpSize: activeLcpEntry?.largestContentfulPaint?.size ?? null,
});
activeSoftNav = null;
activeLcpEntry = null;
}
const softNavObserver = new PerformanceObserver((list) => {
for (const softNavEntry of list.getEntries()) {
// Finalize the previous route's metrics before resetting state for the new route.
finalizeCurrentSoftNav();
activeSoftNav = softNavEntry;
// MANDATORY: Seed the LCP candidate with getLargestInteractionContentfulPaint()
// to capture any attributed contentful paints that occurred before the URL updated.
activeLcpEntry = softNavEntry.getLargestInteractionContentfulPaint();
}
});
const icpObserver = new PerformanceObserver((list) => {
for (const icpEntry of list.getEntries()) {
// MANDATORY: Match by interactionId (not navigationId) so only paints caused
// by the soft navigation's initiating interaction update the route's LCP.
if (activeSoftNav && icpEntry.interactionId === activeSoftNav.interactionId) {
activeLcpEntry = icpEntry;
}
}
});
softNavObserver.observe({ type: 'soft-navigation', buffered: true });
icpObserver.observe({ type: 'interaction-contentful-paint', buffered: true });
// Finalize the active route's LCP when the page is backgrounded or unloaded.
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
finalizeCurrentSoftNav();
}
});
return { softNavObserver, icpObserver, finalizeCurrentSoftNav };
}
3. Core Web Vitals bei Soft-Navigationen mit web-vitals messen
Wenn Sie die standardmäßigen Core Web Vitals (LCP, CLS und INP) für harte und weiche Navigationen an einen Analyseendpunkt melden möchten, verwenden Sie die web-vitals-Bibliothek (v6.0.0+) mit { reportSoftNavs: true }, anstatt jede Messwert-Zeitachse manuell zu segmentieren.
import { onLCP, onFCP, onCLS, onINP } from 'web-vitals';
function sendToAnalytics(metric) {
// Example-only endpoint; replace with your real telemetry URL.
navigator.sendBeacon(
'/analytics',
JSON.stringify({
name: metric.name,
value: metric.value,
navigationType: metric.navigationType,
navigationURL: metric.navigationURL,
}),
);
}
// Pass { reportSoftNavs: true } to report metrics per soft navigation in addition
// to the initial hard navigation.
onLCP(sendToAnalytics, { reportSoftNavs: true });
onFCP(sendToAnalytics, { reportSoftNavs: true });
onCLS(sendToAnalytics, { reportSoftNavs: true });
onINP(sendToAnalytics, { reportSoftNavs: true });
Best Practices
- FÜHREN SIE eine Feature-Erkennung für
'interaction-contentful-paint'und'soft-navigation'mitPerformanceObserver.supportedEntryTypes?.includes(...)durch, bevor Sieobserver.observe()aufrufen. - Übergib
{ buffered: true }, wenn du'interaction-contentful-paint'und'soft-navigation'beobachtest, damit frühe Einträge nicht verpasst werden, wenn der Analysecode asynchron initialisiert wird. - RUFEN SIE
softNavEntry.getLargestInteractionContentfulPaint()auf, wenn Sie einen neuensoft-navigation-Eintrag verarbeiten, damit Paint-Ereignisse, die vor der URL-Aktualisierung aufgetreten sind, in den LCP einbezogen werden. - DO match
interaction-contentful-paintentries to a soft navigation usinginteractionId, notnavigationId. - DO use
entry.duration(orentry.presentationTime - entry.startTime) to measure interaction paint latency, rather than reportingentry.presentationTimedirectly. AllePerformanceEntry-Zeitstempel werden ab dem ursprünglichen harten Seitenladezeitpunkt gemessen, nicht ab dem Start der weichen Navigation. - Verlassen Sie sich NICHT auf
performance.getEntriesByType('soft-navigation'), um langlebige Single-Page-Anwendungen zu beobachten, da der Puffer des Browsers auf die ersten 50 Soft-Navigationseinträge beschränkt ist.
Fallback-Strategien
Wenn Ihre Baseline-Zielgruppe Browser umfasst, die InteractionContentfulPaint oder PerformanceSoftNavigation nicht unterstützen, sollten Sie diese APIs als progressive Verbesserung für das Real User Monitoring (RUM) behandeln:
- Featureerkennung und graduelle Fehlertoleranz: Schützen Sie die Observer-Registrierung mit
PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint')undPerformanceObserver.supportedEntryTypes?.includes('soft-navigation'). In Browsern, die diese Funktion nicht unterstützen, sollten Sie die Observer nicht registrieren, damit Ihre Anwendung ohne Fehler ausgeführt wird. - Standard-Hard-Navigations-Observer aktiv lassen: Beobachten Sie weiterhin die Standardeinträge
'largest-contentful-paint','event'und'layout-shift'in allen Browsern. DaLargestContentfulPaintunabhängig von der Unterstützung von Soft-Navigationen bei der ersten Nutzerinteraktion abgeschlossen wird, werden die ursprünglichen LCP-Messungen für Hard-Loads durch die Messung von Soft-Navigationen in unterstützenden Browsern nicht verändert oder beeinträchtigt.