[{"data":1,"prerenderedAt":1151},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fselective-hydration-with-react-suspense":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fselective-hydration-with-react-suspense":1142},{"id":4,"title":5,"body":6,"description":1121,"extension":1122,"meta":1123,"navigation":1135,"path":1136,"seo":1137,"stem":1140,"__hash__":1141},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fselective-hydration-with-react-suspense\u002Findex.md","Selective Hydration with React Suspense",{"type":7,"value":8,"toc":1104},"minimark",[9,14,38,41,207,212,250,254,260,266,272,281,347,351,356,462,465,469,712,715,719,730,734,741,850,854,864,868,871,875,901,905,911,917,928,934,938,945,987,991,1013,1022,1031,1040,1053,1062,1066,1089,1094,1097,1100],[10,11,13],"h1",{"id":12},"how-to-use-selective-hydration-with-react-suspense","How to Use Selective Hydration with React Suspense",[15,16,17,18,23,24,28,29,33,34,37],"p",{},"This guide covers React's built-in hydration scheduling, within ",[19,20,22],"a",{"href":21},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002F","Hydration & Partial Rehydration Strategies"," and ",[19,25,27],{"href":26},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting",". Before React 18, ",[30,31,32],"code",{},"hydrate()"," processed the entire tree in one synchronous pass: a page with heavy components produced a single long task, and any tap during it waited. React 18's ",[30,35,36],{},"hydrateRoot"," with Suspense boundaries changes that. Each boundary hydrates independently, React yields between boundaries so the browser can handle input, and if the user interacts with a boundary that has not hydrated yet, React prioritises it.",[15,39,40],{},"The behaviour is automatic — but only where boundaries exist. A page with one Suspense boundary (or none) still hydrates as one large unit. Placing boundaries around independent regions is what turns React's capability into real INP and TBT improvements.",[15,42,43],{},[44,45,51,52,51,59,51,63,51,66,51,75,51,81,51,92,51,97,51,106,51,113,51,117,51,123,51,128,51,131,51,135,51,139,51,144,51,148,51,151,51,155,51,160,51,166,51,170,51,174,51,177,51,181,51,185,51,189,51,193,51,197,51,201,51],"svg",{"viewBox":46,"width":47,"role":48,"ariaLabel":49,"style":50},"0 0 760 200","100%","img","Two timelines comparing a single synchronous hydration task with boundary-by-boundary hydration where a user click is prioritised.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[53,54],"rect",{"className":55,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],"svg-canvas","0","#ffffff",[60,61,62],"title",{},"Hydration with and without boundaries",[64,65,49],"desc",{},[53,67],{"x":68,"y":68,"width":69,"height":70,"rx":71,"fill":72,"stroke":73,"style":74},"1","758","198","10","none","currentColor","stroke-opacity:0.18",[76,77,62],"text",{"x":78,"y":79,"fill":73,"style":80},"28.0","34.0","font-size:16px;font-weight:700",[82,83],"line",{"x1":84,"y1":85,"x2":84,"y2":86,"stroke":87,"strokeWidth":88,"strokeDashArray":89,"style":91},"314.1","72.0","156.0","#b8860b","1.5",[90,90],"4","stroke-opacity:0.9",[76,93,96],{"x":78,"y":94,"fill":73,"style":95},"96.0","font-size:12.5px;font-weight:700","One root",[53,98],{"x":99,"y":100,"width":101,"height":102,"rx":103,"fill":104,"stroke":87,"style":105},"108.9","78.0","561.4","26.0","3","#ffc300","fill-opacity:0.24;stroke-opacity:0.9",[76,107,112],{"x":108,"y":109,"fill":73,"style":110,"textAnchor":111},"389.6","95.5","font-size:11.5px","middle","hydrate whole page (one task)",[76,114,116],{"x":78,"y":115,"fill":73,"style":95},"134.0","Boundaries",[53,118],{"x":99,"y":119,"width":120,"height":102,"rx":103,"fill":121,"stroke":121,"style":122},"116.0","94.6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[76,124,127],{"x":125,"y":126,"fill":73,"style":110,"textAnchor":111},"156.2","133.5","header",[53,129],{"x":130,"y":119,"width":120,"height":102,"rx":103,"fill":121,"stroke":121,"style":122},"211.9",[76,132,134],{"x":133,"y":126,"fill":73,"style":110,"textAnchor":111},"259.2","gallery",[53,136],{"x":137,"y":119,"width":138,"height":102,"rx":103,"fill":104,"stroke":87,"style":105},"314.9","67.1",[53,140],{"x":141,"y":119,"width":142,"height":102,"rx":103,"fill":73,"stroke":73,"style":143},"390.4","128.9","fill-opacity:0.06;stroke-opacity:0.4",[76,145,147],{"x":146,"y":126,"fill":73,"style":110,"textAnchor":111},"454.8","reviews",[53,149],{"x":150,"y":119,"width":142,"height":102,"rx":103,"fill":73,"stroke":73,"style":143},"527.7",[76,152,154],{"x":153,"y":126,"fill":73,"style":110,"textAnchor":111},"592.1","recs",[82,156],{"x1":157,"y1":86,"x2":158,"y2":86,"stroke":73,"style":159},"108.2","726.0","stroke-opacity:0.4",[76,161,165],{"x":162,"y":163,"fill":164,"style":110,"textAnchor":111},"118.9","173.0","#51617a","0ms",[76,167,169],{"x":168,"y":163,"fill":164,"style":110,"textAnchor":111},"176.8","100ms",[76,171,173],{"x":172,"y":163,"fill":164,"style":110,"textAnchor":111},"245.5","200ms",[76,175,176],{"x":84,"y":163,"fill":164,"style":110,"textAnchor":111},"300ms",[76,178,180],{"x":179,"y":163,"fill":164,"style":110,"textAnchor":111},"382.8","400ms",[76,182,184],{"x":183,"y":163,"fill":164,"style":110,"textAnchor":111},"451.4","500ms",[76,186,188],{"x":187,"y":163,"fill":164,"style":110,"textAnchor":111},"520.1","600ms",[76,190,192],{"x":191,"y":163,"fill":164,"style":110,"textAnchor":111},"588.7","700ms",[76,194,196],{"x":195,"y":163,"fill":164,"style":110,"textAnchor":111},"657.4","800ms",[76,198,200],{"x":199,"y":163,"fill":164,"style":110,"textAnchor":111},"715.3","900ms",[76,202,206],{"x":84,"y":203,"fill":204,"style":205,"textAnchor":111},"68.0","#8a6608","font-size:12px;font-weight:700","user taps buy box",[208,209,211],"h2",{"id":210},"rapid-diagnosis","Rapid Diagnosis",[213,214,215,229,235,244],"ul",{},[216,217,218,51,222,225,226,228],"li",{},[219,220,221],"strong",{},"Check the root API.",[30,223,224],{},"ReactDOM.hydrate"," (legacy) cannot do selective hydration; you need ",[30,227,36],{}," (React 18+), which frameworks like Next.js use.",[216,230,231,234],{},[219,232,233],{},"Count Suspense boundaries"," around page regions. Zero or one means hydration is effectively monolithic.",[216,236,237,240,241,243],{},[219,238,239],{},"Trace hydration."," In a throttled trace, look for one long task under ",[30,242,36],{}," versus several shorter tasks.",[216,245,246,249],{},[219,247,248],{},"Check early-interaction INP."," High input delay for interactions in the first seconds indicates hydration blocking.",[208,251,253],{"id":252},"root-cause-analysis","Root Cause Analysis",[15,255,256,259],{},[219,257,258],{},"1. No boundaries."," Without Suspense boundaries, React treats the tree as one unit and cannot yield inside it.",[15,261,262,265],{},[219,263,264],{},"2. Boundaries in the wrong place."," A boundary around a tiny component does little; the long work is in large regions without boundaries.",[15,267,268,271],{},[219,269,270],{},"3. Synchronous data access."," Components that read synchronous global stores during hydration cannot be split meaningfully.",[15,273,274,277,278,280],{},[219,275,276],{},"4. Legacy root API."," Apps upgraded to React 18 but still calling ",[30,279,224],{}," keep legacy behaviour.",[15,282,283],{},[44,284,51,287,51,290,51,293,51,295,51,298,51,300,51,306,51,312,51,318,51,321,51,325,51,328,51,330,51,334,51,337,51,340,51,344,51],{"viewBox":285,"width":47,"role":48,"ariaLabel":286,"style":50},"0 0 760 268","Page regions on a product page and whether each should get its own Suspense boundary.",[53,288],{"className":289,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,291,292],{},"Where to put boundaries on a product page",[64,294,286],{},[53,296],{"x":68,"y":68,"width":69,"height":297,"rx":71,"fill":72,"stroke":73,"style":74},"266",[76,299,292],{"x":78,"y":79,"fill":73,"style":80},[53,301],{"x":78,"y":302,"width":303,"height":304,"rx":305,"fill":121,"stroke":121,"style":122},"56.0","704.0","40.0","6",[76,307,311],{"x":308,"y":309,"fill":73,"style":310},"42.0","80.5","font-size:13px;font-weight:700","Header + nav",[76,313,317],{"x":314,"y":309,"fill":73,"style":315,"textAnchor":316},"235.0","font-size:12px","start","small, critical, interactive early — hydrate first, own boundary",[53,319],{"x":78,"y":320,"width":303,"height":304,"rx":305,"fill":121,"stroke":121,"style":122},"106.0",[76,322,324],{"x":308,"y":323,"fill":73,"style":310},"130.5","Gallery",[76,326,327],{"x":314,"y":323,"fill":73,"style":315,"textAnchor":316},"above the fold, interactive — own boundary",[53,329],{"x":78,"y":86,"width":303,"height":304,"rx":305,"fill":104,"stroke":87,"style":105},[76,331,333],{"x":308,"y":332,"fill":73,"style":310},"180.5","Buy box",[76,335,336],{"x":314,"y":332,"fill":73,"style":315,"textAnchor":316},"the key interaction — own boundary so a tap prioritises it",[53,338],{"x":78,"y":339,"width":303,"height":304,"rx":305,"fill":73,"stroke":73,"style":143},"206.0",[76,341,343],{"x":308,"y":342,"fill":73,"style":310},"230.5","Reviews + recommendations",[76,345,346],{"x":314,"y":342,"fill":73,"style":315,"textAnchor":316},"below the fold, heavy — own boundaries, hydrate last",[208,348,350],{"id":349},"step-by-step-resolution","Step-by-Step Resolution",[352,353,355],"h3",{"id":354},"_1-use-hydrateroot","1. Use hydrateRoot",[357,358,363],"pre",{"className":359,"code":360,"language":361,"meta":362,"style":362},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","import { hydrateRoot } from 'react-dom\u002Fclient';\nhydrateRoot(document.getElementById('root'), \u003CApp \u002F>, {\n  onRecoverableError: (err) => reportError(err),       \u002F\u002F surface hydration mismatches\n});\n\u002F\u002F trade-off: switching from the legacy API also opts into concurrent features\n\u002F\u002F and stricter hydration error handling; test for mismatches before shipping.\n","javascript","",[30,364,365,387,415,444,450,456],{"__ignoreMap":362},[366,367,369,373,377,380,384],"span",{"class":82,"line":368},1,[366,370,372],{"class":371},"sPARh","import",[366,374,376],{"class":375},"saISM"," { hydrateRoot } ",[366,378,379],{"class":371},"from",[366,381,383],{"class":382},"sZ8jY"," 'react-dom\u002Fclient'",[366,385,386],{"class":375},";\n",[366,388,390,393,396,399,402,405,408,412],{"class":82,"line":389},2,[366,391,36],{"class":392},"smZ65",[366,394,395],{"class":375},"(document.",[366,397,398],{"class":392},"getElementById",[366,400,401],{"class":375},"(",[366,403,404],{"class":382},"'root'",[366,406,407],{"class":375},"), \u003C",[366,409,411],{"class":410},"sZBmE","App",[366,413,414],{"class":375}," \u002F>, {\n",[366,416,418,421,424,428,431,434,437,440],{"class":82,"line":417},3,[366,419,420],{"class":392},"  onRecoverableError",[366,422,423],{"class":375},": (",[366,425,427],{"class":426},"sQw3B","err",[366,429,430],{"class":375},") ",[366,432,433],{"class":371},"=>",[366,435,436],{"class":392}," reportError",[366,438,439],{"class":375},"(err),       ",[366,441,443],{"class":442},"sjfSM","\u002F\u002F surface hydration mismatches\n",[366,445,447],{"class":82,"line":446},4,[366,448,449],{"class":375},"});\n",[366,451,453],{"class":82,"line":452},5,[366,454,455],{"class":442},"\u002F\u002F trade-off: switching from the legacy API also opts into concurrent features\n",[366,457,459],{"class":82,"line":458},6,[366,460,461],{"class":442},"\u002F\u002F and stricter hydration error handling; test for mismatches before shipping.\n",[15,463,464],{},"Expected outcome: React can hydrate boundaries independently.",[352,466,468],{"id":467},"_2-wrap-independent-regions-in-suspense","2. Wrap independent regions in Suspense",[357,470,474],{"className":471,"code":472,"language":473,"meta":362,"style":362},"language-jsx shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","export function ProductPage({ product }) {\n  return (\u003C>\n    \u003CHeader \u002F>\n    \u003CSuspense fallback={\u003CGallerySkeleton \u002F>}>\u003CGallery images={product.images} \u002F>\u003C\u002FSuspense>\n    \u003CSuspense fallback={\u003CBuyBoxSkeleton \u002F>}>\u003CBuyBox product={product} \u002F>\u003C\u002FSuspense>\n    \u003CSuspense fallback={\u003CReviewsSkeleton \u002F>}>\u003CReviews productId={product.id} \u002F>\u003C\u002FSuspense>\n    \u003CSuspense fallback={\u003CRecsSkeleton \u002F>}>\u003CRecommendations productId={product.id} \u002F>\u003C\u002FSuspense>\n  \u003C\u002F>);\n}\n\u002F\u002F trade-off: with server rendering, fallbacks are only shown if a region\n\u002F\u002F suspends during SSR or the client. Size them like the real content anyway —\n\u002F\u002F a mismatched fallback becomes a layout shift whenever it does appear.\n","jsx",[30,475,476,496,504,515,564,603,643,682,688,694,700,706],{"__ignoreMap":362},[366,477,478,481,484,487,490,493],{"class":82,"line":368},[366,479,480],{"class":371},"export",[366,482,483],{"class":371}," function",[366,485,486],{"class":392}," ProductPage",[366,488,489],{"class":375},"({ ",[366,491,492],{"class":426},"product",[366,494,495],{"class":375}," }) {\n",[366,497,498,501],{"class":82,"line":389},[366,499,500],{"class":371},"  return",[366,502,503],{"class":375}," (\u003C>\n",[366,505,506,509,512],{"class":82,"line":417},[366,507,508],{"class":375},"    \u003C",[366,510,511],{"class":410},"Header",[366,513,514],{"class":375}," \u002F>\n",[366,516,517,519,522,526,529,532,535,538,541,544,546,549,551,554,556,559,561],{"class":82,"line":446},[366,518,508],{"class":375},[366,520,521],{"class":410},"Suspense",[366,523,525],{"class":524},"sPXB4"," fallback",[366,527,528],{"class":371},"={",[366,530,531],{"class":375},"\u003C",[366,533,534],{"class":410},"GallerySkeleton",[366,536,537],{"class":375}," \u002F>",[366,539,540],{"class":371},"}",[366,542,543],{"class":375},">\u003C",[366,545,324],{"class":410},[366,547,548],{"class":524}," images",[366,550,528],{"class":371},[366,552,553],{"class":375},"product.images",[366,555,540],{"class":371},[366,557,558],{"class":375}," \u002F>\u003C\u002F",[366,560,521],{"class":410},[366,562,563],{"class":375},">\n",[366,565,566,568,570,572,574,576,579,581,583,585,588,591,593,595,597,599,601],{"class":82,"line":452},[366,567,508],{"class":375},[366,569,521],{"class":410},[366,571,525],{"class":524},[366,573,528],{"class":371},[366,575,531],{"class":375},[366,577,578],{"class":410},"BuyBoxSkeleton",[366,580,537],{"class":375},[366,582,540],{"class":371},[366,584,543],{"class":375},[366,586,587],{"class":410},"BuyBox",[366,589,590],{"class":524}," product",[366,592,528],{"class":371},[366,594,492],{"class":375},[366,596,540],{"class":371},[366,598,558],{"class":375},[366,600,521],{"class":410},[366,602,563],{"class":375},[366,604,605,607,609,611,613,615,618,620,622,624,627,630,632,635,637,639,641],{"class":82,"line":458},[366,606,508],{"class":375},[366,608,521],{"class":410},[366,610,525],{"class":524},[366,612,528],{"class":371},[366,614,531],{"class":375},[366,616,617],{"class":410},"ReviewsSkeleton",[366,619,537],{"class":375},[366,621,540],{"class":371},[366,623,543],{"class":375},[366,625,626],{"class":410},"Reviews",[366,628,629],{"class":524}," productId",[366,631,528],{"class":371},[366,633,634],{"class":375},"product.id",[366,636,540],{"class":371},[366,638,558],{"class":375},[366,640,521],{"class":410},[366,642,563],{"class":375},[366,644,646,648,650,652,654,656,659,661,663,665,668,670,672,674,676,678,680],{"class":82,"line":645},7,[366,647,508],{"class":375},[366,649,521],{"class":410},[366,651,525],{"class":524},[366,653,528],{"class":371},[366,655,531],{"class":375},[366,657,658],{"class":410},"RecsSkeleton",[366,660,537],{"class":375},[366,662,540],{"class":371},[366,664,543],{"class":375},[366,666,667],{"class":410},"Recommendations",[366,669,629],{"class":524},[366,671,528],{"class":371},[366,673,634],{"class":375},[366,675,540],{"class":371},[366,677,558],{"class":375},[366,679,521],{"class":410},[366,681,563],{"class":375},[366,683,685],{"class":82,"line":684},8,[366,686,687],{"class":375},"  \u003C\u002F>);\n",[366,689,691],{"class":82,"line":690},9,[366,692,693],{"class":375},"}\n",[366,695,697],{"class":82,"line":696},10,[366,698,699],{"class":442},"\u002F\u002F trade-off: with server rendering, fallbacks are only shown if a region\n",[366,701,703],{"class":82,"line":702},11,[366,704,705],{"class":442},"\u002F\u002F suspends during SSR or the client. Size them like the real content anyway —\n",[366,707,709],{"class":82,"line":708},12,[366,710,711],{"class":442},"\u002F\u002F a mismatched fallback becomes a layout shift whenever it does appear.\n",[15,713,714],{},"Expected outcome: hydration splits into boundary-sized tasks, and React yields between them.",[352,716,718],{"id":717},"_3-lazy-load-code-for-heavy-regions","3. Lazy-load code for heavy regions",[15,720,721,722,725,726,729],{},"Combine boundaries with ",[30,723,724],{},"React.lazy"," (or ",[30,727,728],{},"next\u002Fdynamic",") for heavy below-the-fold regions so their code downloads in parallel and hydrates when ready, rather than being part of the main bundle that must evaluate before anything hydrates.",[352,731,733],{"id":732},"_4-stream-the-html-for-each-boundary","4. Stream the HTML for each boundary",[15,735,736,737,740],{},"With streaming SSR (",[30,738,739],{},"renderToPipeableStream"," \u002F Next.js App Router), boundaries also stream: the server sends the shell first and fills boundaries as their data resolves, improving TTFB and LCP alongside hydration.",[15,742,743],{},[44,744,51,747,51,750,51,753,51,755,51,772,51,775,51,777,51,781,51,785,51,788,51,790,51,794,51,797,51,800,51,804,51,807,51,811,51,814,51,818,51,821,51,824,51,828,51,831,51,838,51,842,51,846,51],{"viewBox":745,"width":47,"role":48,"ariaLabel":746,"style":50},"0 0 760 149","Sequence from a tap on an unhydrated Suspense boundary to React prioritising that boundary's hydration and replaying the event.",[53,748],{"className":749,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,751,752],{},"What happens when a user taps an unhydrated region",[64,754,746],{},[756,757,758],"defs",{},[759,760,767],"marker",{"id":761,"viewBox":762,"refX":763,"refY":764,"markerWidth":765,"markerHeight":765,"orient":766},"fa0646f6af","0 0 10 10","9","5","7","auto-start-reverse",[768,769],"path",{"d":770,"fill":73,"style":771},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[53,773],{"x":68,"y":68,"width":69,"height":774,"rx":71,"fill":72,"stroke":73,"style":74},"147",[76,776,752],{"x":78,"y":79,"fill":73,"style":80},[53,778],{"x":78,"y":302,"width":779,"height":780,"rx":305,"fill":121,"stroke":121,"style":122},"108.8","71.0",[76,782,784],{"x":783,"y":100,"fill":73,"style":310,"textAnchor":111},"82.4","Tap",[76,786,787],{"x":783,"y":94,"fill":73,"style":315,"textAnchor":111},"on buy box",[53,789],{"x":168,"y":302,"width":779,"height":780,"rx":305,"fill":121,"stroke":121,"style":122},[76,791,793],{"x":792,"y":100,"fill":73,"style":310,"textAnchor":111},"231.2","React captures",[76,795,796],{"x":792,"y":94,"fill":73,"style":315,"textAnchor":111},"discrete event",[53,798],{"x":799,"y":302,"width":779,"height":780,"rx":305,"fill":104,"stroke":87,"style":105},"325.6",[76,801,803],{"x":802,"y":100,"fill":73,"style":310,"textAnchor":111},"380.0","Prioritise",[76,805,806],{"x":802,"y":94,"fill":73,"style":315,"textAnchor":111},"hydrate buy box",[76,808,810],{"x":802,"y":809,"fill":73,"style":315,"textAnchor":111},"112.0","next",[53,812],{"x":813,"y":302,"width":779,"height":780,"rx":305,"fill":121,"stroke":121,"style":122},"474.4",[76,815,817],{"x":816,"y":100,"fill":73,"style":310,"textAnchor":111},"528.8","Replay",[76,819,820],{"x":816,"y":94,"fill":73,"style":315,"textAnchor":111},"dispatch the click",[53,822],{"x":823,"y":302,"width":779,"height":780,"rx":305,"fill":121,"stroke":121,"style":122},"623.2",[76,825,827],{"x":826,"y":100,"fill":73,"style":310,"textAnchor":111},"677.6","Respond",[76,829,830],{"x":826,"y":94,"fill":73,"style":315,"textAnchor":111},"next paint",[82,832],{"x1":833,"y1":834,"x2":835,"y2":834,"stroke":73,"strokeWidth":88,"style":836,"markerEnd":837},"136.8","91.5","174.8","stroke-opacity:0.6","url(#fa0646f6af)",[82,839],{"x1":840,"y1":834,"x2":841,"y2":834,"stroke":73,"strokeWidth":88,"style":836,"markerEnd":837},"285.6","323.6",[82,843],{"x1":844,"y1":834,"x2":845,"y2":834,"stroke":73,"strokeWidth":88,"style":836,"markerEnd":837},"434.4","472.4",[82,847],{"x1":848,"y1":834,"x2":849,"y2":834,"stroke":73,"strokeWidth":88,"style":836,"markerEnd":837},"583.2","621.2",[208,851,853],{"id":852},"verification","Verification",[15,855,856,857,860,861,863],{},"Record a throttled trace: hydration should appear as several shorter tasks, ideally each under ",[30,858,859],{},"50ms","–",[30,862,169],{},", with gaps where input can be processed. Tap the buy box during hydration and confirm in the Interactions track that its input delay is short and that its boundary hydrated before later ones. In the field, INP for interactions in the first few seconds should fall; segment by time since load to see it.",[208,865,867],{"id":866},"worked-example-prioritising-add-to-cart","Worked Example: Prioritising Add-to-Cart",[15,869,870],{},"A retailer's product page hydrated as one 760ms task on mid-tier phones; users who tapped \"Add to cart\" during load waited up to the full hydration time, and field INP for that button was 410ms at p75. Adding five Suspense boundaries (header, gallery, buy box, reviews, recommendations) and lazy-loading reviews and recommendations split hydration into tasks of 60–180ms. When users tapped the buy box mid-hydration, React hydrated it next and replayed the tap. Add-to-cart INP p75 fell to 170ms, and lab TBT dropped from 690ms to 260ms.",[208,872,874],{"id":873},"common-mistakes","Common Mistakes",[213,876,877,883,889,895],{},[216,878,879,882],{},[219,880,881],{},"One boundary around the whole page."," That is the same as no boundary for hydration purposes.",[216,884,885,888],{},[219,886,887],{},"Hundreds of tiny boundaries."," Each has overhead; aim for one per independently interactive region.",[216,890,891,894],{},[219,892,893],{},"Unsized fallbacks."," If a region suspends on the client, an undersized fallback shifts layout.",[216,896,897,900],{},[219,898,899],{},"Assuming boundaries split a single heavy component."," A boundary cannot split work inside one component; a 300ms component is still a 300ms task.",[208,902,904],{"id":903},"edge-cases","Edge Cases",[15,906,907,910],{},[219,908,909],{},"Context providers above boundaries."," Providers hydrate before their children; heavy provider initialisation still runs up front.",[15,912,913,916],{},[219,914,915],{},"Event types."," React prioritises discrete events (clicks, key presses) for unhydrated boundaries; continuous events like scroll or mouse move do not trigger prioritisation.",[15,918,919,922,923,927],{},[219,920,921],{},"Third-party scripts during hydration."," Selective hydration yields to the browser, which may run third-party tasks in the gaps; those can still delay interactions — see ",[19,924,926],{"href":925},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-input-delay-from-third-party-tags\u002F","reducing input delay from third-party tags",".",[15,929,930,933],{},[219,931,932],{},"Older React versions in micro-frontends."," Mixed React versions on one page each hydrate on their own terms; only React 18+ roots benefit.",[208,935,937],{"id":936},"measuring-the-effect-in-the-field","Measuring the Effect in the Field",[15,939,940,941,944],{},"Selective hydration changes when work happens, so measure it in the window where it matters. Tag each INP beacon with the time since navigation start and with whether hydration had completed (a ",[30,942,943],{},"hydrated"," mark set in the root component's first effect). Compare INP for interactions before hydration completed against those after, before and after adding boundaries. A successful change shows the \"before hydration\" bucket shrinking towards the \"after\" bucket. Also track the share of sessions with an interaction before hydration completed: if it is small, the user-facing benefit is small too, and lab TBT improvements may be the main gain.",[357,946,948],{"className":359,"code":947,"language":361,"meta":362,"style":362},"useEffect(() => { performance.mark('hydrated'); }, []);   \u002F\u002F in the root component\n\u002F\u002F trade-off: the root's effect runs after all boundaries have hydrated, so\n\u002F\u002F this marks full hydration; mark per boundary if you need finer detail.\n",[30,949,950,977,982],{"__ignoreMap":362},[366,951,952,955,958,960,963,966,968,971,974],{"class":82,"line":368},[366,953,954],{"class":392},"useEffect",[366,956,957],{"class":375},"(() ",[366,959,433],{"class":371},[366,961,962],{"class":375}," { performance.",[366,964,965],{"class":392},"mark",[366,967,401],{"class":375},[366,969,970],{"class":382},"'hydrated'",[366,972,973],{"class":375},"); }, []);   ",[366,975,976],{"class":442},"\u002F\u002F in the root component\n",[366,978,979],{"class":82,"line":389},[366,980,981],{"class":442},"\u002F\u002F trade-off: the root's effect runs after all boundaries have hydrated, so\n",[366,983,984],{"class":82,"line":417},[366,985,986],{"class":442},"\u002F\u002F this marks full hydration; mark per boundary if you need finer detail.\n",[208,988,990],{"id":989},"faq","FAQ",[992,993,996,1000],"details",{"className":994},[995],"faq-item",[997,998,999],"summary",{},"Does Next.js do this automatically?",[15,1001,1002,1003,1005,1006,1009,1010,1012],{},"Next.js uses ",[30,1004,36],{}," and streaming, so selective hydration is available; whether it helps depends on your Suspense boundaries. ",[30,1007,1008],{},"loading.js"," files create boundaries per route segment; add explicit ",[30,1011,521],{}," boundaries inside pages for finer control.",[992,1014,1016,1019],{"className":1015},[995],[997,1017,1018],{},"Is selective hydration the same as lazy hydration?",[15,1020,1021],{},"No. Selective hydration still hydrates everything as soon as possible, just in prioritised, interruptible chunks. Lazy hydration delays hydration of some components until a trigger (visible, idle, interaction). They combine well.",[992,1023,1025,1028],{"className":1024},[995],[997,1026,1027],{},"Can I control the order boundaries hydrate in?",[15,1029,1030],{},"Not directly; React hydrates roughly in document order and promotes boundaries the user interacts with. You can influence order by lazy-loading low-priority regions so their code arrives later.",[992,1032,1034,1037],{"className":1033},[995],[997,1035,1036],{},"Does this help LCP?",[15,1038,1039],{},"Indirectly. Hydration does not usually delay the first paint of server-rendered content, but shorter hydration tasks reduce main-thread contention for image decoding and late rendering, and streaming boundaries improve TTFB.",[992,1041,1043,1046],{"className":1042},[995],[997,1044,1045],{},"How do I measure hydration per boundary?",[15,1047,1048,1049,1052],{},"Add ",[30,1050,1051],{},"performance.mark"," calls in each boundary's first effect, or use the React DevTools profiler on a production profiling build. In traces, React's scheduler tasks during hydration can be correlated with component names in development builds.",[992,1054,1056,1059],{"className":1055},[995],[997,1057,1058],{},"Do boundaries change what users see during normal loads?",[15,1060,1061],{},"With server rendering, usually not: the server HTML for every boundary is already on screen, and hydration attaches behaviour without visible change. Fallbacks appear only when a boundary actually suspends — for lazy code that has not loaded or data that is not ready — which is why sizing them correctly still matters.",[208,1063,1065],{"id":1064},"related","Related",[213,1067,1068,1075,1082],{},[216,1069,1070,1074],{},[19,1071,1073],{"href":1072},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Ffixing-long-hydration-tasks-in-nextjs-app-router\u002F","Fixing long hydration tasks in Next.js App Router"," — App Router specifics.",[216,1076,1077,1081],{},[19,1078,1080],{"href":1079},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Ffixing-slow-first-interactions-during-hydration\u002F","Fixing slow first interactions during hydration"," — measuring early-interaction INP.",[216,1083,1084,1088],{},[19,1085,1087],{"href":1086},"\u002Fnetwork-protocol-optimization\u002Fstreaming-ssr-and-early-flush\u002Fstreaming-ssr-with-react-suspense\u002F","Streaming SSR with React Suspense"," — the server half of the same boundaries.",[1090,1091,1093],"script",{"type":1092},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Use Selective Hydration with React Suspense\",\n  \"description\": \"How React's selective hydration works, where to put Suspense boundaries so hydration yields and prioritises interactions, and how to verify it.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Use hydrateRoot\",\n      \"text\": \"Expected outcome: React can hydrate boundaries independently.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Wrap independent regions in Suspense\",\n      \"text\": \"Expected outcome: hydration splits into boundary-sized tasks, and React yields between them.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Lazy-load code for heavy regions\",\n      \"text\": \"Combine boundaries with React.lazy (or next\u002Fdynamic) for heavy below-the-fold regions so their code downloads in parallel and hydrates when ready, rather than being part of the main bundle that must evaluate before anything hydrates.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Stream the HTML for each boundary\",\n      \"text\": \"With streaming SSR (renderToPipeableStream \u002F Next.js App Router), boundaries also stream: the server sends the shell first and fills boundaries as their data resolves, improving TTFB and LCP alongside hydration.\"\n    }\n  ]\n}\n",[1090,1095,1096],{"type":1092},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Use Selective Hydration with React Suspense\",\n  \"description\": \"How React's selective hydration works, where to put Suspense boundaries so hydration yields and prioritises interactions, and how to verify it.\",\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\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fselective-hydration-with-react-suspense\u002F\"\n  }\n}\n",[1090,1098,1099],{"type":1092},"\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\": \"JavaScript Bundle Optimization & Code Splitting\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Hydration & Partial Rehydration Strategies\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Selective Hydration with React Suspense\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fselective-hydration-with-react-suspense\u002F\"\n    }\n  ]\n}\n",[1101,1102,1103],"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 .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}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 .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}",{"title":362,"searchDepth":389,"depth":389,"links":1105},[1106,1107,1108,1114,1115,1116,1117,1118,1119,1120],{"id":210,"depth":389,"text":211},{"id":252,"depth":389,"text":253},{"id":349,"depth":389,"text":350,"children":1109},[1110,1111,1112,1113],{"id":354,"depth":417,"text":355},{"id":467,"depth":417,"text":468},{"id":717,"depth":417,"text":718},{"id":732,"depth":417,"text":733},{"id":852,"depth":389,"text":853},{"id":866,"depth":389,"text":867},{"id":873,"depth":389,"text":874},{"id":903,"depth":389,"text":904},{"id":936,"depth":389,"text":937},{"id":989,"depth":389,"text":990},{"id":1064,"depth":389,"text":1065},"How React’s selective hydration works, where to put Suspense boundaries so hydration yields and prioritises interactions, and how to verify it.","md",{"slug":1124,"type":1125,"breadcrumb":1126,"datePublished":1134,"dateModified":1134},"selective-hydration-with-react-suspense","article",[1127,1130,1131,1132],{"name":1128,"url":1129},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1133},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fselective-hydration-with-react-suspense\u002F","2026-10-06",true,"\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fselective-hydration-with-react-suspense",{"title":1138,"description":1139},"Selective Hydration with React Suspense Boundaries","React 18 hydrates Suspense boundaries independently and prioritises the one the user interacts with. Place boundaries to cut startup long tasks and early INP.","javascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fselective-hydration-with-react-suspense\u002Findex","-_86aQcm9jJMhqrWQqNXA8hbI0wGYIvBQjb8iZJJROc",[1143,1147],{"title":1144,"path":1145,"stem":1146},"Resumability vs Hydration","\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fresumability-vs-hydration","javascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fresumability-vs-hydration\u002Findex",{"title":1148,"path":1149,"stem":1150},"JavaScript Performance Budgets","\u002Fjavascript-bundle-optimization-code-splitting\u002Fjavascript-performance-budgets","javascript-bundle-optimization-code-splitting\u002Fjavascript-performance-budgets\u002Findex",1791308076177]