Blog

Dingen die ik leerde bij het debuggen van Interaction to Next Paint (INP)

Interaction to Next Paint (INP) is een nieuwe performance indicator die een aanzienlijke invloed heeft op je zoekresultaten (SERP). Maar hoe werkt het en hoe kunnen we het meten en debuggen om de INP van onze website te verbeteren?

Core Web Vitals is een initiatief van Google dat een reeks statistieken gebruikt om de gebruikerservaring op een website te meten. Vanaf maart 2024 vervangt Interaction to Next Paint (INP) de First Input Delay (FID) als een Core Web Vital om de responsiviteit van een website te meten. Waar FID de invoervertraging van de eerste interactie met een pagina mat, neemt INP alle interacties binnen een gebruikerssessie in overweging.

Zie hieronder een afbeelding van Google die illustreert wat een "interactie" en een "paint" is, samen met een video van een voorbeeld van goede en slechte responsiviteit:

De levenscyclus van een interactie die uiteindelijk leidt tot het renderen van een frame.
De levenscyclus van een interactie die uiteindelijk leidt tot het renderen van een frame Bron: web.dev

INP zou een betrouwbaardere statistiek moeten zijn om de responsiviteit van een website tijdens gebruikersinteracties vast te leggen. Omdat INP gaat over echte gebruikersinteracties, wilde ik real-user monitoring (RUM) instellen rond INP. Dit is wat ik heb ontdekt.

Hoe Interaction to Next Paint verschilt van andere statistieken

De andere Core Web Vitals, zoals Largest Contentful Paint en Cumulative Layout Shift, zijn voornamelijk statistieken voor de eerste laadtijd, wat betekent dat ze de eerste indruk van een pagina meten. Aangezien INP echte gebruikersinteracties tijdens een sessie meet, is het veel moeilijker om INP in een gecontroleerde, gesimuleerde omgeving te meten. INP is afhankelijk van gebruikersinteractie, wat betekent dat een gebruiker op de website moet klikken, typen of scrollen om INP te meten. Het is lastig om alle mogelijke interacties in synthetische tests te triggeren, en handmatig alle mogelijke interacties uitvoeren kost veel tijd. Daarom is het logisch om Real User Monitoring (RUM)-gegevens te gebruiken om INP-waarden te monitoren. Later kun je proberen die langzame interacties in een gecontroleerde omgeving te reproduceren.

Er zijn uitstekende tools beschikbaar voor performance monitoring die ook INP in de praktijk meten, maar het doel hier was om mijn eigen tool te bouwen om een beter begrip te krijgen van INP en hoe je dit kunt meten.

Laten we dus eens kijken naar het meten van INP in de praktijk.

Hoe meet je INP?

INP wordt gemeten met behulp van de Event Timing API en specifiek de Performance Event Timing interface.

We kunnen een script op elke pagina laden, zoals dit:

let largestINPValue = 0;

new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.duration > largestINPValue) {
      largestINPValue = entry.duration;
      console.log(`[INP] duration: 
        ${entry.duration},
        type: ${entry.name}`,
        entry
      );
    }
  }
}).observe({
  type: 'event',
  buffered: true
});

Nu zal de INP-waarde worden gelogd in de console bij elke interactie die groter is dan de eerder geregistreerde INP-waarde. Dit doen we om te voorkomen dat we onszelf overladen met INP-interacties. We zouden hier ook iets kunnen doen met het rapporteren van waarden die als "slecht" of "verbetering nodig" worden beschouwd.

Zie hieronder een schermopname van INP-waarden die in de console worden gelogd:

Wat ook handig is voor het opsporen en daadwerkelijk oplossen van INP-problemen, is de Long Animation Frame API (LoAF). De LoAF geeft je gedetailleerde informatie over hoe de tijd is besteed en welk script verantwoordelijk was voor de INP-waarde. Dat zou er als volgt uitzien:

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: "long-animation-frame", buffered: true });

Het lastige hier is dat we zowel INP als lange animatieframes moeten meten om de INP-entry met de LoAF-entry te matchen om de toewijzingsgegevens voor de INP-entry te krijgen.

Als je niet in de details van de onderliggende Web API's wilt duiken en gewoon wilt dat dit werkt, is er het geweldige npm-pakket web-vitals, dat de implementatie voor je vereenvoudigt. Vooral het koppelen van de INP-gegevens aan hun toewijzing is een beetje omslachtig, dus web-vitals is hier echt handig. Het bovenstaande, met toewijzing, zou er als volgt uitzien:

import { onINP } from 'web-vitals/attribution';

onINP((data) => {
  console.log(`
    [INP] duration:
    ${data.value},
    type: ${data.entries[0].name}`,
    data.entries[0]
  );
}, {reportAllChanges: true});

Real User Metrics (RUM) verzamelen

In plaats van de gegevens naar de console te loggen, kunnen we ze nu naar onze analyticsprovider sturen of waar je de gegevens ook maar wilt opslaan. Omdat we de INP-waarden per pagina en per gebruiker willen verkrijgen, sturen we met de pagina URL een unieke identificatiecode mee voor de gebruiker. Deze zijn handig wanneer we de gegevens later verwerken.

Onze reportINP-implementatie zou er ongeveer zo uitzien:

import { onINP } from 'web-vitals/attribution';

let sessionId = sessionStorage.getItem('session-id');
if (!sessionId) {
  sessionId = uuid();
  sessionStorage.setItem('session-id', sessionId);
};
	
onINP(({.value. attribution }) => {
  reportINP({
    value,
    attribution,
    sessionId,
  })
}, {reportAllChanges: true});

Alles ziet er goed uit, maar zoals altijd is er een addertje onder het gras...

Overwegingen

Tijdens het verzamelen van RUM-gegevens wilde ik dit op onze eigen website implementeren. Bij het instellen en inloggen in de console kwam ik een paar zaken tegen om rekening mee te houden:

Browserondersteuning

Het meten van INP vereist de PerformanceEventTiming API. Deze web-API wordt niet ondersteund in Safari, wat betekent dat het niet mogelijk is om INP in Safari te meten. Dat betekent dat in Nederland ongeveer 24% van je gebruikers niet in je testresultaten zal worden opgenomen. Ik denk echter dat als je voldoende gegevens verzamelt, je nog steeds een goed inzicht krijgt in de algehele responsiviteit van je website. Het is ook belangrijk om te weten dat de andere core web vitals, Largest Contentful Paint en Cumulative Layout Shift, ook worden gemeten met behulp van web-API's die niet worden ondersteund in Safari, wat betekent dat het niet mogelijk is om RUM-gegevens te verzamelen in niet-Chromium browsers met de web-API's die bedoeld zijn voor deze metingen.

Een opmerking over single-page applications of hybride toepassingen

Onze website is gebouwd met Nuxt, en sommigen van jullie hebben misschien gehoord van Next.js, dat enorm populair is bij webontwikkelaars. Frameworks zoals deze serveren de gebruiker een volledig gerenderde HTML-pagina bij de eerste keer laden, maar gedragen zich daarna als een single-page application voor elke volgende pagina. Dit gedrag wordt "soft navigation" genoemd. In plaats van de hele pagina op te vragen via een URL, laadt de applicatie alleen de extra benodigde bronnen voor de volgende pagina. De URL in de browser wordt daarbij herschreven. Alle performance statistieken worden gemeten ten opzichte van de top-level paginanavigatie, wat betekent dat als we gebruikmaken van soft navigations, de statistieken niet per pagina worden gereset, waardoor het erg moeilijk wordt om core web vitals RUM-gegevens per pagina te verkrijgen.

De toekomst ziet er echter rooskleurig uit! Om deze uitdagingen op te lossen, wordt er gewerkt aan een soft navigation specificatie. Daarnaast zal het mogelijk worden om soft navigations te rapporteren met behulp van performance observers. Het zou er als volgt uitzien, geleend van Experimenting with measuring soft navigations:

const observer = new PerformanceObserver(console.log);
observer.observe({ type: "soft-navigation", buffered: true });

Let ook op dat er een soft-navs branch in de web-vitals repository bestaat, waarin het rapporteren van soft navigations al is geïmplementeerd.

Conclusie

Op het moment van schrijven blijkt het berekenen van RUM-gegevens met de daarvoor bedoelde web-API's moeilijker dan je zou denken, vooral bij het gebruik van populaire hybride rendering webframeworks. Het is ook belangrijk om te onthouden dat de RUM-gegevens die met deze API's worden verzameld, waarschijnlijk niet het volledige verhaal vertellen. Ze houden mogelijk geen rekening met paginabezoeken die client-side worden afgehandeld.

Als je vandaag RUM-gegevens rondom INP wilt verzamelen en je applicatie maakt gebruik van soft navigations, zou ik aanraden om niet naar INP op paginaniveau te kijken. Richt je in plaats daarvan op het definiëren van een gebruikersreis op je website die cruciaal is voor jou en je gebruikers. Bekijk welke onderdelen daarvan veel JavaScript gebruiken. Meet INP op die gebruikersreis in plaats van op individuele pagina's. Door dit te doen, focus je niet op core web vitals scores, maar op de belangrijkste gebruikersreizen binnen je webapplicatie. Dit is uiteindelijk een betere aanpak. Focus op ononderbroken gebruikersreizen zal uiteindelijk ook leiden tot betere core web vitals, en nu weten we hoe we die kunnen meten!

Gerelateerde blog posts

← Alle blogposts

Wil je de snelheid van jouw website verbeteren?

Lees meer over onze web performance optimalisatie dienst.

Meer over web performance