[{"data":1,"prerenderedAt":1096},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Ffixing-hydration-mismatches-that-cost-performance":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Ffixing-hydration-mismatches-that-cost-performance":1087},{"id":4,"title":5,"body":6,"description":1067,"extension":1068,"meta":1069,"navigation":457,"path":1081,"seo":1082,"stem":1085,"__hash__":1086},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Ffixing-hydration-mismatches-that-cost-performance\u002Findex.md","Fixing Hydration Mismatches That Cost Performance",{"type":7,"value":8,"toc":1050},"minimark",[9,14,29,32,163,168,206,210,219,236,245,270,384,388,393,574,577,581,731,734,738,748,752,755,863,867,870,874,877,881,914,918,924,930,936,942,946,956,960,972,981,990,999,1008,1012,1035,1040,1043,1046],[10,11,13],"h1",{"id":12},"how-to-fix-hydration-mismatches-that-cost-performance","How to Fix Hydration Mismatches That Cost Performance",[15,16,17,18,23,24,28],"p",{},"This guide addresses a correctness bug with performance consequences, 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",". Hydration assumes the client's first render produces exactly the HTML the server sent; the framework then attaches event handlers to existing DOM instead of recreating it. When the two renders differ — a mismatch — frameworks recover by patching or re-rendering. React 18+ may discard the server HTML for the affected Suspense boundary (or the whole root) and render it again on the client; Vue patches the mismatched nodes and warns.",[15,30,31],{},"Recovery is not free. Re-rendering a region means creating DOM on the client after it was already painted: extra main-thread work during the busiest moment of the page load, possible layout shifts if the new content differs in size, and a new LCP candidate if the re-rendered region contains the LCP element. Teams often see the console warnings, decide the page \"looks fine\", and never connect them to worse field metrics.",[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,102,42,107,42,114,42,118,42,122,42,126,42,133,42,139,42,143,42,147,42,151,42,155,42,159,42],"svg",{"viewBox":37,"width":38,"role":39,"ariaLabel":40,"style":41},"0 0 760 216","100%","img","Timeline showing server HTML painted, hydration detecting a mismatch, and the client re-rendering the hero, creating a later LCP and a layout shift.","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",{},"Hydration with a mismatch in the hero region",[55,56,40],"desc",{},[44,58],{"x":59,"y":59,"width":60,"height":61,"rx":62,"fill":63,"stroke":64,"style":65},"1","758","214","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","282.3","26.0","3","fill-opacity:0.06;stroke-opacity:0.4",[67,86,91],{"x":87,"y":88,"fill":64,"style":89,"textAnchor":90},"253.1","73.5","font-size:11.5px","middle","server HTML painted",[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":100,"style":101},"419.3","94.0","116.7","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[67,103,106],{"x":104,"y":105,"fill":64,"style":89,"textAnchor":90},"477.7","111.5","hydrate",[44,108],{"x":109,"y":98,"width":110,"height":82,"rx":83,"fill":111,"stroke":112,"style":113},"537.6","104.9","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[67,115,117],{"x":116,"y":105,"fill":64,"style":89,"textAnchor":90},"590.0","client re-render",[67,119,121],{"x":69,"y":120,"fill":64,"style":75},"150.0","Metrics",[44,123],{"x":124,"y":125,"width":70,"height":82,"rx":83,"fill":111,"stroke":112,"style":113},"644.0","132.0",[127,128],"line",{"x1":129,"y1":130,"x2":131,"y2":130,"stroke":64,"style":132},"111.2","172.0","726.0","stroke-opacity:0.4",[67,134,138],{"x":135,"y":136,"fill":137,"style":89,"textAnchor":90},"121.9","189.0","#51617a","0ms",[67,140,142],{"x":141,"y":136,"fill":137,"style":89,"textAnchor":90},"205.8","400ms",[67,144,146],{"x":145,"y":136,"fill":137,"style":89,"textAnchor":90},"300.4","800ms",[67,148,150],{"x":149,"y":136,"fill":137,"style":89,"textAnchor":90},"395.0","1200ms",[67,152,154],{"x":153,"y":136,"fill":137,"style":89,"textAnchor":90},"489.5","1600ms",[67,156,158],{"x":157,"y":136,"fill":137,"style":89,"textAnchor":90},"584.1","2000ms",[67,160,162],{"x":161,"y":136,"fill":137,"style":89,"textAnchor":90},"678.7","2400ms",[164,165,167],"h2",{"id":166},"rapid-diagnosis","Rapid Diagnosis",[169,170,171,179,194,200],"ul",{},[172,173,174,178],"li",{},[175,176,177],"strong",{},"Read the console in development."," React: \"Hydration failed because the server rendered HTML didn't match the client\" or \"Text content does not match\"; Vue: \"Hydration node mismatch\" \u002F \"Hydration text content mismatch\".",[172,180,181,184,185,189,190,193],{},[175,182,183],{},"Check production too."," Production builds report less detail; React 19 logs recoverable errors via ",[186,187,188],"code",{},"onRecoverableError"," on ",[186,191,192],{},"hydrateRoot",". Wire it to your error tracker.",[172,195,196,199],{},[175,197,198],{},"Correlate with LCP and CLS attribution."," If the LCP element or the largest shift source is inside a region that logs mismatches, they are connected.",[172,201,202,205],{},[175,203,204],{},"Trace the hydration task."," In a throttled trace, a second large render after hydration indicates recovery work.",[164,207,209],{"id":208},"root-cause-analysis","Root Cause Analysis",[15,211,212,42,215,218],{},[175,213,214],{},"1. Time- and locale-dependent output.",[186,216,217],{},"new Date().toLocaleString()",", relative times (\"3 minutes ago\"), and number formatting that depends on the browser's locale differ between server and client.",[15,220,221,224,225,228,229,228,232,235],{},[175,222,223],{},"2. Browser-only state."," Reading ",[186,226,227],{},"window.innerWidth",", ",[186,230,231],{},"localStorage",[186,233,234],{},"matchMedia"," or cookies during render produces different output on the server (where they are unavailable).",[15,237,238,42,241,244],{},[175,239,240],{},"3. Randomness and IDs.",[186,242,243],{},"Math.random()"," keys, non-deterministic IDs, or shuffled content.",[15,246,247,42,250,253,254,257,258,261,262,265,266,269],{},[175,248,249],{},"4. Invalid HTML nesting.",[186,251,252],{},"\u003Cdiv>"," inside ",[186,255,256],{},"\u003Cp>",", nested ",[186,259,260],{},"\u003Ca>"," tags or ",[186,263,264],{},"\u003Ctable>"," without ",[186,267,268],{},"\u003Ctbody>"," are \"corrected\" by the browser's parser, so the DOM the client hydrates against differs from what the framework expects.",[15,271,272],{},[35,273,42,276,42,279,42,282,42,284,42,287,42,289,42,293,42,299,42,303,42,307,42,310,42,314,42,318,42,322,42,324,42,328,42,330,42,333,42,336,42,340,42,342,42,345,42,347,42,350,42,353,42,357,42,359,42,362,42,364,42,367,42,370,42,374,42,376,42,379,42,381,42],{"viewBox":274,"width":38,"role":39,"ariaLabel":275,"style":41},"0 0 760 228","Mapping of common hydration mismatch causes to their fixes.",[44,277],{"className":278,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,280,281],{},"Common mismatch causes and fixes",[55,283,275],{},[44,285],{"x":59,"y":59,"width":60,"height":286,"rx":62,"fill":63,"stroke":64,"style":65},"226",[67,288,281],{"x":69,"y":70,"fill":64,"style":71},[44,290],{"x":69,"y":80,"width":291,"height":292,"rx":48,"fill":64,"stroke":64,"style":84},"154.3","30.0",[67,294,298],{"x":295,"y":296,"fill":64,"style":75,"textAnchor":297},"38.0","75.5","start","Cause",[44,300],{"x":301,"y":80,"width":302,"height":292,"rx":48,"fill":64,"stroke":64,"style":84},"182.3","274.9",[67,304,306],{"x":305,"y":296,"fill":64,"style":75,"textAnchor":90},"319.7","Example",[44,308],{"x":309,"y":80,"width":302,"height":292,"rx":48,"fill":64,"stroke":64,"style":84},"457.2",[67,311,313],{"x":312,"y":296,"fill":64,"style":75,"textAnchor":90},"594.6","Fix",[44,315],{"x":69,"y":316,"width":291,"height":292,"rx":48,"fill":63,"stroke":64,"style":317},"86.0","stroke-opacity:0.35",[67,319,321],{"x":295,"y":320,"fill":64,"style":75,"textAnchor":297},"105.5","Time \u002F locale",[44,323],{"x":301,"y":316,"width":302,"height":292,"rx":48,"fill":63,"stroke":64,"style":317},[67,325,327],{"x":305,"y":320,"fill":64,"style":326,"textAnchor":90},"font-size:12px","toLocaleString() in render",[44,329],{"x":309,"y":316,"width":302,"height":292,"rx":48,"fill":100,"stroke":100,"style":101},[67,331,332],{"x":312,"y":320,"fill":64,"style":326,"textAnchor":90},"format on server, pass string",[44,334],{"x":69,"y":335,"width":291,"height":292,"rx":48,"fill":63,"stroke":64,"style":317},"116.0",[67,337,339],{"x":295,"y":338,"fill":64,"style":75,"textAnchor":297},"135.5","Browser APIs in render",[44,341],{"x":301,"y":335,"width":302,"height":292,"rx":48,"fill":63,"stroke":64,"style":317},[67,343,344],{"x":305,"y":338,"fill":64,"style":326,"textAnchor":90},"window.innerWidth, localStorage",[44,346],{"x":309,"y":335,"width":302,"height":292,"rx":48,"fill":100,"stroke":100,"style":101},[67,348,349],{"x":312,"y":338,"fill":64,"style":326,"textAnchor":90},"read in effect after mount",[44,351],{"x":69,"y":352,"width":291,"height":292,"rx":48,"fill":63,"stroke":64,"style":317},"146.0",[67,354,356],{"x":295,"y":355,"fill":64,"style":75,"textAnchor":297},"165.5","Random values",[44,358],{"x":301,"y":352,"width":302,"height":292,"rx":48,"fill":63,"stroke":64,"style":317},[67,360,361],{"x":305,"y":355,"fill":64,"style":326,"textAnchor":90},"Math.random() keys or IDs",[44,363],{"x":309,"y":352,"width":302,"height":292,"rx":48,"fill":100,"stroke":100,"style":101},[67,365,366],{"x":312,"y":355,"fill":64,"style":326,"textAnchor":90},"useId \u002F stable keys",[44,368],{"x":69,"y":369,"width":291,"height":292,"rx":48,"fill":63,"stroke":64,"style":317},"176.0",[67,371,373],{"x":295,"y":372,"fill":64,"style":75,"textAnchor":297},"195.5","Invalid nesting",[44,375],{"x":301,"y":369,"width":302,"height":292,"rx":48,"fill":63,"stroke":64,"style":317},[67,377,378],{"x":305,"y":372,"fill":64,"style":326,"textAnchor":90},"div inside p",[44,380],{"x":309,"y":369,"width":302,"height":292,"rx":48,"fill":100,"stroke":100,"style":101},[67,382,383],{"x":312,"y":372,"fill":64,"style":326,"textAnchor":90},"fix the markup",[164,385,387],{"id":386},"step-by-step-resolution","Step-by-Step Resolution",[389,390,392],"h3",{"id":391},"_1-make-time-and-locale-output-deterministic","1. Make time and locale output deterministic",[394,395,400],"pre",{"className":396,"code":397,"language":398,"meta":399,"style":399},"language-jsx shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Before: server and client format differently.\n\u003Ctime>{new Date(post.publishedAt).toLocaleDateString()}\u003C\u002Ftime>\n\n\u002F\u002F After: format once with an explicit locale and time zone, on the server.\nconst published = new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium', timeZone: 'UTC' }).format(new Date(post.publishedAt));\n\u003Ctime dateTime={post.publishedAt}>{published}\u003C\u002Ftime>\n\u002F\u002F trade-off: a fixed time zone may not match the reader's. If local time\n\u002F\u002F matters, render the UTC value on the server and update it after mount —\n\u002F\u002F keeping the element's size stable to avoid a shift.\n","jsx","",[186,401,402,410,452,459,465,522,556,562,568],{"__ignoreMap":399},[403,404,406],"span",{"class":127,"line":405},1,[403,407,409],{"class":408},"sjfSM","\u002F\u002F Before: server and client format differently.\n",[403,411,413,417,421,424,428,432,435,438,441,444,447,449],{"class":127,"line":412},2,[403,414,416],{"class":415},"saISM","\u003C",[403,418,420],{"class":419},"sZBmE","time",[403,422,423],{"class":415},">",[403,425,427],{"class":426},"sPARh","{new",[403,429,431],{"class":430},"smZ65"," Date",[403,433,434],{"class":415},"(post.publishedAt).",[403,436,437],{"class":430},"toLocaleDateString",[403,439,440],{"class":415},"()",[403,442,443],{"class":426},"}",[403,445,446],{"class":415},"\u003C\u002F",[403,448,420],{"class":419},[403,450,451],{"class":415},">\n",[403,453,455],{"class":127,"line":454},3,[403,456,458],{"emptyLinePlaceholder":457},true,"\n",[403,460,462],{"class":127,"line":461},4,[403,463,464],{"class":408},"\u002F\u002F After: format once with an explicit locale and time zone, on the server.\n",[403,466,468,471,475,478,481,484,487,490,494,497,500,503,506,509,512,514,517,519],{"class":127,"line":467},5,[403,469,470],{"class":426},"const",[403,472,474],{"class":473},"sPXB4"," published",[403,476,477],{"class":426}," =",[403,479,480],{"class":426}," new",[403,482,483],{"class":415}," Intl.",[403,485,486],{"class":430},"DateTimeFormat",[403,488,489],{"class":415},"(",[403,491,493],{"class":492},"sZ8jY","'en-GB'",[403,495,496],{"class":415},", { dateStyle: ",[403,498,499],{"class":492},"'medium'",[403,501,502],{"class":415},", timeZone: ",[403,504,505],{"class":492},"'UTC'",[403,507,508],{"class":415}," }).",[403,510,511],{"class":430},"format",[403,513,489],{"class":415},[403,515,516],{"class":426},"new",[403,518,431],{"class":430},[403,520,521],{"class":415},"(post.publishedAt));\n",[403,523,525,527,529,532,535,538,540,542,545,548,550,552,554],{"class":127,"line":524},6,[403,526,416],{"class":415},[403,528,420],{"class":419},[403,530,531],{"class":473}," dateTime",[403,533,534],{"class":426},"={",[403,536,537],{"class":415},"post.publishedAt",[403,539,443],{"class":426},[403,541,423],{"class":415},[403,543,544],{"class":426},"{",[403,546,547],{"class":415},"published",[403,549,443],{"class":426},[403,551,446],{"class":415},[403,553,420],{"class":419},[403,555,451],{"class":415},[403,557,559],{"class":127,"line":558},7,[403,560,561],{"class":408},"\u002F\u002F trade-off: a fixed time zone may not match the reader's. If local time\n",[403,563,565],{"class":127,"line":564},8,[403,566,567],{"class":408},"\u002F\u002F matters, render the UTC value on the server and update it after mount —\n",[403,569,571],{"class":127,"line":570},9,[403,572,573],{"class":408},"\u002F\u002F keeping the element's size stable to avoid a shift.\n",[15,575,576],{},"Expected outcome: identical server and client output for dates.",[389,578,580],{"id":579},"_2-move-browser-only-reads-into-effects","2. Move browser-only reads into effects",[394,582,584],{"className":396,"code":583,"language":398,"meta":399,"style":399},"function Sidebar() {\n  const [collapsed, setCollapsed] = useState(false);             \u002F\u002F same on server and first client render\n  useEffect(() => setCollapsed(localStorage.getItem('sidebar') === '1'), []);\n  return \u003Caside className={collapsed ? 'collapsed' : ''}>…\u003C\u002Faside>;\n}\n\u002F\u002F trade-off: users with a stored preference see the default for one frame,\n\u002F\u002F then the stored state. Store the preference in a cookie instead so the\n\u002F\u002F server can render it correctly from the start.\n",[186,585,586,597,633,670,711,716,721,726],{"__ignoreMap":399},[403,587,588,591,594],{"class":127,"line":405},[403,589,590],{"class":426},"function",[403,592,593],{"class":430}," Sidebar",[403,595,596],{"class":415},"() {\n",[403,598,599,602,605,608,610,613,616,619,622,624,627,630],{"class":127,"line":412},[403,600,601],{"class":426},"  const",[403,603,604],{"class":415}," [",[403,606,607],{"class":473},"collapsed",[403,609,228],{"class":415},[403,611,612],{"class":473},"setCollapsed",[403,614,615],{"class":415},"] ",[403,617,618],{"class":426},"=",[403,620,621],{"class":430}," useState",[403,623,489],{"class":415},[403,625,626],{"class":473},"false",[403,628,629],{"class":415},");             ",[403,631,632],{"class":408},"\u002F\u002F same on server and first client render\n",[403,634,635,638,641,644,647,650,653,655,658,661,664,667],{"class":127,"line":454},[403,636,637],{"class":430},"  useEffect",[403,639,640],{"class":415},"(() ",[403,642,643],{"class":426},"=>",[403,645,646],{"class":430}," setCollapsed",[403,648,649],{"class":415},"(localStorage.",[403,651,652],{"class":430},"getItem",[403,654,489],{"class":415},[403,656,657],{"class":492},"'sidebar'",[403,659,660],{"class":415},") ",[403,662,663],{"class":426},"===",[403,665,666],{"class":492}," '1'",[403,668,669],{"class":415},"), []);\n",[403,671,672,675,678,681,684,686,689,692,695,698,701,703,706,708],{"class":127,"line":461},[403,673,674],{"class":426},"  return",[403,676,677],{"class":415}," \u003C",[403,679,680],{"class":419},"aside",[403,682,683],{"class":473}," className",[403,685,534],{"class":426},[403,687,688],{"class":415},"collapsed ",[403,690,691],{"class":426},"?",[403,693,694],{"class":492}," 'collapsed'",[403,696,697],{"class":426}," :",[403,699,700],{"class":492}," ''",[403,702,443],{"class":426},[403,704,705],{"class":415},">…\u003C\u002F",[403,707,680],{"class":419},[403,709,710],{"class":415},">;\n",[403,712,713],{"class":127,"line":467},[403,714,715],{"class":415},"}\n",[403,717,718],{"class":127,"line":524},[403,719,720],{"class":408},"\u002F\u002F trade-off: users with a stored preference see the default for one frame,\n",[403,722,723],{"class":127,"line":558},[403,724,725],{"class":408},"\u002F\u002F then the stored state. Store the preference in a cookie instead so the\n",[403,727,728],{"class":127,"line":564},[403,729,730],{"class":408},"\u002F\u002F server can render it correctly from the start.\n",[15,732,733],{},"Expected outcome: no mismatch; any adjustment happens as a normal client update.",[389,735,737],{"id":736},"_3-use-stable-ids-and-keys","3. Use stable IDs and keys",[15,739,740,741,744,745,747],{},"Use ",[186,742,743],{},"useId()"," (React) or framework-provided ID helpers for accessibility IDs, and data IDs for list keys. Never ",[186,746,243],{}," during render.",[389,749,751],{"id":750},"_4-fix-invalid-html","4. Fix invalid HTML",[15,753,754],{},"Run an HTML validator on server output, or watch for nesting warnings in development. Browser parser corrections are invisible in the source but produce mismatches every time.",[15,756,757],{},[35,758,42,761,42,764,42,767,42,769,42,772,42,774,42,780,42,785,42,788,42,791,42,795,42,799,42,802,42,806,42,810,42,813,42,817,42,821,42,828,42,832,42,836,42,841,42,843,42,846,42,850,42,853,42,856,42,859,42],{"viewBox":759,"width":38,"role":39,"ariaLabel":760,"style":41},"0 0 760 318","Four steps to find and eliminate hydration mismatches in production.",[44,762],{"className":763,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,765,766],{},"Clearing hydration mismatches",[55,768,760],{},[44,770],{"x":59,"y":59,"width":60,"height":771,"rx":62,"fill":63,"stroke":64,"style":65},"316",[67,773,766],{"x":69,"y":70,"fill":64,"style":71},[44,775],{"x":776,"y":80,"width":777,"height":778,"rx":779,"fill":100,"stroke":100,"style":101},"72.0","660.0","51.0","6",[67,781,784],{"x":316,"y":782,"fill":64,"style":783,"textAnchor":297},"77.0","font-size:13px;font-weight:700","Report recoverable errors in production",[67,786,787],{"x":316,"y":98,"fill":64,"style":326,"textAnchor":297},"onRecoverableError \u002F app.config.errorHandler to your tracker",[44,789],{"x":776,"y":790,"width":777,"height":778,"rx":779,"fill":100,"stroke":100,"style":101},"119.0",[67,792,794],{"x":316,"y":793,"fill":64,"style":783,"textAnchor":297},"140.0","Group by component and template",[67,796,798],{"x":316,"y":797,"fill":64,"style":326,"textAnchor":297},"157.0","Fix the highest-traffic regions first",[44,800],{"x":776,"y":801,"width":777,"height":778,"rx":779,"fill":100,"stroke":100,"style":101},"182.0",[67,803,805],{"x":316,"y":804,"fill":64,"style":783,"textAnchor":297},"203.0","Make render deterministic",[67,807,809],{"x":316,"y":808,"fill":64,"style":326,"textAnchor":297},"220.0","Fixed locale\u002Ftime zone, stable IDs, no browser APIs in render",[44,811],{"x":776,"y":812,"width":777,"height":778,"rx":779,"fill":100,"stroke":100,"style":101},"245.0",[67,814,816],{"x":316,"y":815,"fill":64,"style":783,"textAnchor":297},"266.0","Validate server HTML",[67,818,820],{"x":316,"y":819,"fill":64,"style":326,"textAnchor":297},"283.0","Catch parser-corrected nesting before it ships",[127,822],{"x1":823,"y1":824,"x2":823,"y2":825,"stroke":64,"strokeWidth":826,"style":827},"43.0","95.5","130.5","1.5","stroke-opacity:0.3",[127,829],{"x1":823,"y1":830,"x2":823,"y2":831,"stroke":64,"strokeWidth":826,"style":827},"158.5","193.5",[127,833],{"x1":823,"y1":834,"x2":823,"y2":835,"stroke":64,"strokeWidth":826,"style":827},"221.5","256.5",[837,838],"circle",{"cx":823,"cy":839,"r":840,"fill":100},"81.5","13",[67,842,59],{"x":823,"y":316,"fill":49,"style":783,"textAnchor":90},[837,844],{"cx":823,"cy":845,"r":840,"fill":100},"144.5",[67,847,849],{"x":823,"y":848,"fill":49,"style":783,"textAnchor":90},"149.0","2",[837,851],{"cx":823,"cy":852,"r":840,"fill":100},"207.5",[67,854,83],{"x":823,"y":855,"fill":49,"style":783,"textAnchor":90},"212.0",[837,857],{"cx":823,"cy":858,"r":840,"fill":100},"270.5",[67,860,862],{"x":823,"y":861,"fill":49,"style":783,"textAnchor":90},"275.0","4",[164,864,866],{"id":865},"verification","Verification",[15,868,869],{},"The console should be free of hydration warnings in development and the error tracker free of recoverable hydration errors in production. In a throttled trace, there should be one hydration pass with no follow-up re-render. If the mismatch was in the LCP region, field LCP should drop; if it caused shifts, CLS attribution should stop naming that region.",[164,871,873],{"id":872},"worked-example-a-last-updated-label-in-the-hero","Worked Example: A \"Last Updated\" Label in the Hero",[15,875,876],{},"A product page rendered \"Updated 3 minutes ago\" in its hero using the current time. Server and client differed by the seconds between render and hydration, so React discarded the hero's server HTML and re-rendered it — including the hero image element, which became a new LCP candidate after hydration. Field LCP on the template was 2.9s, with the LCP timestamp frequently landing just after hydration finished. Rendering an absolute timestamp on the server and updating the relative label in an effect fixed the mismatch; LCP p75 dropped to 2.2s and a recurring 0.04 layout shift disappeared.",[164,878,880],{"id":879},"common-mistakes","Common Mistakes",[169,882,883,893,902,908],{},[172,884,885,892],{},[175,886,887,888,891],{},"Silencing warnings with ",[186,889,890],{},"suppressHydrationWarning","."," It hides one level of text mismatch and is meant for unavoidable cases like timestamps — not structural differences.",[172,894,895,898,899,891],{},[175,896,897],{},"Rendering different components on server and client"," based on ",[186,900,901],{},"typeof window",[172,903,904,907],{},[175,905,906],{},"Ignoring warnings because \"it works\"."," Recovery work is invisible on fast machines and costly on slow ones.",[172,909,910,913],{},[175,911,912],{},"Personalisation in render without server knowledge."," If the server cannot know the value, render a neutral placeholder of the same size.",[164,915,917],{"id":916},"edge-cases","Edge Cases",[15,919,920,923],{},[175,921,922],{},"Browser extensions."," Extensions that modify the DOM before hydration (password managers, translators) cause mismatches you cannot fix; filter them in error reporting by checking for known injected attributes.",[15,925,926,929],{},[175,927,928],{},"CDN HTML rewriting."," Some CDNs or proxies rewrite HTML (email obfuscation, script injection), changing the DOM before hydration. Disable such features for framework-rendered pages.",[15,931,932,935],{},[175,933,934],{},"A\u002FB testing scripts"," that change server-rendered content before hydration produce mismatches and often flicker; run experiments server-side.",[15,937,938,941],{},[175,939,940],{},"Streaming with Suspense."," In React, mismatches inside a Suspense boundary only re-render that boundary, limiting the damage — another reason to use boundaries around independent regions.",[164,943,945],{"id":944},"building-mismatch-detection-into-ci","Building Mismatch Detection into CI",[15,947,948,949,23,952,955],{},"Mismatches tend to be introduced by small, innocent changes — a new date label, a feature flag read in render — so catch them before release. Run an end-to-end test that loads key pages in a real browser with the production build, listens for console errors and recoverable-error reports, and fails on any hydration message. Run it under a different locale and time zone from your CI server's (Playwright's ",[186,950,951],{},"locale",[186,953,954],{},"timezoneId"," options make this easy): many mismatches only appear when the client's environment differs from the server's, which is exactly the situation of real users and never the situation of a developer testing locally.",[164,957,959],{"id":958},"faq","FAQ",[961,962,965,969],"details",{"className":963},[964],"faq-item",[966,967,968],"summary",{},"Does a mismatch always cause a full re-render?",[15,970,971],{},"No. React re-renders the nearest Suspense boundary containing the mismatch (or the root if there is none); Vue patches the specific mismatched nodes. The cost scales with the size of the affected region, which is why boundaries help.",[961,973,975,978],{"className":974},[964],[966,976,977],{},"Is suppressHydrationWarning ever appropriate?",[15,979,980],{},"For a single text node that legitimately differs, such as a timestamp, it avoids a warning and recovery for that node. It does not apply to children or attributes beyond one level, and it should never be used to hide structural differences.",[961,982,984,987],{"className":983},[964],[966,985,986],{},"How do I find mismatches that only happen for some users?",[15,988,989],{},"Report recoverable hydration errors from production with context (route, locale, time zone, user agent). Patterns like \"only users in UTC+10\" or \"only Safari\" point directly at locale, time zone or engine-specific parsing differences.",[961,991,993,996],{"className":992},[964],[966,994,995],{},"Do mismatches affect SEO?",[15,997,998],{},"Search engines see the server HTML; mismatches affect what users see after hydration and performance metrics like LCP and CLS, which do feed into page experience assessments.",[961,1000,1002,1005],{"className":1001},[964],[966,1003,1004],{},"Can islands architectures have mismatches?",[15,1006,1007],{},"Yes, within each island. The blast radius is smaller because only that island re-renders, but the same causes apply.",[164,1009,1011],{"id":1010},"related","Related",[169,1013,1014,1021,1028],{},[172,1015,1016,1020],{},[19,1017,1019],{"href":1018},"\u002Fcore-web-vitals-measurement\u002Fmeasuring-lcp-with-chrome-devtools\u002Ffixing-slow-lcp-in-vue-and-nuxt-apps\u002F","Fixing slow LCP in Vue and Nuxt apps"," — mismatches as an LCP cause in Vue.",[172,1022,1023,1027],{},[19,1024,1026],{"href":1025},"\u002Fcore-web-vitals-measurement\u002Fmeasuring-lcp-with-chrome-devtools\u002Fwhy-the-lcp-element-changes-between-loads\u002F","Why the LCP element changes between loads"," — how re-renders create new candidates.",[172,1029,1030,1034],{},[19,1031,1033],{"href":1032},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fselective-hydration-with-react-suspense\u002F","Selective hydration with React Suspense"," — limiting the blast radius with boundaries.",[1036,1037,1039],"script",{"type":1038},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Fix Hydration Mismatches That Cost Performance\",\n  \"description\": \"Why server\u002Fclient render differences force expensive client re-rendering, how they show up in Core Web Vitals, and how to eliminate them.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Make time and locale output deterministic\",\n      \"text\": \"\u002F\u002F After: format once with an explicit locale and time zone, on the server.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Move browser-only reads into effects\",\n      \"text\": \"Expected outcome: no mismatch; any adjustment happens as a normal client update.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Use stable IDs and keys\",\n      \"text\": \"Use useId() (React) or framework-provided ID helpers for accessibility IDs, and data IDs for list keys.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Fix invalid HTML\",\n      \"text\": \"Run an HTML validator on server output, or watch for nesting warnings in development.\"\n    }\n  ]\n}\n",[1036,1041,1042],{"type":1038},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Fix Hydration Mismatches That Cost Performance\",\n  \"description\": \"Why server\u002Fclient render differences force expensive client re-rendering, how they show up in Core Web Vitals, and how to eliminate them.\",\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\u002Ffixing-hydration-mismatches-that-cost-performance\u002F\"\n  }\n}\n",[1036,1044,1045],{"type":1038},"\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\": \"Fixing Hydration Mismatches That Cost Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Ffixing-hydration-mismatches-that-cost-performance\u002F\"\n    }\n  ]\n}\n",[1047,1048,1049],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}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);}",{"title":399,"searchDepth":412,"depth":412,"links":1051},[1052,1053,1054,1060,1061,1062,1063,1064,1065,1066],{"id":166,"depth":412,"text":167},{"id":208,"depth":412,"text":209},{"id":386,"depth":412,"text":387,"children":1055},[1056,1057,1058,1059],{"id":391,"depth":454,"text":392},{"id":579,"depth":454,"text":580},{"id":736,"depth":454,"text":737},{"id":750,"depth":454,"text":751},{"id":865,"depth":412,"text":866},{"id":872,"depth":412,"text":873},{"id":879,"depth":412,"text":880},{"id":916,"depth":412,"text":917},{"id":944,"depth":412,"text":945},{"id":958,"depth":412,"text":959},{"id":1010,"depth":412,"text":1011},"Why server\u002Fclient render differences force expensive client re-rendering, how they show up in Core Web Vitals, and how to eliminate them.","md",{"slug":1070,"type":1071,"breadcrumb":1072,"datePublished":1080,"dateModified":1080},"fixing-hydration-mismatches-that-cost-performance","article",[1073,1076,1077,1078],{"name":1074,"url":1075},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1079},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Ffixing-hydration-mismatches-that-cost-performance\u002F","2026-10-06","\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Ffixing-hydration-mismatches-that-cost-performance",{"title":1083,"description":1084},"Fixing Hydration Mismatches That Hurt LCP and CLS","Hydration mismatches make frameworks re-render server HTML on the client, adding main-thread work, layout shifts and late LCP. Find and fix the common causes.","javascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Ffixing-hydration-mismatches-that-cost-performance\u002Findex","4NcHbx9apFu62JhGSG9Inwox2BXDen2jzAzumhxdaoc",[1088,1092],{"title":1089,"path":1090,"stem":1091},"Deferring Below-the-Fold Hydration with IntersectionObserver","\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fdeferring-below-the-fold-hydration-with-intersectionobserver","javascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Fdeferring-below-the-fold-hydration-with-intersectionobserver\u002Findex",{"title":1093,"path":1094,"stem":1095},"Fixing Long Hydration Tasks in Next.js App Router","\u002Fjavascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Ffixing-long-hydration-tasks-in-nextjs-app-router","javascript-bundle-optimization-code-splitting\u002Fhydration-and-partial-rehydration-strategies\u002Ffixing-long-hydration-tasks-in-nextjs-app-router\u002Findex",1791308076101]