[{"data":1,"prerenderedAt":329},["ShallowReactive",2],{"DefaultLayouten":3,"language-blog-slug-lessons-learned-debugging-inp-i18n-slugs":134,"language-blog-slug-en-lessons-learned-debugging-inp":141},{"app":4,"menu":31,"footer":66},{"githubUrl":5,"youtubeUrl":6,"linkedinUrl":7,"phoneNumber":8,"emailAddress":9,"legal":10,"addresses":20},"https:\u002F\u002Fgithub.com\u002Fvoorhoede\u002F","https:\u002F\u002Fwww.youtube.com\u002Fchannel\u002FUCzHuhQVYFRixtQN2-swcuGg","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fde-voorhoede","+31 20 2610 954","post@voorhoede.nl",[11,14,17],{"title":12,"value":13},"KvK","56017235",{"title":15,"value":16},"BTW","NL851944620B01",{"title":18,"value":19},"IBAN","NL14TRIO0320142078",[21,26],{"address":22,"city":23,"googleMapsLink":24,"postalCode":25},"Koivistokade 70","Amsterdam","https:\u002F\u002Fwww.google.com\u002Fmaps\u002Fplace\u002FDe+Voorhoede+%7C+Front-end+Development\u002F@52.396847,4.8700823,17z\u002Fdata=!3m1!4b1!4m5!3m4!1s0x47c5e21d502d2d59:0xbf570944a96ebf45!8m2!3d52.347647!4d4.8502154","1013 BB",{"address":27,"city":28,"googleMapsLink":29,"postalCode":30},"Koornmarkt 22","Delft","https:\u002F\u002Fwww.google.nl\u002Fmaps\u002Fplace\u002FKoornmarkt+22,+2611+EG+Delft\u002F@52.0093477,4.3573054,17z\u002F","2611 EG",{"title":32,"callToActions":33,"links":39},"Site Menu",[34],{"id":35,"title":36,"link":37},"163140902","Contact",{"__typename":38},"ContactRecord",[40,46,51,56,61],{"id":41,"title":42,"link":43},"163140904","Impact",{"__typename":44,"slug":45},"PageRecord","impact",{"id":47,"title":48,"link":49},"163140905","Services",{"__typename":50},"ServiceOverviewRecord",{"id":52,"title":53,"link":54},"163140906","Cases",{"__typename":55},"CaseOverviewRecord",{"id":57,"title":58,"link":59},"163140908","About us",{"__typename":44,"slug":60},"about-us",{"id":62,"title":63,"link":64},"d6WdFJq2SOuc3dWtpibbXQ","Work at",{"__typename":44,"slug":65},"work-at",{"links":67,"copyrightTitle":93,"copyrightLabel":94,"copyrightLink":95,"privacyTitle":96,"privacyLabel":97,"privacyLink":98,"certificatesGrid":99},[68,71,74,77,82,85,88],{"id":69,"title":42,"link":70},"144185264",{"__typename":44,"slug":45},{"id":72,"title":48,"link":73},"144185265",{"__typename":50},{"id":75,"title":53,"link":76},"144185266",{"__typename":55},{"id":78,"title":79,"link":80},"144185267","Blog",{"__typename":81},"BlogPostOverviewRecord",{"id":83,"title":58,"link":84},"144185268",{"__typename":44,"slug":60},{"id":86,"title":36,"link":87},"144185269",{"__typename":38},{"id":89,"title":90,"link":91},"144185270","FAQ",{"__typename":44,"slug":92},"faq","Creative Commons licence and disclaimer","CC BY 4.0","https:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby\u002F4.0\u002F","De Voorhoede privacy statement (pdf)","Privacy statement","https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1763455455-vh-isms-006-privacy-statement-de-voorhoede-en.pdf",[100,112,123],{"id":101,"image":102,"link":107},"Xq4bBfg_TZ6Fkjax9mkbLQ",{"url":103,"alt":104,"width":105,"height":106},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1687353463-b-corp-logo-black-rgb.png","B Corp logo",404,680,{"__typename":108,"id":109,"title":110,"url":111},"ExternalLinkRecord","fGW1ak8XQYaYDLkBSyncog","B Corp","https:\u002F\u002Fwww.bcorporation.net\u002Fen-us\u002Ffind-a-b-corp\u002Fcompany\u002Fde-voorhoede\u002F",{"id":113,"image":114,"link":119},"c5mCXRTiSraRIB25fw1p7Q",{"url":115,"alt":116,"width":117,"height":118},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1687353461-dda-boxlogo-black.png","Dutch Digital Agencies logo",627,480,{"__typename":108,"id":120,"title":121,"url":122},"P6Jh7B0cTv2cKyNEeKVWVQ","Dutch Digital Agencies","https:\u002F\u002Fdutchdigitalagencies.com\u002Fleden\u002Fde-voorhoede\u002F",{"id":124,"image":125,"link":129},"MT5SCyNxSTSr_v5eeATMZw",{"url":126,"alt":127,"width":128,"height":128},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1775730283-dnv.png","DNV logo",518,{"id":130,"title":131,"link":132},"BRtNB5HnT5i-7HkA8IYzBw","DIV",{"__typename":44,"slug":133},"impact\u002Fdigitale-producten-privacy-by-design",[135,138],{"locale":136,"value":137},"en","lessons-learned-debugging-inp",{"locale":139,"value":140},"nl","lessen-debuggen-inp",{"page":142},{"slug":137,"i18nSlugs":143,"social":146,"title":151,"subtitle":79,"isArchived":152,"headerIllustration":153,"date":158,"authors":159,"introTitle":168,"items":169,"pivots":261,"relatedBlogPosts":274,"tags":297,"onMountedScript":162,"onUnmountedScript":162},[144,145],{"locale":136,"value":137},{"locale":139,"value":140},{"title":147,"description":148,"image":149},"Lessons learned debugging Interaction to Next Paint (INP)","INP is a new performance metric significantly affetcing your search ranking. But how can we measure and debug in order to improve our website's INP?",{"url":150},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1723473873-linkedin-blog-8.jpg"," Lessons learned debugging Interaction to Next Paint (INP)",false,{"url":154,"alt":155,"width":156,"height":157},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1723473721-20240808-voorhoede-perf.svg",null,673,460,"2024-08-16T09:46:11.712+02:00",[160],{"name":161,"lastName":162,"slug":163,"image":164},"Declan","","declan",{"url":165,"alt":155,"width":166,"height":167},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1683534636-placeholder.jpg",1235,1646,"Interaction to Next Paint (INP) is a new performance metric significantly affecting your search ranking (SERP). But how does it work, and how can we measure and debug it to improve our website's INP?",[170,174,185,197,200,204,208,213,216,225,228,231,234,237,241,244,247,251,254,257],{"__typename":171,"id":172,"title":162,"body":173},"TextSectionRecord","Ux6lB9BrTk-XvpJR0wajsQ","\u003Cp>\u003Cspan style=\"font-weight: 400;\">Core Web Vitals is an initiative by Google that uses a set of metrics to measure user experience on a website. As of March 2024, Interaction to Next Paint (INP) has replaced First Input Delay (FID) as a Core Web Vital to measure the responsiveness of a website. \u003C\u002Fspan>\u003Cspan style=\"font-weight: 400;\">Where FID measures the input delay of the first interaction with a page, INP considers all interactions within a user session.\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>Below is an image from Google illustrating what an &ldquo;interaction&rdquo; and a &ldquo;paint&rdquo; is, along with a video demonstrating examples of good and poor responsiveness:\u003C\u002Fp>",{"__typename":175,"id":176,"image":177,"caption":182,"fullWidth":183,"captionPosition":184},"ImageRecord","JsvzjdAxQUmxJWazvk0-kQ",{"url":178,"alt":179,"width":180,"height":181},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1723467964-interaction-diagram.svg","A diagram of the interaction phases: blocking tasks, input delay, processing time, render, paint, frame presented",1030,272,"The lifecycle of an interaction leading eventually to a frame being painted. Source: web.dev",true,"bottom",{"__typename":186,"id":187,"mute":152,"loop":183,"autoplay":183,"caption":188,"video":189,"gif":155},"ResponsiveVideoRecord","YjiOpHyaRFKSV2vJOOGDwQ"," A video showing good responsiveness on the right and poor responsiveness on the left. Source: web.dev",{"url":190,"title":191,"height":192,"width":193,"provider":194,"providerUid":195,"thumbnailUrl":196},"https:\u002F\u002Fvimeo.com\u002F997749127","Poor vs good responsiveness",110,426,"vimeo","997749127","https:\u002F\u002Fi.vimeocdn.com\u002Fvideo\u002F1913884399-c46b54d5f71907c4195e46305e5878dfae2b3216c44ce9e0cc10ef5df861e175-d_295x166",{"__typename":171,"id":198,"title":162,"body":199},"PMyJKH7PQwmVQusudiAkyw","\u003Cp>INP is designed to be a more reliable metric for capturing a website&rsquo;s responsiveness when a user interacts with it. Given that INP is based on real user interactions, I wanted to set up real user monitoring (RUM) around INP. Here&rsquo;s what I discovered.\u003C\u002Fp>",{"__typename":171,"id":201,"title":202,"body":203},"YZnGYageSgGbR1XdrKbg2g","How Interaction to Next Paint is different from other metrics","\u003Cp>The other Core Web Vitals, such as Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS), are primarily first-load metrics, meaning they measure the initial page impression. Since INP measures real user interactions during a session, lab measurement becomes much more challenging. INP relies on user interaction, so users must interact with the website to measure INP. It is difficult to trigger all possible interactions in synthetic tests, and manually triggering every potential interaction is time-consuming. Therefore, using RUM data to monitor INP values makes sense. You can then attempt to reproduce those slow interactions in a controlled environment.\u003C\u002Fp>\n\u003Cp>While there are excellent tools available for performance monitoring that also measure INP in the field, my goal was to build my own to gain a deeper understanding of INP and how to measure it.\u003C\u002Fp>",{"__typename":171,"id":205,"title":206,"body":207},"AgQqeIYwTqiGZImFnqttHA","How to measure INP","\u003Cp>\u003Cspan style=\"font-weight: 400;\">INP is measured using the \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FPerformanceEventTiming\" target=\"_blank\" rel=\"noopener\">Event Timing API\u003C\u002Fa> and specifically the \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FPerformanceEventTiming\" target=\"_blank\" rel=\"noopener\">Performance Event Timing\u003C\u002Fa> interface.\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan style=\"font-weight: 400;\">We can load a script on every page with something like this:\u003C\u002Fspan>\u003C\u002Fp>",{"__typename":209,"id":210,"language":211,"body":212},"CodeBlockRecord","A_8KvhqKRoGgcNbYMn7VBQ","js","let largestINPValue = 0;\n\nnew PerformanceObserver(list => {\n  for (const entry of list.getEntries()) {\n    if (entry.duration > largestINPValue) {\n      largestINPValue = entry.duration;\n      console.log(`[INP] duration: \n        ${entry.duration},\n        type: ${entry.name}`,\n        entry\n      );\n    }\n  }\n}).observe({\n  type: 'event',\n  buffered: true\n});",{"__typename":171,"id":214,"title":162,"body":215},"ZYs341rESNGeyt3KDagd8Q","\u003Cp>Now, the INP value will be logged to the console on any interaction larger than the previously recorded INP value. We do this to avoid overwhelming ourselves with INP interactions. You could also implement a system to report values that are considered \"poor\" or \"in need of improvement.\"\u003C\u002Fp>\n\u003Cp>Below is a screen recording showing INP values being logged to the console:\u003C\u002Fp>",{"__typename":186,"id":217,"mute":152,"loop":183,"autoplay":183,"caption":162,"video":218,"gif":155},"CmEHa_ZQQmWSyH7Jk4fZ5A",{"url":219,"title":220,"height":221,"width":222,"provider":194,"providerUid":223,"thumbnailUrl":224},"https:\u002F\u002Fvimeo.com\u002F997749868","A screen recording of INP values being logged to the console",240,308,"997749868","https:\u002F\u002Fi.vimeocdn.com\u002Fvideo\u002F1913885254-d412bac84d89ecdfc4deeb0f502d802ec12390f667c17ea263ac70b1354fc1fc-d_295x166",{"__typename":171,"id":226,"title":162,"body":227},"VVIA3WwWRCOAUm90ej_-Qg","\u003Cp>Another useful tool for tracking down and resolving INP issues is the Long Animation Frame API (LoAF). The LoAF can provide detailed information on how time was spent and which script was responsible for the INP value. It looks something like this:\u003C\u002Fp>",{"__typename":209,"id":229,"language":211,"body":230},"ZnAa1XVrTNiJGvbwE4fzoA","const observer = new PerformanceObserver((list) => {\n  console.log(list.getEntries());\n});\n\nobserver.observe({ type: \"long-animation-frame\", buffered: true });",{"__typename":171,"id":232,"title":162,"body":233},"CJqArgPUSla4ytVrHmgmVQ","\u003Cp>The challenge here is that we need to measure both INP and long animation frames to match the INP entry with the LoAF entry and obtain attribution data for the INP event.\u003C\u002Fp>\n\u003Cp>If you prefer not to delve into the underlying Web APIs and just want a working solution, the npm package \u003Ccode>web-vitals\u003C\u002Fcode> simplifies the implementation. Particularly, connecting the INP data to its attribution can be cumbersome, so \u003Ccode>web-vitals\u003C\u002Fcode> is highly beneficial here. With attribution, it would look something like this:\u003C\u002Fp>",{"__typename":209,"id":235,"language":211,"body":236},"GqkRq9EBRy-yd2ZNwoXUkw","import { onINP } from 'web-vitals\u002Fattribution';\n\nonINP((data) => {\n  console.log(`\n    [INP] duration:\n    ${data.value},\n    type: ${data.entries[0].name}`,\n    data.entries[0]\n  );\n}, {reportAllChanges: true});",{"__typename":171,"id":238,"title":239,"body":240},"DNXqZWqMQFi1rCMk7JXtqA","Gathering Real User Metrics (RUM) data","\u003Cp>Instead of logging the data to the console, we can send it to our analytics provider or wherever you prefer to store the information. Since we want to collect INP values per page and per user, we&rsquo;ll send the page URL along with a unique identifier for the user. These will be useful when processing the data later.\u003C\u002Fp>\n\u003Cp>\u003Cspan style=\"font-weight: 400;\">Our `reportINP` implementation looks something like this:\u003C\u002Fspan>\u003C\u002Fp>",{"__typename":209,"id":242,"language":211,"body":243},"erX54W42SF-SYAVI6wMcfQ","import { onINP } from 'web-vitals\u002Fattribution';\n\nlet sessionId = sessionStorage.getItem('session-id');\nif (!sessionId) {\n  sessionId = uuid();\n  sessionStorage.setItem('session-id', sessionId);\n};\n\t\nonINP(({.value. attribution }) => {\n  reportINP({\n    value,\n    attribution,\n    sessionId,\n  })\n}, {reportAllChanges: true});",{"__typename":171,"id":245,"title":162,"body":246},"OrSc9LdBTUGmWHB2idXM7w","\u003Cp>\u003Cspan style=\"font-weight: 400;\">All looks great, but there is a catch like always...\u003C\u002Fspan>\u003C\u002Fp>",{"__typename":171,"id":248,"title":249,"body":250},"Ez4olR2cQt6_cyKZDM6ddA","Considerations","\u003Cp>\u003Cspan style=\"font-weight: 400;\">While in the process of starting to gather RUM data I wanted to implement this on our own website. While setting it up and logging into the console I found a couple of things to consider:\u003C\u002Fspan>\u003C\u002Fp>\n\u003Ch3>\u003Cspan style=\"font-weight: 400;\">Browser support\u003C\u002Fspan>\u003C\u002Fh3>\n\u003Cp>\u003Cspan style=\"font-weight: 400;\">Measuring INP requires the PerformanceEventTiming API\u003C\u002Fspan>\u003Cspan style=\"font-weight: 400;\">. This web API is not supported in Safari meaning there is no way to measure INP in Safari. That means that in the Netherlands around 24% of your users will not be included in your test results. I do think that if you collect enough data you will still capture a good understanding of the overall responsiveness of your website.\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>It's also worth noting that the other Core Web Vitals, such as Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS), are also measured using Web APIs not supported in Safari. Thus, it&rsquo;s impossible to collect RUM data on non-Chromium browsers using these APIs.\u003C\u002Fp>\n\u003Ch3>\u003Cspan style=\"font-weight: 400;\">A note on single-page applications or hybrid applications\u003C\u002Fspan>\u003C\u002Fh3>\n\u003Cp>Our website is built with Nuxt, and some of you may be familiar with Next.js, which is wildly popular among web developers. Frameworks like these typically serve a fully rendered HTML page on first load, but then function as a single-page application (SPA) for subsequent pages. This behaviour is known as soft navigation, where the application only loads the additional resources needed for the next page and rewrites the URL in the browser. All performance metrics are measured relative to the top-level page navigation, meaning that if we use soft navigations, the metrics won&rsquo;t reset per page, making it challenging to obtain core web vitals RUM data per page.\u003C\u002Fp>\n\u003Cp>\u003Cspan style=\"font-weight: 400;\">The future looks bright though! To solve these challenges, there is a \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FWICG\u002Fsoft-navigations\u002Fissues\" target=\"_blank\" rel=\"noopener\">soft navigation specification\u003C\u002Fa> being worked on. Additionally, it will soon be possible to report soft navigations using performance observers. It will look something like this, kindly borrowed from \u003Ca href=\"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fweb-platform\u002Fsoft-navigations-experiment\" target=\"_blank\" rel=\"noopener\">Experimenting with measuring soft navigations\u003C\u002Fa>:\u003C\u002Fspan>\u003C\u002Fp>",{"__typename":209,"id":252,"language":211,"body":253},"VblAE2qASFCAMTKJN5Uabg","const observer = new PerformanceObserver(console.log);\nobserver.observe({ type: \"soft-navigation\", buffered: true });",{"__typename":171,"id":255,"title":162,"body":256},"GxwBU2eTTQSQ3tCXLYa8uA","\u003Cp>\u003Cspan style=\"font-weight: 400;\">Note that there is also a \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FGoogleChrome\u002Fweb-vitals\u002Ftree\u002Fsoft-navs\" target=\"_blank\" rel=\"noopener\">`soft-navs` branch in the web-vitals repo\u003C\u002Fa>&nbsp;that already implements reporting soft navigations.\u003C\u002Fspan>\u003C\u002Fp>",{"__typename":171,"id":258,"title":259,"body":260},"H6HacApqQ9yL4cHWMfgwqw","Conclusion","\u003Cp>\u003Cspan style=\"font-weight: 400;\">At the time of writing, calculating RUM data with web APIs intended to do so is harder than you might think, especially when using popular hybrid rendering web frameworks. It is also important to note when looking at RUM data collected using these API that this data is not telling the whole story. It probably does not take client-side routed page visits into account.\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan style=\"font-weight: 400;\">If you wish to collect RUM data around INP today and your application uses soft navigations, I recommend not focusing on INP at the page level. Instead, define a user journey on your website that is crucial to both you and your users and involves extensive JavaScript. Measure INP for that user journey instead. This approach shifts the focus from Core Web Vitals scores to the most critical user journeys within your web application, which, in my opinion, is a better strategy anyway. Concentrating on uninterrupted user journeys will naturally lead to improved Core Web Vitals, and now we know how to measure them effectively!\u003C\u002Fspan>\u003C\u002Fp>",[262],{"title":263,"body":264,"links":265,"mailchimpValue":162,"mailchimpName":162,"mailchimpId":162,"formType":273,"contactPerson":155},"Would you like to improve your page speed?","\u003Cp>Read more about our web performance optimisation service.\u003C\u002Fp>\n",[266],{"__typename":267,"id":268,"title":269,"link":270},"InternalLinkRecord","aQjogsErRGynbl03g63KZw","More about web performance",{"__typename":271,"slug":272},"ServiceRecord","performance-review","none",[275,283,290],{"slug":276,"title":277,"date":278,"authors":279},"front-end-at-the-edge","Front-end at the Edge","2022-10-04T02:00:00.000+02:00",[280],{"name":281,"image":282},"Vera",{"url":165,"alt":155,"width":166,"height":167},{"slug":284,"title":285,"date":286,"authors":287},"how-to-multilingual-website-rtl-html-css","How to make your multilingual website suitable for RTL with only HTML and CSS","2022-06-13T02:00:00.000+02:00",[288],{"name":161,"image":289},{"url":165,"alt":155,"width":166,"height":167},{"slug":291,"title":292,"date":293,"authors":294},"load-sentry-asynchronously-only-on-error","Load Sentry asynchronously only on error","2021-01-22T01:00:00.000+01:00",[295],{"name":161,"image":296},{"url":165,"alt":155,"width":166,"height":167},[298],{"id":299,"title":300,"slug":301,"blogPosts":302},"b-HOCOQTRJKMsff0UxhDcg","Web performance ","web-performance",[303,314,325],{"slug":304,"title":305,"date":306,"authors":307},"reclaiming-digital-sovereignty-on-european-infrastructure","Reclaiming Digital Sovereignty on European Infrastructure","2026-01-29T12:02:28.591+01:00",[308],{"name":309,"image":310},"Luuk",{"url":311,"alt":155,"width":312,"height":313},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1721036156-luuk-edit-edit.jpg",2198,2969,{"slug":315,"title":316,"date":317,"authors":318},"how-close-to-the-user-should-your-code-be","How close to the user should your code be?","2025-10-30T10:07:10.411+01:00",[319],{"name":320,"image":321},"Jasper",{"url":322,"alt":155,"width":323,"height":324},"https:\u002F\u002Fwww.datocms-assets.com\u002F6524\u002F1683535518-jasper.jpg",1892,2523,{"slug":276,"title":277,"date":278,"authors":326},[327],{"name":281,"image":328},{"url":165,"alt":155,"width":166,"height":167},1776256139077]