[{"data":1,"prerenderedAt":1286},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Ffixing-slow-first-interactions-during-hydration":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Ffixing-slow-first-interactions-during-hydration":1278},{"id":4,"title":5,"body":6,"description":1258,"extension":1259,"meta":1260,"navigation":1272,"path":1273,"seo":1274,"stem":1276,"__hash__":1277},"content\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Ffixing-slow-first-interactions-during-hydration\u002Findex.md","Fixing Slow First Interactions During Hydration",{"type":7,"value":8,"toc":1243},"minimark",[9,14,29,32,168,173,229,233,239,245,251,265,398,402,407,564,567,571,574,758,761,765,783,869,872,876,883,1032,1035,1039,1046,1050,1062,1150,1154,1157,1169,1173,1185,1202,1206,1229,1233,1236,1239],[10,11,13],"h1",{"id":12},"how-to-fix-slow-or-dead-first-interactions-during-hydration","How to Fix Slow or Dead First Interactions During Hydration",[15,16,17,18,23,24,28],"p",{},"This guide extends ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002F","Optimizing First Input Delay (FID) and INP"," within ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement"," to the most frustrating interaction a server-rendered page can produce: a user taps a button that is clearly visible, and nothing happens — or it happens a second later.",[15,30,31],{},"Server rendering paints content early, which is good for LCP, but the content is inert until the framework hydrates it: downloads the JavaScript, re-creates the component tree in memory, and attaches event handlers. On a mid-tier phone, hydration of a moderately complex page takes 500ms–2s, usually as one long task. An interaction during that window either waits for the task to finish (large input delay, poor INP) or arrives before the handler is attached and is silently dropped (a \"dead click\" that INP does not even record).",[15,33,34],{},[35,36,42,43,42,50,42,54,42,57,42,66,42,72,42,77,42,85,42,92,42,95,42,103,42,108,42,112,42,117,42,122,42,129,42,135,42,139,42,143,42,147,42,151,42,155,42,159,42,163,42],"svg",{"viewBox":37,"width":38,"role":39,"ariaLabel":40,"style":41},"0 0 760 242","100%","img","Timeline showing content painted at 1.2 seconds, hydration running until 2.4 seconds, and a tap at 1.6 seconds that waits until hydration finishes.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[44,45],"rect",{"className":46,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],"svg-canvas","0","#ffffff",[51,52,53],"title",{},"A tap that lands during hydration",[55,56,40],"desc",{},[44,58],{"x":59,"y":59,"width":60,"height":61,"rx":62,"fill":63,"stroke":64,"style":65},"1","758","240","10","none","currentColor","stroke-opacity:0.18",[67,68,53],"text",{"x":69,"y":70,"fill":64,"style":71},"28.0","34.0","font-size:16px;font-weight:700",[67,73,76],{"x":69,"y":74,"fill":64,"style":75},"74.0","font-size:12.5px;font-weight:700","Paint",[44,78],{"x":79,"y":80,"width":81,"height":82,"rx":83,"fill":64,"stroke":64,"style":84},"112.0","56.0","262.0","26.0","3","fill-opacity:0.06;stroke-opacity:0.4",[67,86,91],{"x":87,"y":88,"fill":64,"style":89,"textAnchor":90},"242.9","73.5","font-size:11.5px","middle","HTML + CSS",[67,93,94],{"x":69,"y":79,"fill":64,"style":75},"Main thread",[44,96],{"x":97,"y":98,"width":99,"height":82,"rx":83,"fill":100,"stroke":101,"style":102},"397.4","94.0","240.0","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[67,104,107],{"x":105,"y":106,"fill":64,"style":89,"textAnchor":90},"517.4","111.5","hydration (one task)",[67,109,111],{"x":69,"y":110,"fill":64,"style":75},"150.0","Tap",[44,113],{"x":114,"y":115,"width":116,"height":82,"rx":83,"fill":100,"stroke":101,"style":102},"463.3","132.0","185.1",[67,118,121],{"x":119,"y":120,"fill":64,"style":89,"textAnchor":90},"555.8","149.5","waits 850ms",[123,124],"line",{"x1":125,"y1":126,"x2":127,"y2":126,"stroke":64,"style":128},"111.2","172.0","726.0","stroke-opacity:0.4",[67,130,134],{"x":131,"y":132,"fill":133,"style":89,"textAnchor":90},"121.9","189.0","#51617a","0ms",[67,136,138],{"x":137,"y":132,"fill":133,"style":89,"textAnchor":90},"199.0","400ms",[67,140,142],{"x":141,"y":132,"fill":133,"style":89,"textAnchor":90},"286.9","800ms",[67,144,146],{"x":145,"y":132,"fill":133,"style":89,"textAnchor":90},"374.7","1200ms",[67,148,150],{"x":149,"y":132,"fill":133,"style":89,"textAnchor":90},"462.5","1600ms",[67,152,154],{"x":153,"y":132,"fill":133,"style":89,"textAnchor":90},"550.3","2000ms",[67,156,158],{"x":157,"y":132,"fill":133,"style":89,"textAnchor":90},"638.2","2400ms",[67,160,162],{"x":161,"y":132,"fill":133,"style":89,"textAnchor":90},"712.3","2800ms",[67,164,167],{"x":69,"y":165,"fill":133,"style":166},"220.0","font-size:12.5px","The user sees a ready page from 1.2s, but the button cannot respond until hydration ends at 2.4s.",[169,170,172],"h2",{"id":171},"rapid-diagnosis","Rapid Diagnosis",[174,175,176,189,195,205,219],"ul",{},[177,178,179,183,184,188],"li",{},[180,181,182],"strong",{},"Segment INP by time since load."," In RUM, bucket INP by ",[185,186,187],"code",{},"interactionTime"," relative to navigation start. A spike in the first 1–3 seconds that disappears afterwards is a hydration problem.",[177,190,191,194],{},[180,192,193],{},"Reproduce with an early click."," In a throttled trace (4x CPU), start recording, reload, and tap a button as soon as it appears. The interaction's input delay should equal the remaining hydration time.",[177,196,197,200,201,204],{},[180,198,199],{},"Check for dead clicks."," Add a temporary capture-phase listener on ",[185,202,203],{},"document"," that logs clicks; compare with clicks that reached component handlers. Clicks in the first gap are dropped.",[177,206,207,210,211,214,215,218],{},[180,208,209],{},"Find the hydration task."," In the Performance panel, the long task right after the framework bundle evaluates, with call stacks into ",[185,212,213],{},"hydrateRoot",", ",[185,216,217],{},"createApp().mount"," or the framework's hydrate function.",[177,220,221,224,225,228],{},[180,222,223],{},"Measure hydration duration."," Add ",[185,226,227],{},"performance.mark('hydrated')"," in the root component's mount effect and read the time.",[169,230,232],{"id":231},"root-cause-analysis","Root Cause Analysis",[15,234,235,238],{},[180,236,237],{},"1. Monolithic hydration."," The default hydration path processes the whole tree in one synchronous task. Nothing else, including input, can run until it completes.",[15,240,241,244],{},[180,242,243],{},"2. Handlers attached at the end."," Event handlers become live only when their component hydrates. Before that, the DOM is visible but inert.",[15,246,247,250],{},[180,248,249],{},"3. Too much JavaScript before hydration starts."," Hydration cannot begin until the bundle is downloaded and evaluated; large bundles extend the inert window before the long task even starts.",[15,252,253,256,257,260,261,264],{},[180,254,255],{},"4. Third-party scripts competing during hydration."," Tags firing at ",[185,258,259],{},"DOMContentLoaded"," or ",[185,262,263],{},"load"," interleave with or extend the hydration window.",[15,266,267],{},[35,268,42,271,42,274,42,277,42,279,42,282,42,284,42,288,42,294,42,298,42,302,42,305,42,309,42,313,42,317,42,319,42,323,42,325,42,328,42,331,42,335,42,339,42,342,42,344,42,347,42,350,42,354,42,356,42,359,42,361,42,364,42,367,42,371,42,373,42,376,42,378,42,381,42,384,42,388,42,390,42,393,42,395,42],{"viewBox":269,"width":38,"role":39,"ariaLabel":270,"style":41},"0 0 760 258","Comparison of hydration strategies by whether early clicks are delayed, dropped or replayed, and how long the main thread is blocked.",[44,272],{"className":273,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,275,276],{},"Hydration strategies and early-interaction behaviour",[55,278,270],{},[44,280],{"x":59,"y":59,"width":60,"height":281,"rx":62,"fill":63,"stroke":64,"style":65},"256",[67,283,276],{"x":69,"y":70,"fill":64,"style":71},[44,285],{"x":69,"y":80,"width":286,"height":287,"rx":48,"fill":64,"stroke":64,"style":84},"193.9","30.0",[67,289,293],{"x":290,"y":291,"fill":64,"style":75,"textAnchor":292},"38.0","75.5","start","Strategy",[44,295],{"x":296,"y":80,"width":297,"height":287,"rx":48,"fill":64,"stroke":64,"style":84},"221.9","255.0",[67,299,301],{"x":300,"y":291,"fill":64,"style":75,"textAnchor":90},"349.5","Early click",[44,303],{"x":304,"y":80,"width":297,"height":287,"rx":48,"fill":64,"stroke":64,"style":84},"477.0",[67,306,308],{"x":307,"y":291,"fill":64,"style":75,"textAnchor":90},"604.5","Longest task",[44,310],{"x":69,"y":311,"width":286,"height":287,"rx":48,"fill":63,"stroke":64,"style":312},"86.0","stroke-opacity:0.35",[67,314,316],{"x":290,"y":315,"fill":64,"style":75,"textAnchor":292},"105.5","Full synchronous hydration",[44,318],{"x":296,"y":311,"width":297,"height":287,"rx":48,"fill":100,"stroke":101,"style":102},[67,320,322],{"x":300,"y":315,"fill":64,"style":321,"textAnchor":90},"font-size:12px","delayed or dropped",[44,324],{"x":304,"y":311,"width":297,"height":287,"rx":48,"fill":100,"stroke":101,"style":102},[67,326,327],{"x":307,"y":315,"fill":64,"style":321,"textAnchor":90},"whole tree",[44,329],{"x":69,"y":330,"width":286,"height":287,"rx":48,"fill":63,"stroke":64,"style":312},"116.0",[67,332,334],{"x":290,"y":333,"fill":64,"style":75,"textAnchor":292},"135.5","Selective hydration (React 18)",[44,336],{"x":296,"y":330,"width":297,"height":287,"rx":48,"fill":337,"stroke":337,"style":338},"#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[67,340,341],{"x":300,"y":333,"fill":64,"style":321,"textAnchor":90},"prioritised boundary",[44,343],{"x":304,"y":330,"width":297,"height":287,"rx":48,"fill":64,"stroke":64,"style":84},[67,345,346],{"x":307,"y":333,"fill":64,"style":321,"textAnchor":90},"per boundary",[44,348],{"x":69,"y":349,"width":286,"height":287,"rx":48,"fill":63,"stroke":64,"style":312},"146.0",[67,351,353],{"x":290,"y":352,"fill":64,"style":75,"textAnchor":292},"165.5","Islands \u002F partial hydration",[44,355],{"x":296,"y":349,"width":297,"height":287,"rx":48,"fill":337,"stroke":337,"style":338},[67,357,358],{"x":300,"y":352,"fill":64,"style":321,"textAnchor":90},"only islands block",[44,360],{"x":304,"y":349,"width":297,"height":287,"rx":48,"fill":337,"stroke":337,"style":338},[67,362,363],{"x":307,"y":352,"fill":64,"style":321,"textAnchor":90},"per island",[44,365],{"x":69,"y":366,"width":286,"height":287,"rx":48,"fill":63,"stroke":64,"style":312},"176.0",[67,368,370],{"x":290,"y":369,"fill":64,"style":75,"textAnchor":292},"195.5","Resumability (Qwik)",[44,372],{"x":296,"y":366,"width":297,"height":287,"rx":48,"fill":337,"stroke":337,"style":338},[67,374,375],{"x":300,"y":369,"fill":64,"style":321,"textAnchor":90},"handler loaded on demand",[44,377],{"x":304,"y":366,"width":297,"height":287,"rx":48,"fill":337,"stroke":337,"style":338},[67,379,380],{"x":307,"y":369,"fill":64,"style":321,"textAnchor":90},"none at startup",[44,382],{"x":69,"y":383,"width":286,"height":287,"rx":48,"fill":63,"stroke":64,"style":312},"206.0",[67,385,387],{"x":290,"y":386,"fill":64,"style":75,"textAnchor":292},"225.5","Event replay (Angular, React)",[44,389],{"x":296,"y":383,"width":297,"height":287,"rx":48,"fill":337,"stroke":337,"style":338},[67,391,392],{"x":300,"y":386,"fill":64,"style":321,"textAnchor":90},"captured and replayed",[44,394],{"x":304,"y":383,"width":297,"height":287,"rx":48,"fill":64,"stroke":64,"style":84},[67,396,397],{"x":307,"y":386,"fill":64,"style":321,"textAnchor":90},"depends on strategy",[169,399,401],{"id":400},"step-by-step-resolution","Step-by-Step Resolution",[403,404,406],"h3",{"id":405},"_1-measure-early-interaction-inp-separately","1. Measure early-interaction INP separately",[408,409,414],"pre",{"className":410,"code":411,"language":412,"meta":413,"style":413},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","import { onINP } from 'web-vitals\u002Fattribution';\nonINP(({ value, attribution }) => {\n  const hydratedAt = performance.getEntriesByName('hydrated')[0]?.startTime ?? Infinity;\n  report({ inp: Math.round(value), beforeHydration: attribution.interactionTime \u003C hydratedAt,\n           inputDelay: Math.round(attribution.inputDelay) });\n});\n\u002F\u002F trade-off: INP reports only the worst interaction, so early interactions are\n\u002F\u002F only visible when they are the worst. For a complete picture, record every\n\u002F\u002F interaction's timing with an Event Timing observer in sampled sessions.\n","javascript","",[185,415,416,438,466,507,528,539,545,552,558],{"__ignoreMap":413},[417,418,420,424,428,431,435],"span",{"class":123,"line":419},1,[417,421,423],{"class":422},"sPARh","import",[417,425,427],{"class":426},"saISM"," { onINP } ",[417,429,430],{"class":422},"from",[417,432,434],{"class":433},"sZ8jY"," 'web-vitals\u002Fattribution'",[417,436,437],{"class":426},";\n",[417,439,441,445,448,452,454,457,460,463],{"class":123,"line":440},2,[417,442,444],{"class":443},"smZ65","onINP",[417,446,447],{"class":426},"(({ ",[417,449,451],{"class":450},"sQw3B","value",[417,453,214],{"class":426},[417,455,456],{"class":450},"attribution",[417,458,459],{"class":426}," }) ",[417,461,462],{"class":422},"=>",[417,464,465],{"class":426}," {\n",[417,467,469,472,476,479,482,485,488,491,494,496,499,502,505],{"class":123,"line":468},3,[417,470,471],{"class":422},"  const",[417,473,475],{"class":474},"sPXB4"," hydratedAt",[417,477,478],{"class":422}," =",[417,480,481],{"class":426}," performance.",[417,483,484],{"class":443},"getEntriesByName",[417,486,487],{"class":426},"(",[417,489,490],{"class":433},"'hydrated'",[417,492,493],{"class":426},")[",[417,495,48],{"class":474},[417,497,498],{"class":426},"]?.startTime ",[417,500,501],{"class":422},"??",[417,503,504],{"class":474}," Infinity",[417,506,437],{"class":426},[417,508,510,513,516,519,522,525],{"class":123,"line":509},4,[417,511,512],{"class":443},"  report",[417,514,515],{"class":426},"({ inp: Math.",[417,517,518],{"class":443},"round",[417,520,521],{"class":426},"(value), beforeHydration: attribution.interactionTime ",[417,523,524],{"class":422},"\u003C",[417,526,527],{"class":426}," hydratedAt,\n",[417,529,531,534,536],{"class":123,"line":530},5,[417,532,533],{"class":426},"           inputDelay: Math.",[417,535,518],{"class":443},[417,537,538],{"class":426},"(attribution.inputDelay) });\n",[417,540,542],{"class":123,"line":541},6,[417,543,544],{"class":426},"});\n",[417,546,548],{"class":123,"line":547},7,[417,549,551],{"class":550},"sjfSM","\u002F\u002F trade-off: INP reports only the worst interaction, so early interactions are\n",[417,553,555],{"class":123,"line":554},8,[417,556,557],{"class":550},"\u002F\u002F only visible when they are the worst. For a complete picture, record every\n",[417,559,561],{"class":123,"line":560},9,[417,562,563],{"class":550},"\u002F\u002F interaction's timing with an Event Timing observer in sampled sessions.\n",[15,565,566],{},"Expected outcome: a clear split between interactions before and after hydration, quantifying how much of your INP problem this guide addresses.",[403,568,570],{"id":569},"_2-use-selective-hydration-so-interacted-regions-hydrate-first","2. Use selective hydration so interacted regions hydrate first",[15,572,573],{},"React 18+ hydrates Suspense boundaries independently and, if the user interacts with a not-yet-hydrated boundary, prioritises it. Wrap independent page regions in boundaries.",[408,575,579],{"className":576,"code":577,"language":578,"meta":413,"style":413},"language-jsx shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","export default function ProductPage() {\n  return (\n    \u003C>\n      \u003CHeader \u002F>\n      \u003CSuspense fallback={\u003CGallerySkeleton \u002F>}>\u003CGallery \u002F>\u003C\u002FSuspense>\n      \u003CSuspense fallback={\u003CBuyBoxSkeleton \u002F>}>\u003CBuyBox \u002F>\u003C\u002FSuspense>\n      \u003CSuspense fallback={\u003CReviewsSkeleton \u002F>}>\u003CReviews \u002F>\u003C\u002FSuspense>\n    \u003C\u002F>\n  );\n}\n\u002F\u002F trade-off: React can only prioritise at boundary granularity. A single\n\u002F\u002F boundary around the whole page gives you nothing; dozens of tiny ones add\n\u002F\u002F overhead. Aim for one boundary per independently interactive region.\n","jsx",[185,580,581,601,609,614,626,664,694,724,729,734,740,746,752],{"__ignoreMap":413},[417,582,583,586,589,592,595,598],{"class":123,"line":419},[417,584,585],{"class":422},"export",[417,587,588],{"class":422}," default",[417,590,591],{"class":422}," function",[417,593,594],{"class":443}," ProductPage",[417,596,597],{"class":450},"() ",[417,599,600],{"class":426},"{\n",[417,602,603,606],{"class":123,"line":440},[417,604,605],{"class":422},"  return",[417,607,608],{"class":426}," (\n",[417,610,611],{"class":123,"line":468},[417,612,613],{"class":426},"    \u003C>\n",[417,615,616,619,623],{"class":123,"line":509},[417,617,618],{"class":426},"      \u003C",[417,620,622],{"class":621},"sZBmE","Header",[417,624,625],{"class":426}," \u002F>\n",[417,627,628,630,633,636,639,641,644,647,650,653,656,659,661],{"class":123,"line":530},[417,629,618],{"class":426},[417,631,632],{"class":621},"Suspense",[417,634,635],{"class":474}," fallback",[417,637,638],{"class":422},"={",[417,640,524],{"class":426},[417,642,643],{"class":621},"GallerySkeleton",[417,645,646],{"class":426}," \u002F>",[417,648,649],{"class":422},"}",[417,651,652],{"class":426},">\u003C",[417,654,655],{"class":621},"Gallery",[417,657,658],{"class":426}," \u002F>\u003C\u002F",[417,660,632],{"class":621},[417,662,663],{"class":426},">\n",[417,665,666,668,670,672,674,676,679,681,683,685,688,690,692],{"class":123,"line":541},[417,667,618],{"class":426},[417,669,632],{"class":621},[417,671,635],{"class":474},[417,673,638],{"class":422},[417,675,524],{"class":426},[417,677,678],{"class":621},"BuyBoxSkeleton",[417,680,646],{"class":426},[417,682,649],{"class":422},[417,684,652],{"class":426},[417,686,687],{"class":621},"BuyBox",[417,689,658],{"class":426},[417,691,632],{"class":621},[417,693,663],{"class":426},[417,695,696,698,700,702,704,706,709,711,713,715,718,720,722],{"class":123,"line":547},[417,697,618],{"class":426},[417,699,632],{"class":621},[417,701,635],{"class":474},[417,703,638],{"class":422},[417,705,524],{"class":426},[417,707,708],{"class":621},"ReviewsSkeleton",[417,710,646],{"class":426},[417,712,649],{"class":422},[417,714,652],{"class":426},[417,716,717],{"class":621},"Reviews",[417,719,658],{"class":426},[417,721,632],{"class":621},[417,723,663],{"class":426},[417,725,726],{"class":123,"line":554},[417,727,728],{"class":426},"    \u003C\u002F>\n",[417,730,731],{"class":123,"line":560},[417,732,733],{"class":426},"  );\n",[417,735,737],{"class":123,"line":736},10,[417,738,739],{"class":426},"}\n",[417,741,743],{"class":123,"line":742},11,[417,744,745],{"class":550},"\u002F\u002F trade-off: React can only prioritise at boundary granularity. A single\n",[417,747,749],{"class":123,"line":748},12,[417,750,751],{"class":550},"\u002F\u002F boundary around the whole page gives you nothing; dozens of tiny ones add\n",[417,753,755],{"class":123,"line":754},13,[417,756,757],{"class":550},"\u002F\u002F overhead. Aim for one boundary per independently interactive region.\n",[15,759,760],{},"Expected outcome: a tap on the buy box during hydration triggers its boundary first; input delay falls from the whole hydration time to one boundary's time.",[403,762,764],{"id":763},"_3-hydrate-below-the-fold-and-low-priority-components-lazily","3. Hydrate below-the-fold and low-priority components lazily",[15,766,767,768,771,772,775,776,771,779,782],{},"Defer components that cannot be interacted with immediately until they are visible or the browser is idle. Nuxt (3.16+) supports ",[185,769,770],{},"hydrate-on-visible"," and ",[185,773,774],{},"hydrate-on-idle"," on lazy components; Astro uses ",[185,777,778],{},"client:visible",[185,780,781],{},"client:idle",".",[408,784,788],{"className":785,"code":786,"language":787,"meta":413,"style":413},"language-vue shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003Ctemplate>\n  \u003CProductGallery \u002F>                                  \u003C!-- hydrate now: above the fold -->\n  \u003CLazyProductReviews hydrate-on-visible \u002F>           \u003C!-- hydrate when scrolled to -->\n  \u003CLazyNewsletterSignup hydrate-on-idle \u002F>            \u003C!-- hydrate when idle -->\n\u003C\u002Ftemplate>\n\u003C!-- trade-off: a lazily hydrated component is inert until its trigger. If a\n     user can reach it quickly (a short page, a fast scroller), the first click\n     on it is delayed. Do not lazy-hydrate primary calls to action. -->\n","vue",[185,789,790,799,813,829,845,854,859,864],{"__ignoreMap":413},[417,791,792,794,797],{"class":123,"line":419},[417,793,524],{"class":426},[417,795,796],{"class":621},"template",[417,798,663],{"class":426},[417,800,801,804,807,810],{"class":123,"line":440},[417,802,803],{"class":426},"  \u003C",[417,805,806],{"class":621},"ProductGallery",[417,808,809],{"class":426}," \u002F>                                  ",[417,811,812],{"class":550},"\u003C!-- hydrate now: above the fold -->\n",[417,814,815,817,820,823,826],{"class":123,"line":468},[417,816,803],{"class":426},[417,818,819],{"class":621},"LazyProductReviews",[417,821,822],{"class":474}," hydrate-on-visible",[417,824,825],{"class":426}," \u002F>           ",[417,827,828],{"class":550},"\u003C!-- hydrate when scrolled to -->\n",[417,830,831,833,836,839,842],{"class":123,"line":509},[417,832,803],{"class":426},[417,834,835],{"class":621},"LazyNewsletterSignup",[417,837,838],{"class":474}," hydrate-on-idle",[417,840,841],{"class":426}," \u002F>            ",[417,843,844],{"class":550},"\u003C!-- hydrate when idle -->\n",[417,846,847,850,852],{"class":123,"line":530},[417,848,849],{"class":426},"\u003C\u002F",[417,851,796],{"class":621},[417,853,663],{"class":426},[417,855,856],{"class":123,"line":541},[417,857,858],{"class":550},"\u003C!-- trade-off: a lazily hydrated component is inert until its trigger. If a\n",[417,860,861],{"class":123,"line":547},[417,862,863],{"class":550},"     user can reach it quickly (a short page, a fast scroller), the first click\n",[417,865,866],{"class":123,"line":554},[417,867,868],{"class":550},"     on it is delayed. Do not lazy-hydrate primary calls to action. -->\n",[15,870,871],{},"Expected outcome: the startup hydration task shrinks to the above-the-fold components, typically by 40–70%.",[403,873,875],{"id":874},"_4-capture-and-replay-early-events","4. Capture and replay early events",[15,877,878,879,882],{},"Where the framework supports it, enable event replay so clicks during hydration are recorded and dispatched once handlers are attached, instead of being dropped. Angular provides ",[185,880,881],{},"withEventReplay()","; React replays discrete events for Suspense boundaries automatically. For other setups, a tiny inline script can capture clicks on marked elements and re-dispatch them.",[408,884,888],{"className":885,"code":886,"language":887,"meta":413,"style":413},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003Cscript>\n  \u002F\u002F Inline in \u003Chead>: queue clicks on critical controls until the app is ready.\n  window.__early = [];\n  document.addEventListener('click', (e) => {\n    const t = e.target.closest('[data-replay]');\n    if (t && !window.__hydrated) { e.preventDefault(); window.__early.push(t); }\n  }, true);\n\u003C\u002Fscript>\n\u003C!-- After hydration: window.__hydrated = true; window.__early.forEach(el => el.click()); -->\n\u003C!-- trade-off: replayed clicks fire after a delay, so the INP for that\n     interaction still includes the wait. Replay prevents lost clicks; it does\n     not make them fast. Shrinking hydration is still the primary fix. -->\n","html",[185,889,890,899,904,915,941,965,994,1004,1012,1017,1022,1027],{"__ignoreMap":413},[417,891,892,894,897],{"class":123,"line":419},[417,893,524],{"class":426},[417,895,896],{"class":621},"script",[417,898,663],{"class":426},[417,900,901],{"class":123,"line":440},[417,902,903],{"class":550},"  \u002F\u002F Inline in \u003Chead>: queue clicks on critical controls until the app is ready.\n",[417,905,906,909,912],{"class":123,"line":468},[417,907,908],{"class":426},"  window.__early ",[417,910,911],{"class":422},"=",[417,913,914],{"class":426}," [];\n",[417,916,917,920,923,925,928,931,934,937,939],{"class":123,"line":509},[417,918,919],{"class":426},"  document.",[417,921,922],{"class":443},"addEventListener",[417,924,487],{"class":426},[417,926,927],{"class":433},"'click'",[417,929,930],{"class":426},", (",[417,932,933],{"class":450},"e",[417,935,936],{"class":426},") ",[417,938,462],{"class":422},[417,940,465],{"class":426},[417,942,943,946,949,951,954,957,959,962],{"class":123,"line":530},[417,944,945],{"class":422},"    const",[417,947,948],{"class":474}," t",[417,950,478],{"class":422},[417,952,953],{"class":426}," e.target.",[417,955,956],{"class":443},"closest",[417,958,487],{"class":426},[417,960,961],{"class":433},"'[data-replay]'",[417,963,964],{"class":426},");\n",[417,966,967,970,973,976,979,982,985,988,991],{"class":123,"line":541},[417,968,969],{"class":422},"    if",[417,971,972],{"class":426}," (t ",[417,974,975],{"class":422},"&&",[417,977,978],{"class":422}," !",[417,980,981],{"class":426},"window.__hydrated) { e.",[417,983,984],{"class":443},"preventDefault",[417,986,987],{"class":426},"(); window.__early.",[417,989,990],{"class":443},"push",[417,992,993],{"class":426},"(t); }\n",[417,995,996,999,1002],{"class":123,"line":547},[417,997,998],{"class":426},"  }, ",[417,1000,1001],{"class":474},"true",[417,1003,964],{"class":426},[417,1005,1006,1008,1010],{"class":123,"line":554},[417,1007,849],{"class":426},[417,1009,896],{"class":621},[417,1011,663],{"class":426},[417,1013,1014],{"class":123,"line":560},[417,1015,1016],{"class":550},"\u003C!-- After hydration: window.__hydrated = true; window.__early.forEach(el => el.click()); -->\n",[417,1018,1019],{"class":123,"line":736},[417,1020,1021],{"class":550},"\u003C!-- trade-off: replayed clicks fire after a delay, so the INP for that\n",[417,1023,1024],{"class":123,"line":742},[417,1025,1026],{"class":550},"     interaction still includes the wait. Replay prevents lost clicks; it does\n",[417,1028,1029],{"class":123,"line":748},[417,1030,1031],{"class":550},"     not make them fast. Shrinking hydration is still the primary fix. -->\n",[15,1033,1034],{},"Expected outcome: no dead clicks on critical controls; support tickets about \"button doesn't work\" on slow devices disappear.",[169,1036,1038],{"id":1037},"verification","Verification",[15,1040,1041,1042,1045],{},"In a throttled trace, tap the primary button immediately after it paints. The interaction's input delay should be under ",[185,1043,1044],{},"100ms",", and the longest task during startup should be one boundary's hydration rather than the whole tree. In RUM, the \"before hydration\" INP bucket should converge towards the \"after\" bucket. Track hydration duration itself as a custom metric, with a budget per template.",[169,1047,1049],{"id":1048},"reducing-what-needs-hydrating-in-the-first-place","Reducing What Needs Hydrating in the First Place",[15,1051,1052,1053,1056,1057,1061],{},"Every technique above manages the cost of hydration; the most durable fix is to need less of it. Audit what is genuinely interactive on each template. Content sites often hydrate entire article bodies, navigation menus that could be CSS-only, and footers with no behaviour at all. Server Components in React, islands in Astro, and ",[185,1054,1055],{},"\u003CNuxtIsland>"," in Nuxt let you render those parts on the server with zero client JavaScript. On a typical marketing page, the interactive surface is a menu, a form and perhaps a carousel — a small fraction of the DOM. Hydrating only that fraction reduces the startup task by an order of magnitude, which no amount of chunking can match. ",[19,1058,1060],{"href":1059},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fislands-vs-full-hydration-for-content-sites\u002F","Islands vs full hydration for content sites"," quantifies the difference.",[15,1063,1064],{},[35,1065,42,1068,42,1071,42,1074,42,1076,42,1079,42,1081,42,1090,42,1096,42,1103,42,1108,42,1112,42,1116,42,1120,42,1124,42,1128,42,1132,42,1135,42,1140,42,1144,42],{"viewBox":1066,"width":38,"role":39,"ariaLabel":1067,"style":41},"0 0 760 216","Bar chart of the longest startup task for a product page under four hydration strategies, against the 50 millisecond long-task line.",[44,1069],{"className":1070,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,1072,1073],{},"Startup hydration task by strategy (mid-tier phone)",[55,1075,1067],{},[44,1077],{"x":59,"y":59,"width":60,"height":1078,"rx":62,"fill":63,"stroke":64,"style":65},"214",[67,1080,1073],{"x":69,"y":70,"fill":64,"style":71},[123,1082],{"x1":1083,"y1":1084,"x2":1083,"y2":1085,"stroke":101,"strokeWidth":1086,"strokeDashArray":1087,"style":1089},"222.1","72.0","194.0","1.5",[1088,1088],"4","stroke-opacity:0.9",[67,1091,316],{"x":1092,"y":1093,"fill":64,"style":1094,"textAnchor":1095},"185.0","92.0","font-size:13px","end",[44,1097],{"x":1098,"y":1099,"width":1100,"height":1101,"rx":83,"fill":100,"stroke":101,"style":1102},"197.0","78.0","471.0","19","fill-opacity:0.7;stroke-opacity:0.9",[67,1104,1107],{"x":1105,"y":1093,"fill":64,"style":1106},"674.0","font-size:12px;font-weight:600","940ms",[67,1109,1111],{"x":1092,"y":1110,"fill":64,"style":1094,"textAnchor":1095},"123.0","Selective (4 boundaries)",[44,1113],{"x":1098,"y":1114,"width":1115,"height":1101,"rx":83,"fill":100,"stroke":101,"style":1102},"109.0","155.3",[67,1117,1119],{"x":1118,"y":1110,"fill":64,"style":1106},"358.3","310ms",[67,1121,1123],{"x":1092,"y":1122,"fill":64,"style":1094,"textAnchor":1095},"154.0","+ lazy below-the-fold",[44,1125],{"x":1098,"y":1126,"width":1127,"height":1101,"rx":83,"fill":100,"stroke":101,"style":1102},"140.0","90.2",[67,1129,1131],{"x":1130,"y":1122,"fill":64,"style":1106},"293.2","180ms",[67,1133,1134],{"x":1092,"y":1092,"fill":64,"style":1094,"textAnchor":1095},"Islands only",[44,1136],{"x":1098,"y":1137,"width":1138,"height":1101,"rx":83,"fill":337,"stroke":337,"style":1139},"171.0","22.5","fill-opacity:0.55;stroke-opacity:0.9",[67,1141,1143],{"x":1142,"y":1092,"fill":64,"style":1106},"225.6","45ms",[67,1145,1149],{"x":1083,"y":1146,"fill":1147,"style":1148,"textAnchor":90},"68.0","#8a6608","font-size:12px;font-weight:700","50ms long task",[169,1151,1153],{"id":1152},"measuring-hydration-as-a-first-class-metric","Measuring Hydration as a First-Class Metric",[15,1155,1156],{},"Hydration duration is rarely tracked, yet it determines the length of the window in which interactions are slow or lost. Instrument it explicitly: mark the start when the framework bundle begins executing and the end in the root component's mount hook, then send the difference with your RUM beacon alongside device class. Chart the p75 per template.",[15,1158,1159,1160,1164,1165,1168],{},"Two derived numbers are particularly useful. The first is the ",[1161,1162,1163],"em",{},"inert window"," — from first contentful paint to hydration end — which is how long users can see controls that do not work. The second is the ",[1161,1166,1167],{},"share of sessions with an interaction inside the inert window",", which tells you how many users actually experience the problem. A long inert window on a page where nobody interacts for five seconds matters little; a short one on a page where users immediately tap a search box matters a lot.",[169,1170,1172],{"id":1171},"faq","FAQ",[1174,1175,1178,1182],"details",{"className":1176},[1177],"faq-item",[1179,1180,1181],"summary",{},"Can I show a visual \"not ready\" state until hydration finishes?",[15,1183,1184],{},"You can style controls as disabled until hydration, but it trades one problem for another: users see a disabled page that looks broken on slow devices. Prefer making controls work early — selective hydration, replay, or plain HTML forms that function without JavaScript — over signalling that they do not.",[1174,1186,1188,1191],{"className":1187},[1177],[1179,1189,1190],{},"Does a progressive-enhancement form avoid the problem?",[15,1192,1193,1194,1197,1198,1201],{},"Yes, for that control. A real ",[185,1195,1196],{},"\u003Cform>"," with an ",[185,1199,1200],{},"action"," submits without JavaScript, so an early tap works — as a full navigation rather than an in-app transition. Once hydrated, the handler can intercept the submit for a smoother experience. It is the most robust pattern for critical actions like search and add-to-cart.",[169,1203,1205],{"id":1204},"related","Related",[174,1207,1208,1215,1222],{},[177,1209,1210,1214],{},[19,1211,1213],{"href":1212},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002F","Hydration & partial rehydration strategies"," — the architectural options in depth.",[177,1216,1217,1221],{},[19,1218,1220],{"href":1219},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Freducing-total-blocking-time-in-lighthouse\u002F","Reducing Total Blocking Time in Lighthouse"," — the lab metric that tracks the same startup work.",[177,1223,1224,1228],{},[19,1225,1227],{"href":1226},"\u002Fframework-performance\u002Fangular-performance\u002Fangular-ssr-hydration-and-event-replay\u002F","Angular SSR hydration and event replay"," — framework support for replaying early events.",[896,1230,1232],{"type":1231},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Fix Slow or Dead First Interactions During Hydration\",\n  \"description\": \"Why the first tap on a server-rendered page is slow or ignored, and the hydration strategies that make early interactions respond within 200ms.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Measure early-interaction INP separately\",\n      \"text\": \"Expected outcome: a clear split between interactions before and after hydration, quantifying how much of your INP problem this guide addresses.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Use selective hydration so interacted regions hydrate first\",\n      \"text\": \"React 18+ hydrates Suspense boundaries independently and, if the user interacts with a not-yet-hydrated boundary, prioritises it.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Hydrate below-the-fold and low-priority components lazily\",\n      \"text\": \"Defer components that cannot be interacted with immediately until they are visible or the browser is idle.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Capture and replay early events\",\n      \"text\": \"Where the framework supports it, enable event replay so clicks during hydration are recorded and dispatched once handlers are attached, instead of being dropped.\"\n    }\n  ]\n}\n",[896,1234,1235],{"type":1231},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Fix Slow or Dead First Interactions During Hydration\",\n  \"description\": \"Why the first tap on a server-rendered page is slow or ignored, and the hydration strategies that make early interactions respond within 200ms.\",\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\u002Foptimizing-first-input-delay-fid\u002Ffixing-slow-first-interactions-during-hydration\u002F\"\n  }\n}\n",[896,1237,1238],{"type":1231},"\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\": \"Optimizing First Input Delay (FID) and INP\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Fixing Slow First Interactions During Hydration\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Ffixing-slow-first-interactions-during-hydration\u002F\"\n    }\n  ]\n}\n",[1240,1241,1242],"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 .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}",{"title":413,"searchDepth":440,"depth":440,"links":1244},[1245,1246,1247,1253,1254,1255,1256,1257],{"id":171,"depth":440,"text":172},{"id":231,"depth":440,"text":232},{"id":400,"depth":440,"text":401,"children":1248},[1249,1250,1251,1252],{"id":405,"depth":468,"text":406},{"id":569,"depth":468,"text":570},{"id":763,"depth":468,"text":764},{"id":874,"depth":468,"text":875},{"id":1037,"depth":440,"text":1038},{"id":1048,"depth":440,"text":1049},{"id":1152,"depth":440,"text":1153},{"id":1171,"depth":440,"text":1172},{"id":1204,"depth":440,"text":1205},"Why the first tap on a server-rendered page is slow or ignored, and the hydration strategies that make early interactions respond within 200ms.","md",{"slug":1261,"type":1262,"breadcrumb":1263,"datePublished":1271,"dateModified":1271},"fixing-slow-first-interactions-during-hydration","article",[1264,1267,1268,1269],{"name":1265,"url":1266},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1270},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Ffixing-slow-first-interactions-during-hydration\u002F","2026-10-06",true,"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Ffixing-slow-first-interactions-during-hydration",{"title":5,"description":1275},"Clicks during hydration wait for the framework or are lost. Measure early-interaction INP, then hydrate in chunks and replay events to stay under 200ms.","core-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Ffixing-slow-first-interactions-during-hydration\u002Findex","7ON-u_rcALWg8NsglEBzS36H973Un9exVYx7Gw7UjZk",[1279,1282],{"title":22,"path":1280,"stem":1281,"children":-1},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid","core-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Findex",{"title":1283,"path":1284,"stem":1285,"children":-1},"Improving INP for Complex Single-Page Applications","\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Fimproving-inp-for-complex-single-page-applications","core-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Fimproving-inp-for-complex-single-page-applications\u002Findex",1791308073568]