Standaardstatistieken voor weergave en reactiesnelheid laten een meetgat achter na het eerste laden van de pagina:
LargestContentfulPaint(largest-contentful-paint) meet alleen de eerste ('harde') documentlading en stopt met het genereren van vermeldingen zodra de gebruiker voor het eerst interactie heeft met de pagina.PerformanceEventTimingenevent/ Interaction to Next Paint (INP) meet de responsiviteit van de invoer tot het volgende frame (zoals een actieve knop of een laadspinner), maar houdt geen volgende asynchrone weergaven bij, zoals nieuwe content die wordt gerenderd nadat eenfetch()is opgelost of een routetransitie aan de clientzijde is afgerond.- Het kan lastig zijn om routewijzigingen in een app met één pagina (Single Page App, SPA) te observeren buiten de frameworkcode. Als je alleen URL-updates bijhoudt, kan dit leiden tot overrapportage van URL-wijzigingen die niet zijn gekoppeld aan routewijzigingen (bijvoorbeeld wijzigingen in ankerlinks, meerdere omleidingen voor één routewijziging).
De prestatie-invoertypen InteractionContentfulPaint (interaction-contentful-paint) en PerformanceSoftNavigation (soft-navigation) vullen deze leemte op. Ze verspreiden de causaliteit van interacties over asynchrone taken (fetch, await, setTimeout) om nieuwe weergaven met content en routetransities van apps met één pagina (SPA) rechtstreeks toe te schrijven aan de gebruikersinteractie die ze heeft geactiveerd.
Hoe wordt dit geïmplementeerd
1. Observeer contentful paints die worden geactiveerd door interacties
Gebruik een PerformanceObserver met type: 'interaction-contentful-paint' en buffered: true om steeds grotere contentful paints te observeren die kunnen worden toegeschreven aan een gebruikersinteractie.
Elke InteractionContentfulPaint-vermelding bevat:
interactionId: De unieke ID van de initiërende gebruikersinteractie (overeenkomend metPerformanceEventTiming.interactionIdenPerformanceSoftNavigation.interactionId).startTime: De tijdstempel van wanneer de initiërende gebruikersinteractie is gestart, relatief ten opzichte van de oorspronkelijke tijd van de harde navigatie.paintTimeenpresentationTime: Wanneer de weergavefase is beëindigd en wanneer de getekende pixels op het scherm zijn getekend.duration: De verstreken tijd vanaf het begin van de interactie tot de weergave van de tekening (presentationTime - startTime).largestContentfulPaint: Een momentopnameLargestContentfulPaint-object met details over het grootste getekende element voor die interactie tot nu toe (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) meten voor soft navigaties van SPA's
Een zachte navigatie wordt door de browser uitgevoerd als een vertrouwde gebruikersinteractie zowel een zichtbare URL-wijziging als een contentful paint veroorzaakt. Zo bereken je de LCP voor elke soft navigation:
- Observeer zowel
'soft-navigation'- als'interaction-contentful-paint'-items metbuffered: true. - VERPLICHT: Als er een
'soft-navigation'-item wordt ontvangen, seed je de eerste LCP-kandidaat doorsoftNavEntry.getLargestInteractionContentfulPaint()aan te roepen. Als de app content heeft getekend voordat de URL werd geüpdatet (bijvoorbeeld door een weergave te renderen en daarnahistory.pushState()aan te roepen), zijn die vroegeinteraction-contentful-paint-items uitgevoerd vóór hetsoft-navigation-item en bevatten ze nog steeds de vorigenavigationId. - VERPLICHT: Koppel daaropvolgende
'interaction-contentful-paint'-items aan de actieve soft-navigatie doorentry.interactionId === softNavEntry.interactionIdte matchen. Match NIET opentry.navigationId. Als jeinteractionIdgebruikt, zorg je dat je weergaven opneemt die plaatsvonden vóór de URL-update en weergaven uitsluit van latere, niet-gerelateerde interacties op de nieuwe route.
/**
* 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. Meet Core Web Vitals voor zachte navigaties met web-vitals
Als je de standaard Core Web Vitals (LCP, CLS en INP) voor zowel harde als zachte navigaties wilt rapporteren aan een analyse-eindpunt, gebruik je de web-vitals-bibliotheek (v6.0.0+) met { reportSoftNavs: true } in plaats van elke tijdlijn van statistieken handmatig te segmenteren.
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 });
Praktische tips
- WEL functiedetectie
'interaction-contentful-paint'en'soft-navigation'metPerformanceObserver.supportedEntryTypes?.includes(...)voordat jeobserver.observe()aanroept. - Geef
{ buffered: true }WEL door als u'interaction-contentful-paint'en'soft-navigation'waarneemt, zodat vroege vermeldingen niet worden gemist als de analysecode asynchroon wordt geïnitialiseerd. - Roep
softNavEntry.getLargestInteractionContentfulPaint()WEL aan als je een nieuwesoft-navigation-vermelding verwerkt, zodat weergaven die vóór de URL-update hebben plaatsgevonden, worden opgenomen in LCP. - Zorg dat
interaction-contentful-paint-items overeenkomen met een geleidelijke navigatie metinteractionId, niet metnavigationId. - Gebruik WEL
entry.duration(ofentry.presentationTime - entry.startTime) om de latentie van de interactieweergave te meten in plaats vanentry.presentationTimerechtstreeks te rapporteren. AllePerformanceEntry-tijdstempels worden gemeten vanaf de oorspronkelijke laadtijd van pagina, niet vanaf de geleidelijke start van de navigatie. - Vertrouw NIET op
performance.getEntriesByType('soft-navigation')om langdurige apps met één pagina te controleren, omdat de buffer van de browser beperkt is tot de eerste 50 vermeldingen voor zachte navigatie.
Reservestrategieën
Als je Baseline-doel browsers bevat die InteractionContentfulPaint of PerformanceSoftNavigation niet ondersteunen, behandel je deze API's als een progressive enhancement voor Real User Monitoring (RUM):
- Functiedetectie en graceful degradation: Beveilig de registratie van de observer met
PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint')enPerformanceObserver.supportedEntryTypes?.includes('soft-navigation'). In browsers die geen ondersteuning bieden, slaat u de registratie van de observers over, zodat uw app wordt uitgevoerd zonder fouten te genereren. - Houd standaard harde navigatie-observers actief: Blijf standaard
'largest-contentful-paint'-,'event'- en'layout-shift'-items observeren in alle browsers. OmdatLargestContentfulPaintwordt afgerond bij de eerste gebruikersinteractie, ongeacht de ondersteuning voor zachte navigatie, worden de eerste LCP-metingen voor hard laden niet gewijzigd of onderbroken door zachte navigatie te meten in ondersteunende browsers.