[{"data":1,"prerenderedAt":939},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build":931},{"id":4,"title":5,"body":6,"description":912,"extension":913,"meta":914,"navigation":306,"path":925,"seo":926,"stem":929,"__hash__":930},"content\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build\u002Findex.md","Attributing INP to a Component",{"type":7,"value":8,"toc":904},"minimark",[9,14,34,41,46,93,226,230,239,245,251,261,265,270,472,475,481,650,653,663,735,738,758,762,765,768,797,800,804,820,829,838,847,856,865,869,889,894,897,900],[10,11,13],"h1",{"id":12},"how-to-attribute-a-failing-inp-to-the-component-that-caused-it","How to Attribute a Failing INP to the Component That Caused It",[15,16,17,18,23,24,28,29,33],"p",{},"This page extends ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002F","RUM Beacons & Field Data Collection"," inside ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement",". You have field data, your p75 INP is ",[30,31,32],"code",{},"340ms",", and the ticket says \"improve INP\". Nobody knows which interaction is slow, on which route, in which component — so the work stalls or, worse, someone optimises a handler that was never in the sample.",[15,35,36,37,40],{},"The attribution build of ",[30,38,39],{},"web-vitals"," closes that gap: it reports the element that was interacted with, the event type, and the phase breakdown, all in the same callback that reports the value.",[42,43,45],"h2",{"id":44},"rapid-diagnosis-what-you-need-in-the-payload","Rapid Diagnosis: What You Need in the Payload",[47,48,49,57,81,87],"ul",{},[50,51,52,56],"li",{},[53,54,55],"strong",{},"The interaction target selector."," A CSS selector for the element that received the event. This is the single field that makes the metric actionable.",[50,58,59,62,63,66,67,70,71,74,75,77,78,80],{},[53,60,61],{},"The event type."," ",[30,64,65],{},"pointerdown",", ",[30,68,69],{},"keydown"," and ",[30,72,73],{},"click"," fail for different reasons; a slow ",[30,76,69],{}," usually means synchronous work per keystroke, a slow ",[30,79,73],{}," usually means a heavy state update.",[50,82,83,86],{},[53,84,85],{},"The three phases."," Input delay, processing duration and presentation delay. Which one dominates decides the entire fix strategy.",[50,88,89,92],{},[53,90,91],{},"The route pattern and device class."," The same component can pass on desktop and fail on a mid-tier Android, and you need to know which population you are fixing for.",[94,95,102,103,62,110,62,114,62,118,62,127,62,134,62,142,62,149,62,156,62,160,62,166,62,171,62,180,62,185,62,190,62,194,62,198,62,201,62,204,62,207,62,210,62,213,62,216,62,221,62],"svg",{"xmlns":96,"viewBox":97,"width":98,"role":99,"ariaLabel":100,"style":101},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 244","100%","img","Bar showing an interaction of 340 milliseconds split into 120 milliseconds of input delay, 190 of processing and 30 of presentation delay, each phase annotated with the fix it implies","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)","\n  ",[104,105],"rect",{"className":106,"x":108,"y":108,"width":98,"height":98,"fill":109},[107],"svg-canvas","0","#ffffff",[111,112,113],"title",{},"The three INP phases and the fix each one implies",[115,116,117],"desc",{},"A 340 millisecond interaction split into input delay, processing duration and presentation delay, with the corresponding remedy under each phase and the 200 millisecond budget marked.",[104,119],{"x":120,"y":120,"width":121,"height":122,"rx":123,"fill":124,"stroke":125,"style":126},"1","758","242","10","none","currentColor","stroke-opacity:0.18",[128,129,133],"text",{"x":130,"y":131,"fill":125,"style":132},"24","32","font-size:16px;font-weight:700","Which phase owns the 340ms?",[104,135],{"x":136,"y":137,"width":138,"height":139,"rx":140,"fill":125,"stroke":125,"style":141},"40","58","240","42","5","fill-opacity:0.12;stroke-opacity:0.4",[128,143,148],{"x":144,"y":145,"fill":125,"style":146,"textAnchor":147},"160","84","font-size:12px;font-weight:700;","middle","input delay 120ms",[104,150],{"x":151,"y":137,"width":152,"height":139,"rx":140,"fill":153,"stroke":154,"style":155},"280","380","#ffc300","#b8860b","fill-opacity:0.28",[128,157,159],{"x":158,"y":145,"fill":125,"style":146,"textAnchor":147},"470","processing 190ms",[104,161],{"x":162,"y":137,"width":163,"height":139,"rx":140,"fill":164,"stroke":164,"style":165},"660","60","#0466c8","fill-opacity:0.16;stroke-opacity:0.5",[128,167,170],{"x":168,"y":145,"fill":125,"style":169,"textAnchor":147},"690","font-size:11px;","30ms",[172,173],"line",{"x1":174,"y1":175,"x2":174,"y2":176,"stroke":154,"strokeDashArray":177,"style":179},"440","48","112",[140,178],"4","stroke-opacity:0.9",[128,181,184],{"x":174,"y":182,"fill":154,"style":183,"textAnchor":147},"44","font-size:11px;font-weight:700;","200ms budget",[128,186,189],{"x":144,"y":187,"fill":125,"style":188,"textAnchor":147},"134","font-size:11.5px;font-weight:700;","Thread was busy",[128,191,193],{"x":144,"y":192,"fill":125,"style":169,"textAnchor":147},"152","something else ran first:",[128,195,197],{"x":144,"y":196,"fill":125,"style":169,"textAnchor":147},"169","hydration, a tag, a timer",[128,199,200],{"x":158,"y":187,"fill":125,"style":188,"textAnchor":147},"Your handler is slow",[128,202,203],{"x":158,"y":192,"fill":125,"style":169,"textAnchor":147},"split the work, yield,",[128,205,206],{"x":158,"y":196,"fill":125,"style":169,"textAnchor":147},"move computation off-thread",[128,208,209],{"x":168,"y":187,"fill":125,"style":188,"textAnchor":147},"Rendering",[128,211,212],{"x":168,"y":192,"fill":125,"style":169,"textAnchor":147},"large DOM,",[128,214,215],{"x":168,"y":196,"fill":125,"style":169,"textAnchor":147},"expensive layout",[172,217],{"x1":130,"y1":218,"x2":219,"y2":218,"stroke":125,"style":220},"192","736","stroke-opacity:0.2",[128,222,225],{"x":130,"y":223,"fill":125,"style":224},"220","font-size:11.5px;fill-opacity:0.85","Optimising the handler when input delay dominates is the most common wasted sprint in performance work.",[42,227,229],{"id":228},"root-cause-analysis-why-improve-inp-stalls","Root Cause Analysis: Why \"Improve INP\" Stalls",[15,231,232,235,236,238],{},[53,233,234],{},"1. The value arrives without a subject."," The standard ",[30,237,39],{}," build reports a number. Without the target element, the team guesses — and guesses are biased toward the interactions engineers use, not the ones users struggle with.",[15,240,241,244],{},[53,242,243],{},"2. Selectors are unstable."," Hashed CSS-module class names change every build, so a selector captured last week matches nothing today. Aggregation by raw selector then produces thousands of one-off groups instead of a ranked list.",[15,246,247,250],{},[53,248,249],{},"3. The phase split is ignored."," Teams optimise handlers because that is the familiar work, even when the field data says the handler ran for 20ms and the thread was blocked for 200ms before it started.",[15,252,253,256,257,260],{},[53,254,255],{},"4. One rogue interaction hides behind the p75."," INP reports a near-worst interaction per visit. A rarely used control that takes ",[30,258,259],{},"900ms"," can fail the metric for the sessions that touch it while every common interaction is fast — visible only when you group by target.",[42,262,264],{"id":263},"step-by-step-resolution","Step-by-Step Resolution",[15,266,267],{},[53,268,269],{},"1. Capture the target and the phases.",[271,272,277],"pre",{"className":273,"code":274,"language":275,"meta":276,"style":276},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","import { onINP } from 'web-vitals\u002Fattribution';\n\nonINP((metric) => {\n  const a = metric.attribution;\n  send({\n    name: 'INP', value: Math.round(metric.value), rating: metric.rating,\n    target: a.interactionTarget,          \u002F\u002F CSS selector for the element\n    eventType: a.interactionType,         \u002F\u002F 'pointer' | 'keyboard'\n    inputDelay: Math.round(a.inputDelay),\n    processing: Math.round(a.processingDuration),\n    presentation: Math.round(a.presentationDelay),\n    longestScript: a.longAnimationFrameEntries?.[0]?.scripts?.[0]?.sourceURL\n  });\n});\n\u002F\u002F trade-off: the attribution build is roughly 2KB larger than the standard one\n\u002F\u002F and does slightly more work per interaction. That is a good trade for a metric\n\u002F\u002F you intend to act on — but keep it out of pages that never ship fixes.\n","javascript","",[30,278,279,301,308,331,347,356,374,384,393,404,415,426,442,448,454,460,466],{"__ignoreMap":276},[280,281,283,287,291,294,298],"span",{"class":172,"line":282},1,[280,284,286],{"class":285},"sPARh","import",[280,288,290],{"class":289},"saISM"," { onINP } ",[280,292,293],{"class":285},"from",[280,295,297],{"class":296},"sZ8jY"," 'web-vitals\u002Fattribution'",[280,299,300],{"class":289},";\n",[280,302,304],{"class":172,"line":303},2,[280,305,307],{"emptyLinePlaceholder":306},true,"\n",[280,309,311,315,318,322,325,328],{"class":172,"line":310},3,[280,312,314],{"class":313},"smZ65","onINP",[280,316,317],{"class":289},"((",[280,319,321],{"class":320},"sQw3B","metric",[280,323,324],{"class":289},") ",[280,326,327],{"class":285},"=>",[280,329,330],{"class":289}," {\n",[280,332,334,337,341,344],{"class":172,"line":333},4,[280,335,336],{"class":285},"  const",[280,338,340],{"class":339},"sPXB4"," a",[280,342,343],{"class":285}," =",[280,345,346],{"class":289}," metric.attribution;\n",[280,348,350,353],{"class":172,"line":349},5,[280,351,352],{"class":313},"  send",[280,354,355],{"class":289},"({\n",[280,357,359,362,365,368,371],{"class":172,"line":358},6,[280,360,361],{"class":289},"    name: ",[280,363,364],{"class":296},"'INP'",[280,366,367],{"class":289},", value: Math.",[280,369,370],{"class":313},"round",[280,372,373],{"class":289},"(metric.value), rating: metric.rating,\n",[280,375,377,380],{"class":172,"line":376},7,[280,378,379],{"class":289},"    target: a.interactionTarget,          ",[280,381,383],{"class":382},"sjfSM","\u002F\u002F CSS selector for the element\n",[280,385,387,390],{"class":172,"line":386},8,[280,388,389],{"class":289},"    eventType: a.interactionType,         ",[280,391,392],{"class":382},"\u002F\u002F 'pointer' | 'keyboard'\n",[280,394,396,399,401],{"class":172,"line":395},9,[280,397,398],{"class":289},"    inputDelay: Math.",[280,400,370],{"class":313},[280,402,403],{"class":289},"(a.inputDelay),\n",[280,405,407,410,412],{"class":172,"line":406},10,[280,408,409],{"class":289},"    processing: Math.",[280,411,370],{"class":313},[280,413,414],{"class":289},"(a.processingDuration),\n",[280,416,418,421,423],{"class":172,"line":417},11,[280,419,420],{"class":289},"    presentation: Math.",[280,422,370],{"class":313},[280,424,425],{"class":289},"(a.presentationDelay),\n",[280,427,429,432,434,437,439],{"class":172,"line":428},12,[280,430,431],{"class":289},"    longestScript: a.longAnimationFrameEntries?.[",[280,433,108],{"class":339},[280,435,436],{"class":289},"]?.scripts?.[",[280,438,108],{"class":339},[280,440,441],{"class":289},"]?.sourceURL\n",[280,443,445],{"class":172,"line":444},13,[280,446,447],{"class":289},"  });\n",[280,449,451],{"class":172,"line":450},14,[280,452,453],{"class":289},"});\n",[280,455,457],{"class":172,"line":456},15,[280,458,459],{"class":382},"\u002F\u002F trade-off: the attribution build is roughly 2KB larger than the standard one\n",[280,461,463],{"class":172,"line":462},16,[280,464,465],{"class":382},"\u002F\u002F and does slightly more work per interaction. That is a good trade for a metric\n",[280,467,469],{"class":172,"line":468},17,[280,470,471],{"class":382},"\u002F\u002F you intend to act on — but keep it out of pages that never ship fixes.\n",[15,473,474],{},"Expected outcome: every INP row now names an element and a dominant phase.",[15,476,477,480],{},[53,478,479],{},"2. Make selectors stable enough to group."," Add an explicit attribute to interactive components and prefer it over the generated selector.",[271,482,484],{"className":273,"code":483,"language":275,"meta":276,"style":276},"\u002F\u002F \u003Cbutton data-perf-id=\"filter-panel\u002Fapply\">Apply\u003C\u002Fbutton>\nfunction stableId(selector) {\n  if (!selector) return null;\n  const el = document.querySelector(selector);\n  const owned = el?.closest('[data-perf-id]');\n  return owned ? owned.dataset.perfId : selector.replace(\u002F\\.[a-z]+_[a-z0-9]{5,}\u002Fgi, '');\n}\n\u002F\u002F trade-off: data attributes are one more thing to maintain and they can drift\n\u002F\u002F from the component that owns them. Apply them at the boundary components\n\u002F\u002F (panels, forms, toolbars) rather than to every button.\n",[30,485,486,491,508,530,548,571,630,635,640,645],{"__ignoreMap":276},[280,487,488],{"class":172,"line":282},[280,489,490],{"class":382},"\u002F\u002F \u003Cbutton data-perf-id=\"filter-panel\u002Fapply\">Apply\u003C\u002Fbutton>\n",[280,492,493,496,499,502,505],{"class":172,"line":303},[280,494,495],{"class":285},"function",[280,497,498],{"class":313}," stableId",[280,500,501],{"class":289},"(",[280,503,504],{"class":320},"selector",[280,506,507],{"class":289},") {\n",[280,509,510,513,516,519,522,525,528],{"class":172,"line":310},[280,511,512],{"class":285},"  if",[280,514,515],{"class":289}," (",[280,517,518],{"class":285},"!",[280,520,521],{"class":289},"selector) ",[280,523,524],{"class":285},"return",[280,526,527],{"class":339}," null",[280,529,300],{"class":289},[280,531,532,534,537,539,542,545],{"class":172,"line":333},[280,533,336],{"class":285},[280,535,536],{"class":339}," el",[280,538,343],{"class":285},[280,540,541],{"class":289}," document.",[280,543,544],{"class":313},"querySelector",[280,546,547],{"class":289},"(selector);\n",[280,549,550,552,555,557,560,563,565,568],{"class":172,"line":349},[280,551,336],{"class":285},[280,553,554],{"class":339}," owned",[280,556,343],{"class":285},[280,558,559],{"class":289}," el?.",[280,561,562],{"class":313},"closest",[280,564,501],{"class":289},[280,566,567],{"class":296},"'[data-perf-id]'",[280,569,570],{"class":289},");\n",[280,572,573,576,579,582,585,588,591,594,596,599,603,606,609,612,615,618,620,623,625,628],{"class":172,"line":358},[280,574,575],{"class":285},"  return",[280,577,578],{"class":289}," owned ",[280,580,581],{"class":285},"?",[280,583,584],{"class":289}," owned.dataset.perfId ",[280,586,587],{"class":285},":",[280,589,590],{"class":289}," selector.",[280,592,593],{"class":313},"replace",[280,595,501],{"class":289},[280,597,598],{"class":296},"\u002F",[280,600,602],{"class":601},"sn2CQ","\\.",[280,604,605],{"class":339},"[a-z]",[280,607,608],{"class":285},"+",[280,610,611],{"class":296},"_",[280,613,614],{"class":339},"[a-z0-9]",[280,616,617],{"class":285},"{5,}",[280,619,598],{"class":296},[280,621,622],{"class":285},"gi",[280,624,66],{"class":289},[280,626,627],{"class":296},"''",[280,629,570],{"class":289},[280,631,632],{"class":172,"line":376},[280,633,634],{"class":289},"}\n",[280,636,637],{"class":172,"line":386},[280,638,639],{"class":382},"\u002F\u002F trade-off: data attributes are one more thing to maintain and they can drift\n",[280,641,642],{"class":172,"line":395},[280,643,644],{"class":382},"\u002F\u002F from the component that owns them. Apply them at the boundary components\n",[280,646,647],{"class":172,"line":406},[280,648,649],{"class":382},"\u002F\u002F (panels, forms, toolbars) rather than to every button.\n",[15,651,652],{},"Expected outcome: the dashboard groups by roughly a hundred meaningful component IDs rather than thousands of hashed selectors.",[15,654,655,658,659,662],{},[53,656,657],{},"3. Rank by field impact, not by worst case."," The interaction to fix first is the one whose total contribution — frequency multiplied by the amount it exceeds ",[30,660,661],{},"200ms"," — is largest.",[271,664,668],{"className":665,"code":666,"language":667,"meta":276,"style":276},"language-sql shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","-- Ranked list of what to fix, over the last 7 days.\nSELECT target,\n       count(*)                                   AS interactions,\n       approx_quantile(value, 0.75)               AS p75,\n       sum(greatest(value - 200, 0)) \u002F 1000.0     AS excess_seconds\nFROM inp_events\nWHERE ts > now() - interval '7 days'\nGROUP BY target\nHAVING count(*) > 100\nORDER BY excess_seconds DESC\nLIMIT 20;\n-- trade-off: the HAVING clause hides genuinely broken but rarely used controls.\n-- Run a second query without it, sorted by p75, to catch those separately.\n","sql",[30,669,670,675,680,685,690,695,700,705,710,715,720,725,730],{"__ignoreMap":276},[280,671,672],{"class":172,"line":282},[280,673,674],{},"-- Ranked list of what to fix, over the last 7 days.\n",[280,676,677],{"class":172,"line":303},[280,678,679],{},"SELECT target,\n",[280,681,682],{"class":172,"line":310},[280,683,684],{},"       count(*)                                   AS interactions,\n",[280,686,687],{"class":172,"line":333},[280,688,689],{},"       approx_quantile(value, 0.75)               AS p75,\n",[280,691,692],{"class":172,"line":349},[280,693,694],{},"       sum(greatest(value - 200, 0)) \u002F 1000.0     AS excess_seconds\n",[280,696,697],{"class":172,"line":358},[280,698,699],{},"FROM inp_events\n",[280,701,702],{"class":172,"line":376},[280,703,704],{},"WHERE ts > now() - interval '7 days'\n",[280,706,707],{"class":172,"line":386},[280,708,709],{},"GROUP BY target\n",[280,711,712],{"class":172,"line":395},[280,713,714],{},"HAVING count(*) > 100\n",[280,716,717],{"class":172,"line":406},[280,718,719],{},"ORDER BY excess_seconds DESC\n",[280,721,722],{"class":172,"line":417},[280,723,724],{},"LIMIT 20;\n",[280,726,727],{"class":172,"line":428},[280,728,729],{},"-- trade-off: the HAVING clause hides genuinely broken but rarely used controls.\n",[280,731,732],{"class":172,"line":444},[280,733,734],{},"-- Run a second query without it, sorted by p75, to catch those separately.\n",[15,736,737],{},"Expected outcome: a prioritised list where the top entry is usually responsible for a third or more of the total excess.",[15,739,740,743,744,70,748,752,753,757],{},[53,741,742],{},"4. Route each row to the right fix by phase."," Where input delay dominates, the culprit is other work on the thread — hydration, a third-party tag, a timer — and the remedies are in ",[19,745,747],{"href":746},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002F","Hydration & Partial Rehydration Strategies",[19,749,751],{"href":750},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002F","Third-Party Script Performance",". Where processing dominates, the handler itself needs splitting — see ",[19,754,756],{"href":755},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F","optimizing INP with scheduler.yield()",". Where presentation delay dominates, the render after the handler is expensive: shrink the DOM affected, use containment, or avoid layout-triggering reads.",[42,759,761],{"id":760},"verification","Verification",[15,763,764],{},"Reproduce the top-ranked interaction in the lab before changing anything. Take the target and the device class from the field row, set the Performance panel to the matching CPU throttle, and record just that interaction. The panel's interaction track should show a total in the same range as the field p75 — if it does not, your throttle profile is wrong and any improvement you measure will not transfer.",[15,766,767],{},"After the fix, confirm in both places. In the lab, the same recording should show the dominant phase reduced. In the field, watch that target's p75 specifically rather than the site aggregate:",[271,769,771],{"className":665,"code":770,"language":667,"meta":276,"style":276},"SELECT date_trunc('day', ts) AS d,\n       approx_quantile(value, 0.75) AS p75, count(*) AS n\nFROM inp_events\nWHERE target = 'filter-panel\u002Fapply' AND ts > now() - interval '14 days'\nGROUP BY 1 ORDER BY 1;\n",[30,772,773,778,783,787,792],{"__ignoreMap":276},[280,774,775],{"class":172,"line":282},[280,776,777],{},"SELECT date_trunc('day', ts) AS d,\n",[280,779,780],{"class":172,"line":303},[280,781,782],{},"       approx_quantile(value, 0.75) AS p75, count(*) AS n\n",[280,784,785],{"class":172,"line":310},[280,786,699],{},[280,788,789],{"class":172,"line":333},[280,790,791],{},"WHERE target = 'filter-panel\u002Fapply' AND ts > now() - interval '14 days'\n",[280,793,794],{"class":172,"line":349},[280,795,796],{},"GROUP BY 1 ORDER BY 1;\n",[15,798,799],{},"A successful fix shows the target's own p75 dropping within a day of the deploy, followed by the site-wide p75 moving by a smaller amount — the difference being how much of your traffic touches that control.",[42,801,803],{"id":802},"faq","FAQ",[805,806,809,813],"details",{"className":807},[808],"faq-item",[810,811,812],"summary",{},"Why is interactionTarget sometimes null?",[15,814,815,816,819],{},"Because the element was removed from the DOM before the metric finalised, which is common for controls inside a menu or modal that closes as part of the interaction. Capture a stable identifier at event time — for example, reading the nearest ",[30,817,818],{},"data-perf-id"," in a capture-phase listener — and attach it to the report, so a disappearing element still yields a usable group.",[805,821,823,826],{"className":822},[808],[810,824,825],{},"Does the attribution build slow the page down?",[15,827,828],{},"Marginally, and far less than the problem it diagnoses. It adds around 2KB and a small amount of bookkeeping per interaction, using data the browser already produces. The practical risk is not the library but what you do with it: serialising a large attribution payload on every interaction, rather than batching one report at page hide, is what would actually cost you.",[805,830,832,835],{"className":831},[808],[810,833,834],{},"How do I attribute an interaction inside a third-party iframe?",[15,836,837],{},"You cannot, and that is intentional — INP is measured per document, so an interaction inside a cross-origin iframe is attributed to that frame, not to your page. What you can measure is the effect the iframe has on your main thread. If your input delay is high and your own scripts are idle, profile with the frame removed to quantify its contribution.",[805,839,841,844],{"className":840},[808],[810,842,843],{},"Should I report every interaction or only the final INP?",[15,845,846],{},"Report the final value per page view for the dashboard, and sample a small fraction of individual interactions separately for debugging. The final value is what the metric assesses, so percentile charts must be built from it. Interim entries are useful for understanding the shape of a session — whether one control is consistently slow or one unlucky interaction spiked — but mixing them into the same table makes every percentile ambiguous.",[805,848,850,853],{"className":849},[808],[810,851,852],{},"What if the same component is slow on only one route?",[15,854,855],{},"That is a useful finding, not a complication: include the route pattern in the grouping key so a component's cost can differ per context. The usual explanation is that the route renders far more instances of it, or that a heavier sibling on that page is competing for the thread. Grouping by target alone would average the two and hide it, so keep target and route as separate dimensions and look at their combination.",[805,857,859,862],{"className":858},[808],[810,860,861],{},"Does attribution work for interactions during page load?",[15,863,864],{},"Yes, and those are often the worst ones. An interaction that arrives while hydration is still running shows a large input delay and a small processing duration, which the phase split identifies immediately. Segment by time since navigation to separate them: the early population is fixed by reducing startup work, while the steady-state population is fixed inside the handler. Treating them as one group produces a fix that helps neither.",[42,866,868],{"id":867},"related","Related",[47,870,871,876,883],{},[50,872,873,875],{},[19,874,22],{"href":21}," — the pipeline that carries this attribution to your dashboard.",[50,877,878,882],{},[19,879,881],{"href":880},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002F","Profiling Event Handlers for INP"," — reproduce the attributed interaction in the Performance panel.",[50,884,885,888],{},[19,886,887],{"href":755},"Optimizing INP with scheduler.yield()"," — the fix when processing duration is the dominant phase.",[890,891,893],"script",{"type":892},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Attribute a failing INP to a component\",\n  \"description\": \"Capture interaction target and phase breakdown with the web-vitals attribution build, stabilise selectors, rank interactions by field impact, and route each to the fix its dominant phase implies.\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Capture target and phases\", \"text\": \"Report interactionTarget, interaction type, input delay, processing duration and presentation delay with every INP value.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Stabilise selectors\", \"text\": \"Prefer an explicit data-perf-id on boundary components so grouping survives hashed class names.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Rank by excess\", \"text\": \"Order targets by frequency multiplied by the amount each interaction exceeds the 200ms budget.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 4, \"name\": \"Route by phase\", \"text\": \"Send input-delay-dominated rows to thread contention work and processing-dominated rows to handler splitting.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build\u002F#step-by-step-resolution\" }\n  ]\n}\n",[890,895,896],{"type":892},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Attribute a Failing INP to the Component That Caused It\",\n  \"description\": \"Use the web-vitals attribution build to name the element, event type and dominant phase behind a failing INP, then rank fixes by real field impact.\",\n  \"datePublished\": \"2026-08-01\",\n  \"dateModified\": \"2026-08-01\",\n  \"author\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"publisher\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"mainEntityOfPage\": { \"@type\": \"WebPage\", \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build\u002F\" }\n}\n",[890,898,899],{"type":892},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    { \"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 2, \"name\": \"Core Web Vitals & Measurement\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"RUM Beacons & Field Data Collection\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 4, \"name\": \"Attributing INP to a Component\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build\u002F\" }\n  ]\n}\n",[901,902,903],"style",{},"html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sn2CQ, html code.shiki .sn2CQ{--shiki-default:#024C1A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold;--shiki-light:#024C1A;--shiki-light-font-weight:bold}",{"title":276,"searchDepth":303,"depth":303,"links":905},[906,907,908,909,910,911],{"id":44,"depth":303,"text":45},{"id":228,"depth":303,"text":229},{"id":263,"depth":303,"text":264},{"id":760,"depth":303,"text":761},{"id":802,"depth":303,"text":803},{"id":867,"depth":303,"text":868},"Use the web-vitals attribution build to map a slow interaction back to the component and phase responsible, so the fix has an owner.","md",{"slug":915,"type":916,"breadcrumb":917,"datePublished":924,"dateModified":924},"attributing-inp-to-a-component-with-the-attribution-build","article",[918,920,921,922],{"name":919,"url":598},"Home",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":923},"\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build\u002F","2026-08-01","\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build",{"title":927,"description":928},"Attributing INP to a Component with the web-vitals Attribution Build","Turn a failing INP number into a named component: capture interactionTarget, map selectors to owners, split input delay from processing, and rank fixes by field impact.","core-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fattributing-inp-to-a-component-with-the-attribution-build\u002Findex","7YbywH-rN3zBcpPTVZosauOggVdE9z_Sg70DnvchdUQ",[932,935],{"title":22,"path":933,"stem":934},"\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection","core-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Findex",{"title":936,"path":937,"stem":938},"CrUX vs Your Own RUM","\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fcrux-vs-your-own-rum-why-the-numbers-differ","core-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fcrux-vs-your-own-rum-why-the-numbers-differ\u002Findex",1785622406642]