[{"data":1,"prerenderedAt":1490},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Fmeasuring-lcp-after-client-side-route-changes":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Fmeasuring-lcp-after-client-side-route-changes":1481},{"id":4,"title":5,"body":6,"description":1462,"extension":1463,"meta":1464,"navigation":670,"path":1476,"seo":1477,"stem":1479,"__hash__":1480},"content\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Fmeasuring-lcp-after-client-side-route-changes\u002Findex.md","Measuring LCP After Client-Side Route Changes",{"type":7,"value":8,"toc":1447},"minimark",[9,14,34,42,45,194,199,236,240,246,252,258,268,272,277,280,453,456,460,467,575,586,590,908,919,1051,1055,1062,1179,1182,1186,1189,1273,1310,1317,1321,1327,1331,1334,1370,1373,1377,1389,1405,1409,1432,1437,1440,1443],[10,11,13],"h1",{"id":12},"how-to-measure-lcp-for-every-route-change-in-a-single-page-app","How to Measure LCP for Every Route Change in a Single-Page App",[15,16,17,18,23,24,28,29,33],"p",{},"This guide is part of ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002F","Soft Navigations & SPA Metrics"," in the ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement"," section. It answers a question the standard metric cannot: how long did the ",[30,31,32],"em",{},"new"," view take to show its main content after the user clicked?",[15,35,36,37,41],{},"Largest Contentful Paint is defined for a document load. The browser stops emitting ",[38,39,40],"code",{},"largest-contentful-paint"," entries as soon as the user interacts or scrolls, because after that point \"the largest thing painted\" stops meaning \"the page has loaded\". In an SPA, every in-app route change happens after an interaction, so it never gets an LCP. The product detail page your users reach from a category listing — often your most valuable view — has no loading metric at all.",[15,43,44],{},"You can close the gap two ways: the experimental Soft Navigations API, which emits contentful-paint entries for soft navigations in Chromium when enabled, and a portable approach using Element Timing and your router's lifecycle. Production setups usually run the portable approach everywhere and use the experimental entries to validate it.",[15,46,47],{},[48,49,55,56,55,63,55,67,55,70,55,79,55,85,55,96,55,101,55,110,55,117,55,122,55,126,55,133,55,138,55,142,55,146,55,151,55,157,55,161,55,165,55,169,55,173,55,177,55,181,55,185,55,188,55],"svg",{"viewBox":50,"width":51,"role":52,"ariaLabel":53,"style":54},"0 0 760 200","100%","img","Timeline showing LCP entries emitted during the initial load, stopping at the first click, and no entries for the following route change.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[57,58],"rect",{"className":59,"x":61,"y":61,"width":51,"height":51,"fill":62},[60],"svg-canvas","0","#ffffff",[64,65,66],"title",{},"Why the standard metric goes silent",[68,69,53],"desc",{},[57,71],{"x":72,"y":72,"width":73,"height":74,"rx":75,"fill":76,"stroke":77,"style":78},"1","758","198","10","none","currentColor","stroke-opacity:0.18",[80,81,66],"text",{"x":82,"y":83,"fill":77,"style":84},"28.0","34.0","font-size:16px;font-weight:700",[86,87],"line",{"x1":88,"y1":89,"x2":88,"y2":90,"stroke":91,"strokeWidth":92,"strokeDashArray":93,"style":95},"349.2","72.0","156.0","#b8860b","1.5",[94,94],"4","stroke-opacity:0.9",[80,97,100],{"x":82,"y":98,"fill":77,"style":99},"96.0","font-size:12.5px;font-weight:700","Hard load",[57,102],{"x":103,"y":104,"width":105,"height":106,"rx":107,"fill":108,"stroke":108,"style":109},"98.7","78.0","163.4","26.0","3","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[80,111,116],{"x":112,"y":113,"fill":77,"style":114,"textAnchor":115},"180.4","95.5","font-size:11.5px","middle","LCP entries",[57,118],{"x":119,"y":104,"width":120,"height":106,"rx":107,"fill":77,"stroke":77,"style":121},"263.6","84.9","fill-opacity:0.06;stroke-opacity:0.4",[80,123,125],{"x":82,"y":124,"fill":77,"style":99},"134.0","Soft nav",[57,127],{"x":128,"y":129,"width":130,"height":106,"rx":107,"fill":131,"stroke":91,"style":132},"349.9","116.0","132.0","#ffc300","fill-opacity:0.24;stroke-opacity:0.9",[80,134,137],{"x":135,"y":136,"fill":77,"style":114,"textAnchor":115},"415.9","133.5","new view renders",[57,139],{"x":140,"y":129,"width":141,"height":106,"rx":107,"fill":77,"stroke":77,"style":121},"483.4","241.9",[80,143,145],{"x":144,"y":136,"fill":77,"style":114,"textAnchor":115},"604.3","no LCP emitted",[86,147],{"x1":148,"y1":90,"x2":149,"y2":90,"stroke":77,"style":150},"97.9","726.0","stroke-opacity:0.4",[80,152,156],{"x":153,"y":154,"fill":155,"style":114,"textAnchor":115},"103.9","173.0","#51617a","0s",[80,158,160],{"x":159,"y":154,"fill":155,"style":114,"textAnchor":115},"176.5","1s",[80,162,164],{"x":163,"y":154,"fill":155,"style":114,"textAnchor":115},"255.0","2s",[80,166,168],{"x":167,"y":154,"fill":155,"style":114,"textAnchor":115},"333.5","3s",[80,170,172],{"x":171,"y":154,"fill":155,"style":114,"textAnchor":115},"412.0","4s",[80,174,176],{"x":175,"y":154,"fill":155,"style":114,"textAnchor":115},"490.5","5s",[80,178,180],{"x":179,"y":154,"fill":155,"style":114,"textAnchor":115},"569.0","6s",[80,182,184],{"x":183,"y":154,"fill":155,"style":114,"textAnchor":115},"647.5","7s",[80,186,187],{"x":149,"y":154,"fill":155,"style":114,"textAnchor":115},"8s",[80,189,193],{"x":88,"y":190,"fill":191,"style":192,"textAnchor":115},"68.0","#8a6608","font-size:12px;font-weight:700","first click",[195,196,198],"h2",{"id":197},"rapid-diagnosis","Rapid Diagnosis",[200,201,202,214,220,226],"ul",{},[203,204,205,209,210,213],"li",{},[206,207,208],"strong",{},"Confirm the gap."," In DevTools, run ",[38,211,212],{},"new PerformanceObserver(l => console.log(l.getEntries())).observe({type: 'largest-contentful-paint', buffered: true})",", click to another route, and note that no new entries arrive.",[203,215,216,219],{},[206,217,218],{},"Identify the route's hero element."," On the new view, decide which element represents \"content has loaded\": the product image, the article headline, the first row of results.",[203,221,222,225],{},[206,223,224],{},"Check for skeletons."," If the route paints a skeleton first, you must decide whether the skeleton or the real content counts. Users perceive the real content; measure that.",[203,227,228,231,232,235],{},[206,229,230],{},"Note image loading attributes."," Route hero images are often ",[38,233,234],{},"loading=\"lazy\""," by default in component libraries, which delays them on soft navigations just as on hard loads.",[195,237,239],{"id":238},"root-cause-analysis-why-route-lcp-is-hard","Root Cause Analysis: Why Route LCP Is Hard",[15,241,242,245],{},[206,243,244],{},"1. The browser's heuristic ends at input."," LCP stops on interaction by design. Nothing in the standard metric can be \"re-armed\" for a new view.",[15,247,248,251],{},[206,249,250],{},"2. \"Largest\" is ambiguous within a persistent shell."," The header, sidebar and footer survive the route change. A naive \"largest element painted after navigation\" can pick a persistent element that re-rendered, not the new content.",[15,253,254,257],{},[206,255,256],{},"3. The navigation start is not the click."," The meaningful start time is the interaction that triggered the route, not the moment the router committed. Measuring from commit hides chunk loading and data fetching — the phases most likely to be slow.",[15,259,260,263,264,267],{},[206,261,262],{},"4. Images paint after their element is inserted."," An ",[38,265,266],{},"\u003Cimg>"," inserted at 300ms may not render pixels until 900ms. Measuring DOM insertion instead of paint under-reports the real experience.",[195,269,271],{"id":270},"step-by-step-resolution","Step-by-Step Resolution",[273,274,276],"h3",{"id":275},"_1-anchor-the-start-at-the-triggering-interaction","1. Anchor the start at the triggering interaction",[15,278,279],{},"Record the timestamp of the interaction that caused the navigation, then pass it into the route's measurement.",[281,282,287],"pre",{"className":283,"code":284,"language":285,"meta":286,"style":286},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","let pendingNavStart = null;\naddEventListener('click', (e) => {\n  const a = e.target.closest('a[href]');\n  if (a && a.origin === location.origin) pendingNavStart = e.timeStamp;\n}, { capture: true });\nrouter.beforeEach(() => { pendingNavStart ??= performance.now(); });\n\u002F\u002F trade-off: e.timeStamp is the hardware input time, which includes input delay.\n\u002F\u002F That is deliberate — users waited through it — but it means a slow click\n\u002F\u002F handler shows up in both INP and route LCP.\n","javascript","",[38,288,289,311,341,367,393,405,434,441,447],{"__ignoreMap":286},[290,291,293,297,301,304,308],"span",{"class":86,"line":292},1,[290,294,296],{"class":295},"sPARh","let",[290,298,300],{"class":299},"saISM"," pendingNavStart ",[290,302,303],{"class":295},"=",[290,305,307],{"class":306},"sPXB4"," null",[290,309,310],{"class":299},";\n",[290,312,314,318,321,325,328,332,335,338],{"class":86,"line":313},2,[290,315,317],{"class":316},"smZ65","addEventListener",[290,319,320],{"class":299},"(",[290,322,324],{"class":323},"sZ8jY","'click'",[290,326,327],{"class":299},", (",[290,329,331],{"class":330},"sQw3B","e",[290,333,334],{"class":299},") ",[290,336,337],{"class":295},"=>",[290,339,340],{"class":299}," {\n",[290,342,344,347,350,353,356,359,361,364],{"class":86,"line":343},3,[290,345,346],{"class":295},"  const",[290,348,349],{"class":306}," a",[290,351,352],{"class":295}," =",[290,354,355],{"class":299}," e.target.",[290,357,358],{"class":316},"closest",[290,360,320],{"class":299},[290,362,363],{"class":323},"'a[href]'",[290,365,366],{"class":299},");\n",[290,368,370,373,376,379,382,385,388,390],{"class":86,"line":369},4,[290,371,372],{"class":295},"  if",[290,374,375],{"class":299}," (a ",[290,377,378],{"class":295},"&&",[290,380,381],{"class":299}," a.origin ",[290,383,384],{"class":295},"===",[290,386,387],{"class":299}," location.origin) pendingNavStart ",[290,389,303],{"class":295},[290,391,392],{"class":299}," e.timeStamp;\n",[290,394,396,399,402],{"class":86,"line":395},5,[290,397,398],{"class":299},"}, { capture: ",[290,400,401],{"class":306},"true",[290,403,404],{"class":299}," });\n",[290,406,408,411,414,417,419,422,425,428,431],{"class":86,"line":407},6,[290,409,410],{"class":299},"router.",[290,412,413],{"class":316},"beforeEach",[290,415,416],{"class":299},"(() ",[290,418,337],{"class":295},[290,420,421],{"class":299}," { pendingNavStart ",[290,423,424],{"class":295},"??=",[290,426,427],{"class":299}," performance.",[290,429,430],{"class":316},"now",[290,432,433],{"class":299},"(); });\n",[290,435,437],{"class":86,"line":436},7,[290,438,440],{"class":439},"sjfSM","\u002F\u002F trade-off: e.timeStamp is the hardware input time, which includes input delay.\n",[290,442,444],{"class":86,"line":443},8,[290,445,446],{"class":439},"\u002F\u002F That is deliberate — users waited through it — but it means a slow click\n",[290,448,450],{"class":86,"line":449},9,[290,451,452],{"class":439},"\u002F\u002F handler shows up in both INP and route LCP.\n",[15,454,455],{},"Expected outcome: route timings start where the user's wait starts, typically adding 20–80ms compared with measuring from router commit.",[273,457,459],{"id":458},"_2-mark-each-routes-hero-element-with-element-timing","2. Mark each route's hero element with Element Timing",[15,461,462,463,466],{},"Element Timing (",[38,464,465],{},"elementtiming"," attribute) reports the paint time of specific images and text nodes — exactly the primitive needed.",[281,468,472],{"className":469,"code":470,"language":471,"meta":286,"style":286},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003C!-- ProductDetail.vue \u002F ProductDetail.tsx -->\n\u003Cimg src=\"\u002Fimg\u002Fp42-800.avif\" width=\"800\" height=\"800\" alt=\"Trail shoe\"\n     elementtiming=\"route-hero\" fetchpriority=\"high\">\n\u003Ch1 elementtiming=\"route-hero-text\">Trail Runner 4\u003C\u002Fh1>\n\u003C!-- trade-off: Element Timing only reports images and text-node containers,\n     not arbitrary divs or canvas. A chart-led view needs a manual mark after\n     the chart's first draw instead. -->\n","html",[38,473,474,479,518,539,560,565,570],{"__ignoreMap":286},[290,475,476],{"class":86,"line":292},[290,477,478],{"class":439},"\u003C!-- ProductDetail.vue \u002F ProductDetail.tsx -->\n",[290,480,481,484,487,490,492,495,498,500,503,506,508,510,513,515],{"class":86,"line":313},[290,482,483],{"class":299},"\u003C",[290,485,52],{"class":486},"sZBmE",[290,488,489],{"class":306}," src",[290,491,303],{"class":299},[290,493,494],{"class":323},"\"\u002Fimg\u002Fp42-800.avif\"",[290,496,497],{"class":306}," width",[290,499,303],{"class":299},[290,501,502],{"class":323},"\"800\"",[290,504,505],{"class":306}," height",[290,507,303],{"class":299},[290,509,502],{"class":323},[290,511,512],{"class":306}," alt",[290,514,303],{"class":299},[290,516,517],{"class":323},"\"Trail shoe\"\n",[290,519,520,523,525,528,531,533,536],{"class":86,"line":343},[290,521,522],{"class":306},"     elementtiming",[290,524,303],{"class":299},[290,526,527],{"class":323},"\"route-hero\"",[290,529,530],{"class":306}," fetchpriority",[290,532,303],{"class":299},[290,534,535],{"class":323},"\"high\"",[290,537,538],{"class":299},">\n",[290,540,541,543,545,548,550,553,556,558],{"class":86,"line":369},[290,542,483],{"class":299},[290,544,10],{"class":486},[290,546,547],{"class":306}," elementtiming",[290,549,303],{"class":299},[290,551,552],{"class":323},"\"route-hero-text\"",[290,554,555],{"class":299},">Trail Runner 4\u003C\u002F",[290,557,10],{"class":486},[290,559,538],{"class":299},[290,561,562],{"class":86,"line":395},[290,563,564],{"class":439},"\u003C!-- trade-off: Element Timing only reports images and text-node containers,\n",[290,566,567],{"class":86,"line":407},[290,568,569],{"class":439},"     not arbitrary divs or canvas. A chart-led view needs a manual mark after\n",[290,571,572],{"class":86,"line":436},[290,573,574],{"class":439},"     the chart's first draw instead. -->\n",[15,576,577,578,581,582,585],{},"Expected outcome: a ",[38,579,580],{},"PerformanceElementTiming"," entry with ",[38,583,584],{},"renderTime"," for each marked element on each route.",[273,587,589],{"id":588},"_3-compute-route-lcp-as-the-latest-hero-paint","3. Compute route LCP as the latest hero paint",[281,591,593],{"className":283,"code":592,"language":285,"meta":286,"style":286},"const heroPaints = [];\nnew PerformanceObserver((l) => heroPaints.push(...l.getEntries()))\n  .observe({ type: 'element', buffered: true });\n\nrouter.afterEach(async (to) => {\n  const start = pendingNavStart; pendingNavStart = null;\n  await new Promise((r) => setTimeout(r, 3000));       \u002F\u002F allow late images to paint\n  const paints = heroPaints.filter((p) => p.renderTime >= start && p.identifier.startsWith('route-hero'));\n  const lcp = paints.length ? Math.max(...paints.map((p) => p.renderTime)) - start : null;\n  report({ route: to.matched.at(-1)?.path, routeLcp: lcp && Math.round(lcp) });\n});\n\u002F\u002F trade-off: a fixed 3s settle window under-reports routes slower than 3s and\n\u002F\u002F misses routes the user leaves early. Finalise on the next navigation or on\n\u002F\u002F visibilitychange instead when slow routes are the ones you care about.\n",[38,594,595,608,645,666,672,696,714,749,796,853,884,890,896,902],{"__ignoreMap":286},[290,596,597,600,603,605],{"class":86,"line":292},[290,598,599],{"class":295},"const",[290,601,602],{"class":306}," heroPaints",[290,604,352],{"class":295},[290,606,607],{"class":299}," [];\n",[290,609,610,612,615,618,621,623,625,628,631,633,636,639,642],{"class":86,"line":313},[290,611,32],{"class":295},[290,613,614],{"class":316}," PerformanceObserver",[290,616,617],{"class":299},"((",[290,619,620],{"class":330},"l",[290,622,334],{"class":299},[290,624,337],{"class":295},[290,626,627],{"class":299}," heroPaints.",[290,629,630],{"class":316},"push",[290,632,320],{"class":299},[290,634,635],{"class":295},"...",[290,637,638],{"class":299},"l.",[290,640,641],{"class":316},"getEntries",[290,643,644],{"class":299},"()))\n",[290,646,647,650,653,656,659,662,664],{"class":86,"line":343},[290,648,649],{"class":299},"  .",[290,651,652],{"class":316},"observe",[290,654,655],{"class":299},"({ type: ",[290,657,658],{"class":323},"'element'",[290,660,661],{"class":299},", buffered: ",[290,663,401],{"class":306},[290,665,404],{"class":299},[290,667,668],{"class":86,"line":369},[290,669,671],{"emptyLinePlaceholder":670},true,"\n",[290,673,674,676,679,681,684,687,690,692,694],{"class":86,"line":395},[290,675,410],{"class":299},[290,677,678],{"class":316},"afterEach",[290,680,320],{"class":299},[290,682,683],{"class":295},"async",[290,685,686],{"class":299}," (",[290,688,689],{"class":330},"to",[290,691,334],{"class":299},[290,693,337],{"class":295},[290,695,340],{"class":299},[290,697,698,700,703,705,708,710,712],{"class":86,"line":407},[290,699,346],{"class":295},[290,701,702],{"class":306}," start",[290,704,352],{"class":295},[290,706,707],{"class":299}," pendingNavStart; pendingNavStart ",[290,709,303],{"class":295},[290,711,307],{"class":306},[290,713,310],{"class":299},[290,715,716,719,722,725,727,730,732,734,737,740,743,746],{"class":86,"line":436},[290,717,718],{"class":295},"  await",[290,720,721],{"class":295}," new",[290,723,724],{"class":306}," Promise",[290,726,617],{"class":299},[290,728,729],{"class":330},"r",[290,731,334],{"class":299},[290,733,337],{"class":295},[290,735,736],{"class":316}," setTimeout",[290,738,739],{"class":299},"(r, ",[290,741,742],{"class":306},"3000",[290,744,745],{"class":299},"));       ",[290,747,748],{"class":439},"\u002F\u002F allow late images to paint\n",[290,750,751,753,756,758,760,763,765,767,769,771,774,777,780,782,785,788,790,793],{"class":86,"line":443},[290,752,346],{"class":295},[290,754,755],{"class":306}," paints",[290,757,352],{"class":295},[290,759,627],{"class":299},[290,761,762],{"class":316},"filter",[290,764,617],{"class":299},[290,766,15],{"class":330},[290,768,334],{"class":299},[290,770,337],{"class":295},[290,772,773],{"class":299}," p.renderTime ",[290,775,776],{"class":295},">=",[290,778,779],{"class":299}," start ",[290,781,378],{"class":295},[290,783,784],{"class":299}," p.identifier.",[290,786,787],{"class":316},"startsWith",[290,789,320],{"class":299},[290,791,792],{"class":323},"'route-hero'",[290,794,795],{"class":299},"));\n",[290,797,798,800,803,805,808,811,814,817,820,822,824,827,830,832,834,836,838,841,844,846,849,851],{"class":86,"line":449},[290,799,346],{"class":295},[290,801,802],{"class":306}," lcp",[290,804,352],{"class":295},[290,806,807],{"class":299}," paints.",[290,809,810],{"class":306},"length",[290,812,813],{"class":295}," ?",[290,815,816],{"class":299}," Math.",[290,818,819],{"class":316},"max",[290,821,320],{"class":299},[290,823,635],{"class":295},[290,825,826],{"class":299},"paints.",[290,828,829],{"class":316},"map",[290,831,617],{"class":299},[290,833,15],{"class":330},[290,835,334],{"class":299},[290,837,337],{"class":295},[290,839,840],{"class":299}," p.renderTime)) ",[290,842,843],{"class":295},"-",[290,845,779],{"class":299},[290,847,848],{"class":295},":",[290,850,307],{"class":306},[290,852,310],{"class":299},[290,854,856,859,862,865,867,869,871,874,876,878,881],{"class":86,"line":855},10,[290,857,858],{"class":316},"  report",[290,860,861],{"class":299},"({ route: to.matched.",[290,863,864],{"class":316},"at",[290,866,320],{"class":299},[290,868,843],{"class":295},[290,870,72],{"class":306},[290,872,873],{"class":299},")?.path, routeLcp: lcp ",[290,875,378],{"class":295},[290,877,816],{"class":299},[290,879,880],{"class":316},"round",[290,882,883],{"class":299},"(lcp) });\n",[290,885,887],{"class":86,"line":886},11,[290,888,889],{"class":299},"});\n",[290,891,893],{"class":86,"line":892},12,[290,894,895],{"class":439},"\u002F\u002F trade-off: a fixed 3s settle window under-reports routes slower than 3s and\n",[290,897,899],{"class":86,"line":898},13,[290,900,901],{"class":439},"\u002F\u002F misses routes the user leaves early. Finalise on the next navigation or on\n",[290,903,905],{"class":86,"line":904},14,[290,906,907],{"class":439},"\u002F\u002F visibilitychange instead when slow routes are the ones you care about.\n",[15,909,577,910,913,914,918],{},[38,911,912],{},"routeLcp"," value per soft navigation. Use ",[19,915,917],{"href":916},"\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Fsegmenting-rum-by-device-and-connection\u002F","segmenting RUM by device and connection"," dimensions so the p75 is comparable with hard-load LCP.",[15,920,921],{},[48,922,55,925,55,928,55,931,55,933,55,950,55,953,55,955,55,961,55,965,55,968,55,972,55,975,55,979,55,981,55,985,55,991,55,994,55,997,55,1000,55,1006,55,1012,55,1016,55,1021,55,1025,55,1029,55,1033,55,1037,55,1042,55,1046,55],{"viewBox":923,"width":51,"role":52,"ariaLabel":924,"style":54},"0 0 760 356","Sequence showing the click timestamp, router navigation, route chunk and data loading, and the hero element paint reported by Element Timing.",[57,926],{"className":927,"x":61,"y":61,"width":51,"height":51,"fill":62},[60],[64,929,930],{},"Route LCP measurement sequence",[68,932,924],{},[934,935,936],"defs",{},[937,938,945],"marker",{"id":939,"viewBox":940,"refX":941,"refY":942,"markerWidth":943,"markerHeight":943,"orient":944},"faef15937b","0 0 10 10","9","5","7","auto-start-reverse",[946,947],"path",{"d":948,"fill":77,"style":949},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[57,951],{"x":72,"y":72,"width":73,"height":952,"rx":75,"fill":76,"stroke":77,"style":78},"354",[80,954,930],{"x":82,"y":83,"fill":77,"style":84},[57,956],{"x":957,"y":958,"width":959,"height":83,"rx":960,"fill":77,"stroke":77,"style":121},"41.0","56.0","150.0","6",[80,962,964],{"x":129,"y":104,"fill":77,"style":963,"textAnchor":115},"font-size:13px;font-weight:700","User",[57,966],{"x":967,"y":958,"width":959,"height":83,"rx":960,"fill":77,"stroke":77,"style":121},"217.0",[80,969,971],{"x":970,"y":104,"fill":77,"style":963,"textAnchor":115},"292.0","Router",[57,973],{"x":974,"y":958,"width":959,"height":83,"rx":960,"fill":77,"stroke":77,"style":121},"393.0",[80,976,978],{"x":977,"y":104,"fill":77,"style":963,"textAnchor":115},"468.0","Network",[57,980],{"x":179,"y":958,"width":959,"height":83,"rx":960,"fill":77,"stroke":77,"style":121},[80,982,984],{"x":983,"y":104,"fill":77,"style":963,"textAnchor":115},"644.0","Renderer",[86,986],{"x1":129,"y1":987,"x2":129,"y2":988,"stroke":77,"strokeWidth":92,"strokeDashArray":989,"style":990},"90.0","308.0",[94,94],"stroke-opacity:0.35",[86,992],{"x1":970,"y1":987,"x2":970,"y2":988,"stroke":77,"strokeWidth":92,"strokeDashArray":993,"style":990},[94,94],[86,995],{"x1":977,"y1":987,"x2":977,"y2":988,"stroke":77,"strokeWidth":92,"strokeDashArray":996,"style":990},[94,94],[86,998],{"x1":983,"y1":987,"x2":983,"y2":988,"stroke":77,"strokeWidth":92,"strokeDashArray":999,"style":990},[94,94],[86,1001],{"x1":129,"y1":1002,"x2":1003,"y2":1002,"stroke":77,"strokeWidth":92,"style":1004,"markerEnd":1005},"124.0","290.0","stroke-opacity:0.6","url(#faef15937b)",[80,1007,1011],{"x":1008,"y":1009,"fill":77,"style":1010,"textAnchor":115},"204.0","117.0","font-size:12px","click (t0)",[86,1013],{"x1":970,"y1":1014,"x2":1015,"y2":1014,"stroke":77,"strokeWidth":92,"style":1004,"markerEnd":1005},"164.0","466.0",[80,1017,1020],{"x":1018,"y":1019,"fill":77,"style":1010,"textAnchor":115},"380.0","157.0","chunk + data",[86,1022],{"x1":977,"y1":1008,"x2":1023,"y2":1008,"stroke":77,"strokeWidth":92,"strokeDashArray":1024,"style":1004,"markerEnd":1005},"294.0",[942,94],[80,1026,1028],{"x":1018,"y":1027,"fill":77,"style":1010,"textAnchor":115},"197.0","resolved",[86,1030],{"x1":970,"y1":1031,"x2":1032,"y2":1031,"stroke":77,"strokeWidth":92,"style":1004,"markerEnd":1005},"244.0","642.0",[80,1034,1036],{"x":977,"y":1035,"fill":77,"style":1010,"textAnchor":115},"237.0","commit new view",[86,1038],{"x1":983,"y1":1039,"x2":1040,"y2":1039,"stroke":77,"strokeWidth":92,"strokeDashArray":1041,"style":1004,"markerEnd":1005},"284.0","118.0",[942,94],[80,1043,1045],{"x":1018,"y":1044,"fill":77,"style":1010,"textAnchor":115},"277.0","hero painted (t1)",[80,1047,1050],{"x":82,"y":1048,"fill":155,"style":1049},"334.0","font-size:12.5px","Route LCP is t1 minus t0 — the hero paint relative to the click, not to the router commit.",[273,1052,1054],{"id":1053},"_4-cross-check-with-experimental-soft-navigation-entries","4. Cross-check with experimental soft-navigation entries",[15,1056,1057,1058,1061],{},"Where the Soft Navigations API is enabled, Chromium emits a ",[38,1059,1060],{},"soft-navigation"," entry and contentful-paint entries for the new view. Log both and compare on a sample.",[281,1063,1065],{"className":283,"code":1064,"language":285,"meta":286,"style":286},"if (PerformanceObserver.supportedEntryTypes.includes('soft-navigation')) {\n  new PerformanceObserver((l) => {\n    for (const nav of l.getEntries()) console.debug('soft-nav', nav.name, Math.round(nav.startTime));\n  }).observe({ type: 'soft-navigation', buffered: true });\n}\n\u002F\u002F trade-off: the experimental API's heuristics and entry types have changed\n\u002F\u002F between origin trials. Use it to validate your own measurement, not as the\n\u002F\u002F only production source.\n",[38,1066,1067,1086,1103,1142,1159,1164,1169,1174],{"__ignoreMap":286},[290,1068,1069,1072,1075,1078,1080,1083],{"class":86,"line":292},[290,1070,1071],{"class":295},"if",[290,1073,1074],{"class":299}," (PerformanceObserver.supportedEntryTypes.",[290,1076,1077],{"class":316},"includes",[290,1079,320],{"class":299},[290,1081,1082],{"class":323},"'soft-navigation'",[290,1084,1085],{"class":299},")) {\n",[290,1087,1088,1091,1093,1095,1097,1099,1101],{"class":86,"line":313},[290,1089,1090],{"class":295},"  new",[290,1092,614],{"class":316},[290,1094,617],{"class":299},[290,1096,620],{"class":330},[290,1098,334],{"class":299},[290,1100,337],{"class":295},[290,1102,340],{"class":299},[290,1104,1105,1108,1110,1112,1115,1118,1121,1123,1126,1129,1131,1134,1137,1139],{"class":86,"line":343},[290,1106,1107],{"class":295},"    for",[290,1109,686],{"class":299},[290,1111,599],{"class":295},[290,1113,1114],{"class":306}," nav",[290,1116,1117],{"class":295}," of",[290,1119,1120],{"class":299}," l.",[290,1122,641],{"class":316},[290,1124,1125],{"class":299},"()) console.",[290,1127,1128],{"class":316},"debug",[290,1130,320],{"class":299},[290,1132,1133],{"class":323},"'soft-nav'",[290,1135,1136],{"class":299},", nav.name, Math.",[290,1138,880],{"class":316},[290,1140,1141],{"class":299},"(nav.startTime));\n",[290,1143,1144,1147,1149,1151,1153,1155,1157],{"class":86,"line":369},[290,1145,1146],{"class":299},"  }).",[290,1148,652],{"class":316},[290,1150,655],{"class":299},[290,1152,1082],{"class":323},[290,1154,661],{"class":299},[290,1156,401],{"class":306},[290,1158,404],{"class":299},[290,1160,1161],{"class":86,"line":395},[290,1162,1163],{"class":299},"}\n",[290,1165,1166],{"class":86,"line":407},[290,1167,1168],{"class":439},"\u002F\u002F trade-off: the experimental API's heuristics and entry types have changed\n",[290,1170,1171],{"class":86,"line":436},[290,1172,1173],{"class":439},"\u002F\u002F between origin trials. Use it to validate your own measurement, not as the\n",[290,1175,1176],{"class":86,"line":443},[290,1177,1178],{"class":439},"\u002F\u002F only production source.\n",[15,1180,1181],{},"Expected outcome: your Element Timing route LCP should agree with the browser's soft-navigation paint within roughly ±100ms on most routes; large disagreements usually mean the wrong hero element is marked.",[195,1183,1185],{"id":1184},"turning-route-lcp-into-fixes","Turning Route LCP Into Fixes",[15,1187,1188],{},"Once the number exists, its breakdown points at the fix just like hard-load LCP. Subtract the chunk and data timings (from Resource Timing for the route chunk and API calls) to see which phase dominates.",[15,1190,1191],{},[48,1192,55,1195,55,1198,55,1201,55,1203,55,1206,55,1208,55,1215,55,1221,55,1226,55,1230,55,1234,55,1238,55,1241,55,1245,55,1249,55,1253,55,1257,55,1261,55,1265,55,1269,55],{"viewBox":1193,"width":51,"role":52,"ariaLabel":1194,"style":54},"0 0 760 225","Bar chart splitting a 1.7 second route LCP into input, route chunk, data, image load and render phases.",[57,1196],{"className":1197,"x":61,"y":61,"width":51,"height":51,"fill":62},[60],[64,1199,1200],{},"Where route LCP goes on a product detail view",[68,1202,1194],{},[57,1204],{"x":72,"y":72,"width":73,"height":1205,"rx":75,"fill":76,"stroke":77,"style":78},"223",[80,1207,1200],{"x":82,"y":83,"fill":77,"style":84},[80,1209,1214],{"x":1210,"y":1211,"fill":77,"style":1212,"textAnchor":1213},"160.0","70.0","font-size:13px","end","Input + handler",[57,1216],{"x":1217,"y":958,"width":1218,"height":1219,"rx":107,"fill":108,"stroke":108,"style":1220},"172.0","41.3","19","fill-opacity:0.55;stroke-opacity:0.9",[80,1222,1225],{"x":1223,"y":1211,"fill":77,"style":1224},"219.4","font-size:12px;font-weight:600","60ms",[80,1227,1229],{"x":1210,"y":1228,"fill":77,"style":1212,"textAnchor":1213},"101.0","Route chunk download",[57,1231],{"x":1217,"y":1232,"width":1233,"height":1219,"rx":107,"fill":108,"stroke":108,"style":1220},"87.0","213.5",[80,1235,1237],{"x":1236,"y":1228,"fill":77,"style":1224},"391.6","310ms",[80,1239,1240],{"x":1210,"y":130,"fill":77,"style":1212,"textAnchor":1213},"Product API (serial x2)",[57,1242],{"x":1217,"y":1040,"width":1243,"height":1219,"rx":107,"fill":131,"stroke":91,"style":1244},"496.0","fill-opacity:0.7;stroke-opacity:0.9",[80,1246,1248],{"x":1247,"y":130,"fill":77,"style":1224},"674.0","720ms",[80,1250,1252],{"x":1210,"y":1251,"fill":77,"style":1212,"textAnchor":1213},"163.0","Hero image load",[57,1254],{"x":1217,"y":1255,"width":1256,"height":1219,"rx":107,"fill":108,"stroke":108,"style":1220},"149.0","330.6",[80,1258,1260],{"x":1259,"y":1251,"fill":77,"style":1224},"508.7","480ms",[80,1262,1264],{"x":1210,"y":1263,"fill":77,"style":1212,"textAnchor":1213},"194.0","Render + paint",[57,1266],{"x":1217,"y":1267,"width":1268,"height":1219,"rx":107,"fill":108,"stroke":108,"style":1220},"180.0","89.5",[80,1270,1272],{"x":1271,"y":1263,"fill":77,"style":1224},"267.6","130ms",[200,1274,1275,1286,1295,1304],{},[203,1276,1277,1280,1281,1285],{},[206,1278,1279],{},"Route chunk dominates"," → prefetch it on hover or viewport, as described in ",[19,1282,1284],{"href":1283},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fprefetching-route-chunks-on-hover-and-viewport\u002F","prefetching route chunks on hover and viewport",".",[203,1287,1288,1291,1292,1294],{},[206,1289,1290],{},"Data dominates"," → start the request in the router's ",[38,1293,413],{}," in parallel with the chunk, and remove serial dependencies between calls.",[203,1296,1297,1300,1301,1303],{},[206,1298,1299],{},"Image dominates"," → preload the hero image as soon as its URL is known, and drop ",[38,1302,234],{}," on route hero images.",[203,1305,1306,1309],{},[206,1307,1308],{},"Render dominates"," → the route renders too much before showing the hero; render above-the-fold content first.",[15,1311,1312,1313,1316],{},"Expected outcome after addressing the dominant phase: route LCP under ",[38,1314,1315],{},"1.5s"," at p75 on mid-tier mobile for routes with cached shells — noticeably faster than any hard load can be, which is the point of an SPA.",[195,1318,1320],{"id":1319},"verification","Verification",[15,1322,1323,1324,1326],{},"Throttle to \"Fast 4G\" and 4x CPU, navigate between two routes twenty times, and compare the distribution of ",[38,1325,912],{}," before and after the fix. In RUM, chart route LCP p75 per route template next to hard-load LCP for the same template: a well-built SPA should show soft-navigation LCP substantially lower. If they are equal, the SPA is paying its JavaScript cost without delivering its speed benefit.",[195,1328,1330],{"id":1329},"choosing-the-hero-element-per-route-template","Choosing the Hero Element Per Route Template",[15,1332,1333],{},"The quality of route LCP depends on marking the right element, and the right element is a product decision as much as a technical one. A few rules keep it consistent:",[200,1335,1336,1345,1358,1364],{},[203,1337,1338,1341,1342,1344],{},[206,1339,1340],{},"One primary marker per template."," A product page's hero is the product image; a search results page's is the first result card's image or title; an article's is the headline. Put the ",[38,1343,465],{}," attribute in the template component so every instance is marked identically.",[203,1346,1347,1350,1351,1354,1355,1357],{},[206,1348,1349],{},"Mark the content, not the container."," Element Timing reports images and text; a wrapper ",[38,1352,1353],{},"div"," is not reported. Put the attribute on the ",[38,1356,266],{}," or on the element directly containing the headline text.",[203,1359,1360,1363],{},[206,1361,1362],{},"Avoid persistent elements."," Never mark anything rendered by the app shell — it may repaint on navigation and produce a misleadingly early timestamp.",[203,1365,1366,1369],{},[206,1367,1368],{},"Prefer the larger of image and text."," When both exist above the fold, mark both with the same identifier prefix and take the later paint, mirroring how LCP treats the largest candidate as the one that matters.",[15,1371,1372],{},"Review the markers when templates are redesigned. A hero image moved below a new promotional banner is no longer what users wait for, and the metric will quietly start measuring the wrong thing.",[195,1374,1376],{"id":1375},"faq","FAQ",[1378,1379,1382,1386],"details",{"className":1380},[1381],"faq-item",[1383,1384,1385],"summary",{},"Can I just call it \"LCP\" in dashboards?",[15,1387,1388],{},"Label it distinctly — \"route LCP\" or \"soft-nav LCP\". It is measured differently from the standard metric and is not what CrUX or Search Console reports. Mixing the two in one series produces numbers nobody can reproduce.",[1378,1390,1392,1395],{"className":1391},[1381],[1383,1393,1394],{},"What if the route has no image?",[15,1396,1397,1398,1400,1401,1404],{},"Mark the main heading or first paragraph container with ",[38,1399,465],{},". Text-node containers are reported when their text paints, which captures font-loading delays too. For routes whose main content is a canvas or a chart, emit a ",[38,1402,1403],{},"performance.mark()"," after the first meaningful draw and use that as the end point.",[195,1406,1408],{"id":1407},"related","Related",[200,1410,1411,1418,1425],{},[203,1412,1413,1417],{},[19,1414,1416],{"href":1415},"\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Ftracking-route-transition-time-with-user-timing\u002F","Tracking route transition time with User Timing"," — a simpler, broader route metric that complements route LCP.",[203,1419,1420,1424],{},[19,1421,1423],{"href":1422},"\u002Fcore-web-vitals-measurement\u002Fmeasuring-lcp-with-chrome-devtools\u002F","Measuring LCP with Chrome DevTools"," — the phase model reused here for soft navigations.",[203,1426,1427,1431],{},[19,1428,1430],{"href":1429},"\u002Fimage-media-optimization\u002Fimage-cdns-and-fetchpriority\u002Fusing-fetchpriority-to-prioritize-the-lcp-image\u002F","Using fetchpriority to prioritize the LCP image"," — applies to route hero images just as it does on first load.",[1433,1434,1436],"script",{"type":1435},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Measure LCP for Every Route Change in a Single-Page App\",\n  \"description\": \"Build a trustworthy largest-contentful-paint measurement for every soft navigation in a single-page app.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Anchor the start at the triggering interaction\",\n      \"text\": \"Record the timestamp of the interaction that caused the navigation, then pass it into the route's measurement.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Mark each route's hero element with Element Timing\",\n      \"text\": \"Element Timing (elementtiming attribute) reports the paint time of specific images and text nodes — exactly the primitive needed.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Compute route LCP as the latest hero paint\",\n      \"text\": \"router.afterEach(async (to) => { const start = pendingNavStart; pendingNavStart = null; await new Promise((r) => setTimeout(r, 3000)); \u002F\u002F allow late images to paint const paints = heroPaints.filter((p) => p.renderTime >= start && p.identifier.startsWith('route-hero')); const lcp = paints.length ?\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Cross-check with experimental soft-navigation entries\",\n      \"text\": \"Where the Soft Navigations API is enabled, Chromium emits a soft-navigation entry and contentful-paint entries for the new view.\"\n    }\n  ]\n}\n",[1433,1438,1439],{"type":1435},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Measure LCP for Every Route Change in a Single-Page App\",\n  \"description\": \"Build a trustworthy largest-contentful-paint measurement for every soft navigation in a single-page app.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Fmeasuring-lcp-after-client-side-route-changes\u002F\"\n  }\n}\n",[1433,1441,1442],{"type":1435},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Core Web Vitals & Measurement\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Soft Navigations & SPA Metrics\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Measuring LCP After Client-Side Route Changes\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Fmeasuring-lcp-after-client-side-route-changes\u002F\"\n    }\n  ]\n}\n",[1444,1445,1446],"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 .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}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 .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}",{"title":286,"searchDepth":313,"depth":313,"links":1448},[1449,1450,1451,1457,1458,1459,1460,1461],{"id":197,"depth":313,"text":198},{"id":238,"depth":313,"text":239},{"id":270,"depth":313,"text":271,"children":1452},[1453,1454,1455,1456],{"id":275,"depth":343,"text":276},{"id":458,"depth":343,"text":459},{"id":588,"depth":343,"text":589},{"id":1053,"depth":343,"text":1054},{"id":1184,"depth":313,"text":1185},{"id":1319,"depth":313,"text":1320},{"id":1329,"depth":313,"text":1330},{"id":1375,"depth":313,"text":1376},{"id":1407,"depth":313,"text":1408},"Build a trustworthy largest-contentful-paint measurement for every soft navigation in a single-page app.","md",{"slug":1465,"type":1466,"breadcrumb":1467,"datePublished":1475,"dateModified":1475},"measuring-lcp-after-client-side-route-changes","article",[1468,1471,1472,1473],{"name":1469,"url":1470},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1474},"\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Fmeasuring-lcp-after-client-side-route-changes\u002F","2026-10-06","\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Fmeasuring-lcp-after-client-side-route-changes",{"title":5,"description":1478},"Standard LCP stops at the first interaction, so SPA route changes never get one. Build a route-level contentful paint with Element Timing and soft-nav entries.","core-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Fmeasuring-lcp-after-client-side-route-changes\u002Findex","b7UFfAWtbikELhGmGVjWhOCq78FqZ7nIOeBIbfO3Z0w",[1482,1486],{"title":1483,"path":1484,"stem":1485,"children":-1},"Attributing INP to the Right Route in an SPA","\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Fattributing-inp-to-the-right-route-in-an-spa","core-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Fattributing-inp-to-the-right-route-in-an-spa\u002Findex",{"title":1487,"path":1488,"stem":1489,"children":-1},"Tracking Route Transition Time with User Timing","\u002Fcore-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Ftracking-route-transition-time-with-user-timing","core-web-vitals-measurement\u002Fsoft-navigations-and-spa-metrics\u002Ftracking-route-transition-time-with-user-timing\u002Findex",1791308074532]