Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 3 min read

Find the Click That's Failing INP: web-vitals Attribution in 20 Lines

Search Console says your mobile pages "need improvement" on INP. PageSpeed Insights agrees. Neither tells you which button is slow. That's the annoying part of Interaction to Next Paint. LCP points at an element. CLS po

Search Console says your mobile pages "need improvement" on INP. PageSpeed Insights agrees. Neither tells you which button is slow.

That's the annoying part of Interaction to Next Paint. LCP points at an element. CLS points at what moved. INP gives you a number, and the interaction behind it happened on some real visitor's phone, not in your lab test.

The fix is to let real visitors tell you. The web-vitals library has an attribution build that reports which element was interacted with and where the time went. Here's the setup I use on client stores, mostly Shopify themes, though nothing below is Shopify-specific.

The snippet

<script type="module">
  import { onINP } from 'https://unpkg.com/web-vitals@5/dist/web-vitals.attribution.js?module';

  onINP(({ value, rating, attribution }) => {
    const a = attribution;
    const script = a.longestScript?.entry;
    const payload = {
      page: location.pathname,
      inp: Math.round(value),
      rating,                         // 'good' | 'needs-improvement' | 'poor'
      target: a.interactionTarget,    // CSS selector of the element
      type: a.interactionType,        // 'pointer' or 'keyboard'
      loadState: a.loadState,
      inputDelay: Math.round(a.inputDelay),
      processing: Math.round(a.processingDuration),
      presentation: Math.round(a.presentationDelay),
      script: script ? script.sourceURL || script.invoker : null,
      scriptPart: a.longestScript?.subpart ?? null,
    };
    navigator.sendBeacon('/your-endpoint', JSON.stringify(payload));
  });
</script>

Swap /your-endpoint for wherever you collect data. A tiny serverless function writing to a sheet is plenty. For a first look, console.log(payload) and click around on a throttled phone works too.

Pin the version in production rather than pulling @5 from a CDN on every page view, or bundle it if you have a build step.

Reading the three numbers

INP is split into three parts, and each one points somewhere different.

Input delay is high. The main thread was busy when the visitor tapped, so their tap waited in line. That's usually something unrelated to the button: a third-party script parsing on load, a chat widget booting, an analytics tag. Check loadState here. If it says the page was still loading, the problem is what runs during load, not the click handler.

Processing is high. Your own event handlers are slow. On stores, I most often see this on the variant picker and add-to-cart: one handler re-renders the price, the images, the stock message and the sticky bar, all synchronously. Split the urgent update (the button state) from the rest, and push the rest to after the next paint.

Presentation delay is high. The handler finished, but the browser took ages to draw the result. Big DOM changes, layout thrashing, or swapping a whole section's HTML at once. On Shopify themes, this is the section rendering pattern that replaces a large block of markup after a variant change.

The field that saves the most time

longestScript comes from the Long Animation Frames API and tells you which script was running longest during the slow interaction. sourceURL is the file. When it's an app's script, you've just found your answer without opening a single performance trace.

It won't always be filled in (browser support and cross-origin scripts limit it), so treat it as a bonus, not the only signal.

A practical example

Say your data shows something like this after a day:

target: "button.product-form__submit"   inp: 412   inputDelay: 290   processing: 70   presentation: 52   loadState: "dom-content-loaded"

412 ms needs improvement (good is 200 ms or less). But look where the time is: most of it is input delay, and the page hadn't finished loading. The add-to-cart handler itself is fine. What's happening is people tap "Add to cart" quickly, while something else is still booting.

That changes the fix completely. Rewriting the cart code would do nothing. Delaying the review widget or the chat script until after load, or until the visitor scrolls, would.

Group before you fix

Don't chase single reports. Group by target and look at the 75th percentile, since that's the figure Google uses for Core Web Vitals. One element usually stands out, and it's rarely the one you'd have guessed.

Once it's fixed, leave the snippet running. INP regressions tend to arrive with the next app install, not with your own code.

If you're working on a Shopify theme and want help digging into this, it's part of what we do in Shopify theme development.

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.