Technical SEO MasteryCore Web Vitals, mobile-first indexing and security · Lesson 14 of 18
INP deep dive: long tasks, attribution and real-user monitoring
Video lecture
INP deep dive: long tasks, attribution and real-user monitoring
The narrated lecture is in production
Every chapter is scripted and ready. Browse the chapters and read the full transcript now — the video will appear here when it’s published.
Chapters
Transcript of the narration, chapter by chapter.
0:00 INP deep dive
Interaction to Next Paint is the Core Web Vital that sites fail most often on mobile, and it's the hardest to debug. Why? Because it depends on real interactions, on real devices, with real third-party scripts. Your laptop in the office won't reproduce a customer tapping a filter on a mid-range Android phone. In this lecture you'll learn the anatomy of an interaction, the tools that show you what's slow, and how to set up real-user monitoring that names the exact element and script responsible.
0:37 What INP measures
First, what does INP actually measure? It watches interactions during a page visit: clicks, taps and key presses. It reports a value close to the worst one, ignoring some extreme outliers on pages with many interactions. Good is two hundred milliseconds or less at the seventy-fifth percentile. Poor is over five hundred. Think of it like a restaurant. INP isn't the average wait. It's closer to, how long did the slowest table wait for someone to acknowledge them?
1:11 Three phases
Every interaction has three phases. Input delay: the time until your event handlers can even start, because the main thread is busy with something else, like hydration, third-party scripts or timers. Processing duration: your handlers running. And presentation delay: the browser working out styles, layout and paint for the next frame. Here's the key idea. You can't fix INP until you know which phase dominates, because each phase has completely different fixes.
1:42 The toolkit
Now tools. The Chrome DevTools Performance panel lets you record an interaction and see the long tasks around it, and its live metrics show INP for interactions you perform. The Long Animation Frames API, often called LoAF, exposes frames that took more than fifty milliseconds, with script attribution: which script, which function, which source URL. That's far more useful than the older Long Tasks API. The web-vitals library's attribution build reports INP from real users, with the target element, the phase breakdown and those LoAF entries. And CrUX and Search Console tell you that you have a problem, but not why.
2:26 Hands-on: RUM for INP
Hands-on. The lesson text has a small script using the web-vitals attribution build. When INP is reported, it sends a beacon with the value and rating, the interaction target as a CSS selector, whether it was a pointer or keyboard interaction, the three phase durations, the source URLs of scripts from the long animation frames, and the page path. In production, pin and self-host the library rather than loading it from a public CDN, and check the README for field names when you upgrade.
3:03 Aggregate and rank
Then aggregate. On your server or in your analytics warehouse, group by page template and by target, and rank by the seventy-fifth percentile of the value. Now you have a list like: on category pages, taps on the filter chip button are the slowest interaction, mostly processing time, and the scripts involved are our main bundle plus a heatmap tool. That's a ticket a developer can act on. Compare that with, INP is poor, please improve. Night and day.
3:37 Fixes by phase
Fixes by phase. For input delay, reduce what's running when people interact: defer or remove non-essential third-party scripts, load chat widgets only when someone clicks them, split hydration with islands or partial hydration, and avoid long timers and polling. For processing, make handlers cheap: give instant visual feedback, then yield to the main thread before heavy work, using scheduler dot yield with a setTimeout fallback. For presentation delay, keep rendering cheap: smaller DOMs, virtualised long lists, no layout thrashing, and content-visibility auto for off-screen sections.
4:14 Example 1: salon booking calendar
Worked example one, simple. A Birmingham salon's booking page has a date picker that freezes for a moment on older phones. DevTools shows a single long task when the calendar opens, as the script builds a whole year of dates at once. The fix is building only the visible month, and giving instant feedback when the button is tapped. Processing duration drops, and the page feels responsive. Small change, big difference.
4:45 Example 2: Dubai marketplace filters (illustrative)
Worked example two, with illustrative details. A Dubai marketplace has poor mobile INP on category pages. RUM shows the worst interactions are taps on filter chips, dominated by processing time, and LoAF attribution points at the site's own bundle plus a heatmap script loaded through the tag manager. Fixes: the filter handler updates the chip immediately and yields before recomputing the grid; the grid is virtualised; and the heatmap script is sampled to a small share of sessions. Real-user p75 INP for filter taps drops into the good range over the following weeks, later confirmed in CrUX.
5:27 Measures and mistakes
How do you measure success? Real-user p75 INP by template and by top interaction target. The share of interactions rated good. And the CrUX or Search Console status for the URL group after the twenty-eight-day window. Common mistakes: optimising Total Blocking Time in Lighthouse and assuming INP is fixed; collecting INP without attribution, so nobody knows which element is slow; and blaming the framework before checking third-party scripts in the LoAF data.
5:58 Watch me do it: slowest interaction
Watch me do it. I'll find the slowest interaction on a product page with real-user data. Step one: I add the web-vitals attribution snippet to the product template, self-hosted and pinned, sending beacons to our small endpoint. Step two: after a week, I aggregate by target. The top row is button dot add-to-basket, with p75 INP in the poor range. Step three: I look at the phase breakdown. Input delay is small. Processing duration is large. Presentation delay is moderate. So the handler itself is heavy. Step four: the script attribution shows two sources: our main bundle and a third-party personalisation script. Step five: I reproduce it locally. In DevTools, I record a performance trace while clicking add to basket on a throttled CPU. There's a long task: the personalisation script recalculating recommendations on every basket change, synchronously. Step six: the ticket. Update the basket icon immediately, then yield before running recommendations; ask the vendor about an asynchronous mode; and virtualise the mini-basket list. Step seven: after release, the RUM table shows the add-to-basket p75 dropping into good. I confirm with CrUX once the window rolls over.
7:19 Where RUM data goes
Where should you send the RUM data? For a small site, sending INP as a GA4 event with custom parameters for the target and phases works, and you can explore it in GA4 or export it to BigQuery. For larger sites, a dedicated endpoint that writes to your data warehouse, or a commercial RUM product, gives you more control and volume. Either way, sample if traffic is huge, respect consent requirements for analytics in your markets, and never send personal data in the payload. A CSS selector and a script URL are all you need.
8:00 Recap and try this now
Recap. INP captures near-worst interaction latency, split into input delay, processing and presentation. Use DevTools and LoAF to debug, and the web-vitals attribution build for real-user data that names the element and script. Fix the dominant phase. Try this now. Add the attribution snippet to one template for a week, collecting to a simple endpoint or as a GA4 event. Then rank the top five slowest targets and pick one to fix.
Why INP needs its own lesson
Interaction to Next Paint is the Core Web Vital sites fail most often on mobile, and it's the hardest to debug because it depends on real interactions on real devices. Lab tools can't reproduce your users' taps on a mid-range Android phone with twelve third-party scripts loaded. This lesson gives you the model, the tooling and a real-user monitoring (RUM) set-up that tells you which element and which script make interactions slow.
The anatomy of an interaction
INP observes interactions (clicks, taps, key presses) during the page visit and reports a value close to the worst one (for pages with many interactions, high outliers are ignored). Each interaction has three phases:
| Phase | What happens | Typical causes of delay |
|---|---|---|
| Input delay | Time until event handlers can start | Main thread busy with other long tasks (hydration, third-party scripts, timers) |
| Processing duration | Your event handlers run | Heavy handler logic, synchronous state updates, large re-renders |
| Presentation delay | Browser renders the next frame | Large DOM, expensive style/layout, forced synchronous layout |
Good INP is ≤ 200 ms at the 75th percentile; poor is > 500 ms.
Tooling
- Chrome DevTools Performance panel: record an interaction; look at the Interactions track and the long tasks around it. Live metrics show INP for interactions you perform locally.
- Long Animation Frames (LoAF) API: exposes frames that took longer than 50 ms, with script attribution (which script, which function, which source URL) — far more useful than the older Long Tasks API.
- web-vitals library (attribution build): reports INP from real users with the interaction target, phase breakdown and associated LoAF entries.
- CrUX / Search Console: tell you that you have a problem, per URL group, but not why.
Hands-on: RUM for INP with the web-vitals attribution build
<script type="module">
import { onINP } from "https://unpkg.com/web-vitals@5/dist/web-vitals.attribution.js?module";
onINP(({ name, value, rating, attribution }) => {
const body = JSON.stringify({
metric: name, value: Math.round(value), rating,
target: attribution.interactionTarget, // CSS selector of the element
type: attribution.interactionType, // "pointer" or "keyboard"
inputDelay: Math.round(attribution.inputDelay),
processing: Math.round(attribution.processingDuration),
presentation: Math.round(attribution.presentationDelay),
scripts: (attribution.longAnimationFrameEntries || [])
.flatMap(f => f.scripts || []).map(s => s.sourceURL).slice(0, 5),
page: location.pathname,
});
navigator.sendBeacon("/rum", body); // or send to GA4 as an event
});
</script>Pin the library version in production (self-host or bundle it) rather than loading from a public CDN, and check the web-vitals README for the current attribution field names when you upgrade. Aggregate server-side by page template and target, and rank by the 75th percentile of value. The scripts list is where third-party culprits reveal themselves.
Fix patterns by phase
Input delay — reduce what's running when users interact:
- Defer or remove non-essential third-party scripts; load chat widgets on interaction.
- Split hydration (islands/partial hydration) so the whole page doesn't hydrate at once.
- Avoid long-running timers and polling on the main thread.
Processing duration — make handlers cheap and yield:
button.addEventListener("click", async () => {
showSpinner(); // immediate visual feedback
await (globalThis.scheduler?.yield?.() ?? new Promise(r => setTimeout(r, 0)));
const result = expensiveFilter(items); // heavy work after the next paint opportunity
render(result);
});Presentation delay — keep rendering cheap:
- Reduce DOM size; virtualise long lists.
- Avoid layout thrashing (reading
offsetHeightright after writing styles). - Use
content-visibility: autofor off-screen sections.
Worked example: a Dubai marketplace's filter taps
A Dubai marketplace (illustrative) has poor mobile INP on category pages. RUM shows the worst interactions are taps on filter chips (button.filter-chip), with large processing duration, and LoAF script attribution pointing at the site's own bundle plus a tag-manager-loaded heatmap script. Fixes: the filter handler updates the chip state immediately and yields before recomputing the product grid; grid rendering is virtualised; the heatmap script is sampled to a small share of sessions. RUM p75 INP for filter taps drops into the "good" range over the following weeks, later confirmed in CrUX.
Measuring success
- RUM p75 INP by template and by top interaction target.
- Share of interactions rated "good".
- CrUX/Search Console status for the URL group after the 28-day window.
Common mistakes
- Optimising Total Blocking Time in Lighthouse and assuming INP is fixed.
- Collecting INP without attribution, so nobody knows which element is slow.
- Blaming "the framework" before checking third-party scripts in LoAF data.
Key takeaways
- INP reports near-worst interaction latency at p75: ≤ 200 ms good, > 500 ms poor.
- Each interaction splits into input delay, processing duration and presentation delay; fixes depend on the dominant phase.
- Long Animation Frames (LoAF) and the web-vitals attribution build name the element and scripts behind slow interactions.
- Give instant feedback, then yield; defer third parties; shrink the DOM and virtualise lists.
- Measure with RUM by template and target, then confirm in CrUX after the 28-day window.
Check your understanding
Quick questions to lock in the lesson. They don’t count towards your certificate.
Put it into practice
Add the web-vitals attribution snippet to one template for a week, rank the five slowest interaction targets by p75, and write a ticket for the worst one naming its dominant phase and scripts.
Enrol for free to save your progress
Reading is always free. Enrol to keep your place, take the final assessment and earn a verifiable certificate.