Metrik render dan responsivitas standar meninggalkan celah pengukuran setelah pemuatan halaman awal:
LargestContentfulPaint(largest-contentful-paint) hanya mengukur pemuatan dokumen awal ("hard") dan berhenti memunculkan entri segera setelah pengguna pertama kali berinteraksi dengan halaman.PerformanceEventTimingdanevent/ Interaction to Next Paint (INP) mengukur responsivitas input hingga frame berikutnya (seperti status aktif tombol atau indikator lingkaran berputar), tetapi tidak melacak render asinkron berikutnya—seperti konten baru yang dirender setelahfetch()diselesaikan, atau transisi rute sisi klien selesai.- Mengamati perubahan rute di Aplikasi Web Satu Halaman (SPA) bisa jadi sulit di luar kode framework. Hanya memantau pembaruan URL dapat menyebabkan pelaporan berlebihan atas perubahan URL yang tidak terkait dengan perubahan rute (misalnya, perubahan link anchor, beberapa "pengalihan" untuk satu perubahan rute).
Jenis entri performa InteractionContentfulPaint (interaction-contentful-paint) dan PerformanceSoftNavigation (soft-navigation) mengisi kesenjangan ini. API ini menyebarkan kausalitas interaksi di seluruh tugas asinkron (fetch, await, setTimeout) untuk mengatribusikan konten baru dan transisi rute Aplikasi Web Satu Halaman (SPA) langsung kembali ke interaksi pengguna yang memicunya.
Cara menerapkan
1. Mengamati contentful paint yang dipicu oleh interaksi
Gunakan PerformanceObserver dengan type: 'interaction-contentful-paint' dan buffered: true untuk mengamati contentful paint yang semakin besar yang disebabkan oleh interaksi pengguna.
Setiap entri InteractionContentfulPaint menyediakan:
interactionId: ID unik interaksi pengguna yang memulai (cocok denganPerformanceEventTiming.interactionIddanPerformanceSoftNavigation.interactionId).startTime: Stempel waktu saat interaksi pengguna yang memulai dimulai, relatif terhadap asal waktu navigasi berat awal.paintTimedanpresentationTime: Saat fase rendering berakhir dan saat piksel yang digambar ditampilkan di layar.duration: Waktu yang berlalu dari awal interaksi hingga presentasi render (presentationTime - startTime).largestContentfulPaint: ObjekLargestContentfulPaintsnapshot yang berisi detail tentang elemen terbesar yang dirender untuk interaksi tersebut sejauh ini (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. Mengukur Largest Contentful Paint (LCP) untuk navigasi ringan SPA
Navigasi semu ditampilkan oleh browser saat interaksi pengguna tepercaya menyebabkan perubahan URL yang terlihat dan contentful paint. Untuk menghitung LCP untuk setiap navigasi aplikasi web satu halaman:
- Amati entri
'soft-navigation'dan'interaction-contentful-paint'denganbuffered: true. - WAJIB: Saat entri
'soft-navigation'diterima, lakukan seeding kandidat LCP awalnya dengan memanggilsoftNavEntry.getLargestInteractionContentfulPaint(). Jika aplikasi merender konten sebelum memperbarui URL (misalnya, merender tampilan, lalu memanggilhistory.pushState()), entriinteraction-contentful-paintawal tersebut akan ditampilkan sebelum entrisoft-navigationdan masih membawanavigationIdsebelumnya. - WAJIB: Korelasikan entri
'interaction-contentful-paint'berikutnya dengan navigasi tidak kentara yang aktif dengan mencocokkanentry.interactionId === softNavEntry.interactionId, JANGAN cocokkan denganentry.navigationId. PenggunaaninteractionIdmemastikan Anda menyertakan render yang terjadi sebelum pembaruan URL dan mengecualikan render dari interaksi yang tidak terkait di rute baru.
/**
* 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. Mengukur Core Web Vitals di seluruh navigasi ringan dengan web-vitals
Jika sasaran Anda adalah melaporkan Core Web Vitals standar (LCP, CLS, dan INP) di seluruh navigasi hard dan soft ke endpoint analisis, gunakan library web-vitals (v6.0.0+) dengan { reportSoftNavs: true }, bukan membagi setiap linimasa metrik secara manual.
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 });
Praktik terbaik
- LAKUKAN deteksi fitur
'interaction-contentful-paint'dan'soft-navigation'menggunakanPerformanceObserver.supportedEntryTypes?.includes(...)sebelum memanggilobserver.observe(). - LAKUKAN penerusan
{ buffered: true }saat mengamati'interaction-contentful-paint'dan'soft-navigation'agar entri awal tidak terlewat jika kode analisis diinisialisasi secara asinkron. - LAKUKAN panggilan
softNavEntry.getLargestInteractionContentfulPaint()saat menangani entrisoft-navigationbaru sehingga render yang terjadi sebelum update URL disertakan dalam LCP. - LAKUKAN pencocokan entri
interaction-contentful-paintdengan navigasi sederhana menggunakaninteractionId, bukannavigationId. - LAKUKAN penggunaan
entry.duration(atauentry.presentationTime - entry.startTime) untuk mengukur latensi render interaksi, bukan melaporkanentry.presentationTimesecara langsung. Semua stempel waktuPerformanceEntrydiukur dari origin waktu pemuatan halaman penuh awal, bukan dari awal navigasi ringan. - JANGAN mengandalkan
performance.getEntriesByType('soft-navigation')untuk memantau aplikasi web satu halaman yang berjalan lama, karena buffer browser dibatasi hingga 50 entri navigasi ringan pertama.
Strategi penggantian
Jika target Dasar Pengukuran Anda mencakup browser yang tidak mendukung InteractionContentfulPaint atau PerformanceSoftNavigation, perlakukan API ini sebagai progressive enhancement untuk Real User Monitoring (RUM):
- Deteksi fitur dan degradasi halus: Lindungi pendaftaran observer dengan
PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint')danPerformanceObserver.supportedEntryTypes?.includes('soft-navigation'). Di browser yang tidak mendukung, lewati pendaftaran observer agar aplikasi Anda berjalan tanpa menampilkan error. - Tetap aktifkan observer navigasi keras standar: Terus amati entri
'largest-contentful-paint','event', dan'layout-shift'standar di semua browser. KarenaLargestContentfulPaintdiselesaikan pada interaksi pengguna pertama, terlepas dari dukungan navigasi ringan, mengukur navigasi ringan di browser yang mendukung tidak akan mengubah atau merusak pengukuran LCP pemuatan berat awal.