[{"data":1,"prerenderedAt":2093},["ShallowReactive",2],{"page:\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites":3,"all-docs-nav":1855},{"id":4,"title":5,"body":6,"breadcrumb":1831,"dateModified":1837,"datePublished":1837,"description":1838,"extension":1839,"faq":1840,"meta":1849,"navigation":791,"path":1850,"seo":1851,"slug":12,"stem":1852,"type":1853,"__hash__":1854},"content\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Findex.md","Cumulative Layout Shift Fixes for Static Sites",{"type":7,"value":8,"toc":1811},"minimark",[9,13,17,36,169,174,185,192,195,199,215,301,377,400,403,482,557,565,569,576,673,676,822,844,848,851,854,883,931,934,996,1008,1012,1015,1018,1128,1135,1139,1150,1254,1257,1265,1383,1387,1390,1411,1422,1469,1477,1500,1526,1593,1600,1604,1673,1677,1702,1706,1711,1714,1718,1725,1729,1740,1744,1747,1751,1754,1758,1761,1765,1808],[10,11,5],"h1",{"id":12},"cumulative-layout-shift-fixes-for-static-sites",[14,15,16],"p",{},"Cumulative Layout Shift measures how much visible content jumps around after it first paints. It is the one Core Web Vital where a static site should have an unfair advantage: the markup is fully known at build time, so nothing needs to appear late. In practice static sites still score badly, and almost always for the same four reasons — media without dimensions, web fonts that swap after first paint, embeds injected by scripts, and stylesheets that arrive after the first render pass.",[14,18,19,20,25,26,30,31,35],{},"This guide works through each cause, the build-time fix for it, and the measured CLS before and after. It sits under ",[21,22,24],"a",{"href":23},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002F","Performance Optimization & Core Web Vitals for SSGs"," alongside ",[21,27,29],{"href":28},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002F","Largest Contentful Paint Optimization for Static Sites","; LCP asks ",[32,33,34],"em",{},"when"," the main content appears, CLS asks whether it stays where it landed.",[37,38,39,165],"figure",{},[40,41,48,49,48,53,48,57,48,64],"svg",{"viewBox":42,"role":43,"ariaLabelledBy":44,"xmlns":47},"0 0 760 320","img",[45,46],"cls-anatomy-title","cls-anatomy-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[50,51,52],"title",{"id":45},"Anatomy of a layout shift on a static page",[54,55,56],"desc",{"id":46},"Two stacked renderings of the same article. In the first frame an unsized image collapses to zero height and the heading sits near the top. In the second frame the image has loaded at 320 pixels tall and pushed the heading and paragraph down, producing an impact fraction of 0.62 and a distance fraction of 0.40 for a layout shift score of 0.25.",[58,59],"rect",{"x":60,"y":60,"width":61,"height":62,"fill":63},"0","760","320","#ffffff",[65,66,68,69,68,77,68,88,68,94,68,101,68,108,68,113,68,117,68,120,68,124,68,127,68,131,68,138,68,142,68,147,68,151,68,154,68,157,68,160,48],"g",{"style":67},"font-family:system-ui, sans-serif;font-size:13px","\n    ",[70,71,76],"text",{"x":72,"y":73,"fill":74,"style":75},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","One unsized image, one 0.25 layout shift",[58,78],{"x":79,"y":80,"width":81,"height":82,"rx":83,"fill":84,"opacity":85,"stroke":86,"style":87},"40","52","300","230","12","#d9e2ef","0.35","#556071","stroke-width:1.5px",[70,89,93],{"x":90,"y":91,"fill":86,"style":92},"190","74","font-size:12px;text-anchor:middle","Frame 1 — image not yet decoded",[58,95],{"x":96,"y":97,"width":98,"height":99,"rx":100,"fill":84},"60","86","260","10","4",[58,102],{"x":96,"y":103,"width":98,"height":104,"rx":105,"fill":106,"opacity":107,"stroke":106,"style":87},"106","34","6","#6a4c93","0.16",[70,109,112],{"x":90,"y":110,"fill":74,"style":111},"128","font-size:12px;font-weight:700;text-anchor:middle","Section heading",[58,114],{"x":96,"y":115,"width":98,"height":116,"rx":100,"fill":84},"150","8",[58,118],{"x":96,"y":119,"width":82,"height":116,"rx":100,"fill":84},"166",[58,121],{"x":96,"y":122,"width":123,"height":116,"rx":100,"fill":84},"182","245",[58,125],{"x":126,"y":80,"width":81,"height":82,"rx":83,"fill":84,"opacity":85,"stroke":86,"style":87},"420",[70,128,130],{"x":129,"y":91,"fill":86,"style":92},"570","Frame 2 — image arrives at 320 px tall",[58,132],{"x":133,"y":97,"width":98,"height":134,"rx":105,"fill":135,"opacity":136,"stroke":137,"style":87},"440","76","#ffca3a","0.3","#a97b00",[70,139,141],{"x":129,"y":140,"fill":74,"style":111},"130","hero.jpg decoded",[58,143],{"x":133,"y":144,"width":98,"height":104,"rx":105,"fill":145,"opacity":107,"stroke":146,"style":87},"172","#ff595e","#d83b41",[70,148,150],{"x":129,"y":149,"fill":74,"style":111},"194","Section heading (moved)",[58,152],{"x":133,"y":153,"width":98,"height":116,"rx":100,"fill":84},"216",[58,155],{"x":133,"y":156,"width":82,"height":116,"rx":100,"fill":84},"232",[58,158],{"x":133,"y":159,"width":123,"height":116,"rx":100,"fill":84},"248",[70,161,164],{"x":79,"y":162,"fill":86,"style":163},"302","font-size:12px","impact fraction 0.62  ×  distance fraction 0.40  =  layout shift score 0.25",[166,167,168],"figcaption",{},"The score is the fraction of the viewport that moved multiplied by how far it moved. One unsized image in the article body is enough to blow the 0.1 budget on its own.",[170,171,173],"h2",{"id":172},"how-the-score-is-actually-computed","How the Score Is Actually Computed",[14,175,176,177,180,181,184],{},"Every time a visible element changes position between two frames, the browser records a layout shift value: the ",[32,178,179],{},"impact fraction"," (the share of the viewport occupied by the moving content, before and after, combined) multiplied by the ",[32,182,183],{},"distance fraction"," (the greatest distance any element moved, divided by the viewport's largest dimension). CLS is the largest burst of those values within a 5-second window, where a burst is a run of shifts separated by less than a second.",[14,186,187,188,191],{},"Two consequences matter when you are hunting shifts on a static site. First, a small element that moves a long way scores about the same as a large element that moves slightly, so a 40-pixel banner that pushes the whole article down is not a small problem. Second, because the metric takes the worst ",[32,189,190],{},"burst",", fixing three of four shifts that happen together may not move your score at all — the remaining one still anchors the burst. Fix causes in the order the frames reveal them, not in the order that is easiest.",[14,193,194],{},"Shifts within 500 ms of a user interaction are excluded, which is why an accordion or a menu that expands on click is free. Nothing else gets a pass: a font swap, a lazily injected embed, or a banner appearing 900 ms after load all count in full.",[170,196,198],{"id":197},"reserve-space-for-every-image-and-embed","Reserve Space for Every Image and Embed",[14,200,201,202,206,207,210,211,214],{},"The single biggest source of static-site CLS is media without intrinsic dimensions. An ",[203,204,205],"code",{},"\u003Cimg>"," with no ",[203,208,209],{},"width","\u002F",[203,212,213],{},"height"," attributes has zero height until its bytes arrive, so everything below it sits too high and then jumps. Always emit both attributes and let CSS scale the box:",[216,217,222],"pre",{"className":218,"code":219,"language":220,"meta":221,"style":221},"language-html shiki shiki-themes github-light github-dark","\u003Cimg src=\"\u002Fimg\u002Fhero.avif\" width=\"1200\" height=\"675\" alt=\"Build pipeline overview\"\n     style=\"max-width:100%;height:auto\" loading=\"eager\" fetchpriority=\"high\">\n","html","",[203,223,224,271],{"__ignoreMap":221},[225,226,229,233,236,240,243,247,250,252,255,258,260,263,266,268],"span",{"class":227,"line":228},"line",1,[225,230,232],{"class":231},"sVt8B","\u003C",[225,234,43],{"class":235},"s9eBZ",[225,237,239],{"class":238},"sScJk"," src",[225,241,242],{"class":231},"=",[225,244,246],{"class":245},"sZZnC","\"\u002Fimg\u002Fhero.avif\"",[225,248,249],{"class":238}," width",[225,251,242],{"class":231},[225,253,254],{"class":245},"\"1200\"",[225,256,257],{"class":238}," height",[225,259,242],{"class":231},[225,261,262],{"class":245},"\"675\"",[225,264,265],{"class":238}," alt",[225,267,242],{"class":231},[225,269,270],{"class":245},"\"Build pipeline overview\"\n",[225,272,274,277,279,282,285,287,290,293,295,298],{"class":227,"line":273},2,[225,275,276],{"class":238},"     style",[225,278,242],{"class":231},[225,280,281],{"class":245},"\"max-width:100%;height:auto\"",[225,283,284],{"class":238}," loading",[225,286,242],{"class":231},[225,288,289],{"class":245},"\"eager\"",[225,291,292],{"class":238}," fetchpriority",[225,294,242],{"class":231},[225,296,297],{"class":245},"\"high\"",[225,299,300],{"class":231},">\n",[216,302,306],{"className":303,"code":304,"language":305,"meta":221,"style":221},"language-css shiki shiki-themes github-light github-dark","\u002F* Keeps the reserved box correct when responsive rules change the width *\u002F\n.prose img { max-width: 100%; height: auto; aspect-ratio: attr(width) \u002F attr(height); }\n","css",[203,307,308,314],{"__ignoreMap":221},[225,309,310],{"class":227,"line":228},[225,311,313],{"class":312},"sJ8bj","\u002F* Keeps the reserved box correct when responsive rules change the width *\u002F\n",[225,315,316,319,322,325,329,332,335,339,342,344,346,349,351,354,356,359,362,365,368,370,372,374],{"class":227,"line":273},[225,317,318],{"class":238},".prose",[225,320,321],{"class":235}," img",[225,323,324],{"class":231}," { ",[225,326,328],{"class":327},"sj4cs","max-width",[225,330,331],{"class":231},": ",[225,333,334],{"class":327},"100",[225,336,338],{"class":337},"szBVR","%",[225,340,341],{"class":231},"; ",[225,343,213],{"class":327},[225,345,331],{"class":231},[225,347,348],{"class":327},"auto",[225,350,341],{"class":231},[225,352,353],{"class":327},"aspect-ratio",[225,355,331],{"class":231},[225,357,358],{"class":327},"attr",[225,360,361],{"class":231},"(",[225,363,209],{"class":364},"s4XuR",[225,366,367],{"class":231},") \u002F ",[225,369,358],{"class":327},[225,371,361],{"class":231},[225,373,213],{"class":364},[225,375,376],{"class":231},"); }\n",[14,378,379,380,383,384,387,388,392,393,383,396,399],{},"Most generators can do this for you. Astro's ",[203,381,382],{},"\u003CImage>"," and ",[203,385,386],{},"\u003CPicture>"," read the intrinsic size from the imported file and stamp it into the markup — one of the reasons ",[21,389,391],{"href":390},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002F","Image Optimization Pipelines in Astro"," is worth adopting even if you only care about layout stability. Hugo exposes ",[203,394,395],{},".Width",[203,397,398],{},".Height"," on any resource, and Eleventy's image plugin returns the dimensions with the generated markup.",[14,401,402],{},"Iframes are worse than images because they never have an intrinsic size at all. Wrap them in an aspect-ratio box:",[216,404,406],{"className":303,"code":405,"language":305,"meta":221,"style":221},".embed { aspect-ratio: 16 \u002F 9; width: 100%; }\n.embed > iframe { width: 100%; height: 100%; border: 0; }\n",[203,407,408,441],{"__ignoreMap":221},[225,409,410,413,415,417,419,422,425,428,430,432,434,436,438],{"class":227,"line":228},[225,411,412],{"class":238},".embed",[225,414,324],{"class":231},[225,416,353],{"class":327},[225,418,331],{"class":231},[225,420,421],{"class":327},"16",[225,423,424],{"class":231}," \u002F ",[225,426,427],{"class":327},"9",[225,429,341],{"class":231},[225,431,209],{"class":327},[225,433,331],{"class":231},[225,435,334],{"class":327},[225,437,338],{"class":337},[225,439,440],{"class":231},"; }\n",[225,442,443,445,448,451,453,455,457,459,461,463,465,467,469,471,473,476,478,480],{"class":227,"line":273},[225,444,412],{"class":238},[225,446,447],{"class":337}," >",[225,449,450],{"class":235}," iframe",[225,452,324],{"class":231},[225,454,209],{"class":327},[225,456,331],{"class":231},[225,458,334],{"class":327},[225,460,338],{"class":337},[225,462,341],{"class":231},[225,464,213],{"class":327},[225,466,331],{"class":231},[225,468,334],{"class":327},[225,470,338],{"class":337},[225,472,341],{"class":231},[225,474,475],{"class":327},"border",[225,477,331],{"class":231},[225,479,60],{"class":327},[225,481,440],{"class":231},[483,484,485,504],"table",{},[486,487,488],"thead",{},[489,490,491,495,498,501],"tr",{},[492,493,494],"th",{},"Page",[492,496,497],{},"CLS before",[492,499,500],{},"CLS after",[492,502,503],{},"Change made",[505,506,507,528,544],"tbody",{},[489,508,509,513,516,519],{},[510,511,512],"td",{},"Article with 6 body images",[510,514,515],{},"0.31",[510,517,518],{},"0.01",[510,520,521,210,523,525,526],{},[203,522,209],{},[203,524,213],{}," on every ",[203,527,205],{},[489,529,530,533,536,539],{},[510,531,532],{},"Tutorial with a video embed",[510,534,535],{},"0.24",[510,537,538],{},"0.00",[510,540,541,543],{},[203,542,353],{}," wrapper on the iframe",[489,545,546,549,552,554],{},[510,547,548],{},"Docs index with avatar grid",[510,550,551],{},"0.08",[510,553,538],{},[510,555,556],{},"Fixed 48 px avatar box",[14,558,559,560,564],{},"The full recipe, including the responsive-image case where the aspect ratio changes per breakpoint, is in ",[21,561,563],{"href":562},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Freserving-space-for-images-and-embeds-to-stop-layout-shift\u002F","Reserving Space for Images and Embeds to Stop Layout Shift",".",[170,566,568],{"id":567},"stop-fonts-from-reflowing-the-text","Stop Fonts From Reflowing the Text",[14,570,571,572,575],{},"A web font that loads after first paint re-renders every line it touches. With ",[203,573,574],{},"font-display: swap"," the fallback shows immediately and then swaps — good for LCP, bad for CLS if the two faces have different metrics, because the text reflows and everything below it moves.",[37,577,578,670],{},[40,579,48,584,48,587,48,590,48,592],{"viewBox":580,"role":43,"ariaLabelledBy":581,"xmlns":47},"0 0 760 300",[582,583],"cls-font-title","cls-font-desc",[50,585,586],{"id":582},"Font swap with and without metric overrides",[54,588,589],{"id":583},"Two timelines. The top timeline shows a fallback face rendering at 0.4 seconds, the web font arriving at 1.1 seconds and the text reflowing three lines, shifting the content below by 46 pixels. The bottom timeline uses size-adjust and ascent-override so the swap changes glyph shapes only and the content below stays at zero offset.",[58,591],{"x":60,"y":60,"width":61,"height":81,"fill":63},[65,593,68,594,68,598,68,603,68,608,68,614,68,618,68,620,68,624,68,627,68,632,68,636,68,641,68,644,68,647,68,650,68,654,68,656,68,658,68,662,68,666,48],{"style":67},[70,595,597],{"x":72,"y":596,"fill":74,"style":75},"26","The swap is free only when the metrics match",[70,599,602],{"x":79,"y":600,"fill":146,"style":601},"58","font-size:13px;font-weight:700","Unadjusted fallback",[227,604],{"x1":79,"y1":605,"x2":606,"y2":605,"stroke":84,"style":607},"70","720","stroke-width:2px",[58,609],{"x":96,"y":610,"width":611,"height":612,"rx":116,"fill":84,"opacity":613,"stroke":86,"style":87},"80","180","44","0.5",[70,615,617],{"x":115,"y":616,"fill":74,"style":92},"107","0.4 s · fallback paints",[58,619],{"x":81,"y":610,"width":611,"height":612,"rx":116,"fill":145,"opacity":107,"stroke":146,"style":87},[70,621,623],{"x":622,"y":616,"fill":74,"style":92},"390","1.1 s · web font swaps",[58,625],{"x":626,"y":610,"width":611,"height":612,"rx":116,"fill":145,"opacity":107,"stroke":146,"style":87},"540",[70,628,631],{"x":629,"y":630,"fill":74,"style":92},"630","102","3 lines reflow",[70,633,635],{"x":629,"y":634,"fill":146,"style":111},"118","46 px shift · CLS 0.14",[70,637,640],{"x":79,"y":638,"fill":639,"style":601},"176","#5a8a16","size-adjust + ascent-override",[227,642],{"x1":79,"y1":643,"x2":606,"y2":643,"stroke":84,"style":607},"188",[58,645],{"x":96,"y":646,"width":611,"height":612,"rx":116,"fill":84,"opacity":613,"stroke":86,"style":87},"198",[70,648,617],{"x":115,"y":649,"fill":74,"style":92},"225",[58,651],{"x":81,"y":646,"width":611,"height":612,"rx":116,"fill":652,"opacity":653,"stroke":639,"style":87},"#8ac926","0.2",[70,655,623],{"x":622,"y":649,"fill":74,"style":92},[58,657],{"x":626,"y":646,"width":611,"height":612,"rx":116,"fill":652,"opacity":653,"stroke":639,"style":87},[70,659,661],{"x":629,"y":660,"fill":74,"style":92},"220","same line boxes",[70,663,665],{"x":629,"y":664,"fill":639,"style":111},"236","0 px shift · CLS 0.00",[70,667,669],{"x":79,"y":668,"fill":86,"style":163},"272","Measured on a 900-word article, Chrome 4× CPU throttle, fast 3G profile",[166,671,672],{},"Matching the fallback's metrics to the web font makes the swap invisible to layout: glyph shapes change, line boxes do not.",[14,674,675],{},"Declare a metric-matched fallback so the fallback occupies exactly the space the real font will:",[216,677,679],{"className":303,"code":678,"language":305,"meta":221,"style":221},"@font-face {\n  font-family: 'Inter Fallback';\n  src: local('Arial');\n  size-adjust: 107.4%;\n  ascent-override: 90%;\n  descent-override: 22.4%;\n  line-gap-override: 0%;\n}\n\nbody { font-family: Inter, 'Inter Fallback', system-ui, sans-serif; }\n",[203,680,681,689,702,721,736,751,766,780,786,793],{"__ignoreMap":221},[225,682,683,686],{"class":227,"line":228},[225,684,685],{"class":337},"@font-face",[225,687,688],{"class":231}," {\n",[225,690,691,694,696,699],{"class":227,"line":273},[225,692,693],{"class":327},"  font-family",[225,695,331],{"class":231},[225,697,698],{"class":245},"'Inter Fallback'",[225,700,701],{"class":231},";\n",[225,703,705,708,710,713,715,718],{"class":227,"line":704},3,[225,706,707],{"class":327},"  src",[225,709,331],{"class":231},[225,711,712],{"class":327},"local",[225,714,361],{"class":231},[225,716,717],{"class":245},"'Arial'",[225,719,720],{"class":231},");\n",[225,722,724,727,729,732,734],{"class":227,"line":723},4,[225,725,726],{"class":327},"  size-adjust",[225,728,331],{"class":231},[225,730,731],{"class":327},"107.4",[225,733,338],{"class":337},[225,735,701],{"class":231},[225,737,739,742,744,747,749],{"class":227,"line":738},5,[225,740,741],{"class":327},"  ascent-override",[225,743,331],{"class":231},[225,745,746],{"class":327},"90",[225,748,338],{"class":337},[225,750,701],{"class":231},[225,752,754,757,759,762,764],{"class":227,"line":753},6,[225,755,756],{"class":327},"  descent-override",[225,758,331],{"class":231},[225,760,761],{"class":327},"22.4",[225,763,338],{"class":337},[225,765,701],{"class":231},[225,767,769,772,774,776,778],{"class":227,"line":768},7,[225,770,771],{"class":327},"  line-gap-override",[225,773,331],{"class":231},[225,775,60],{"class":327},[225,777,338],{"class":337},[225,779,701],{"class":231},[225,781,783],{"class":227,"line":782},8,[225,784,785],{"class":231},"}\n",[225,787,789],{"class":227,"line":788},9,[225,790,792],{"emptyLinePlaceholder":791},true,"\n",[225,794,796,799,801,804,807,809,812,815,817,820],{"class":227,"line":795},10,[225,797,798],{"class":235},"body",[225,800,324],{"class":231},[225,802,803],{"class":327},"font-family",[225,805,806],{"class":231},": Inter, ",[225,808,698],{"class":245},[225,810,811],{"class":231},", ",[225,813,814],{"class":327},"system-ui",[225,816,811],{"class":231},[225,818,819],{"class":327},"sans-serif",[225,821,440],{"class":231},[14,823,824,825,829,830,834,835,838,839,843],{},"Self-hosting the font removes the extra DNS lookup and connection that delay the swap in the first place — see ",[21,826,828],{"href":827},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fself-hosting-google-fonts-to-eliminate-layout-shift\u002F","Self-Hosting Google Fonts to Eliminate Layout Shift"," and the wider ",[21,831,833],{"href":832},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002F","Font Loading Strategies for Static Sites",". On a documentation site we measured, self-hosting plus ",[203,836,837],{},"size-adjust"," took CLS from ",[840,841,842],"strong",{},"0.14 to 0.00"," and shaved 180 ms off the swap.",[170,845,847],{"id":846},"keep-late-arriving-scripts-out-of-the-layout","Keep Late-Arriving Scripts Out of the Layout",[14,849,850],{},"Anything a script inserts into the document flow after paint shifts everything below it: consent banners, newsletter forms, comment widgets, ad slots, \"related posts\" pulled from an API. On a static site these are the shifts that survive after you have fixed the media.",[14,852,853],{},"Three rules cover almost every case:",[855,856,857,864,877],"ol",{},[858,859,860,863],"li",{},[840,861,862],{},"Render the container at build time."," If a widget will occupy 320 px, emit an empty 320 px box in the HTML and let the script fill it. The generator already knows the widget exists.",[858,865,866,869,870,873,874,564],{},[840,867,868],{},"Take overlays out of flow."," A cookie banner should be ",[203,871,872],{},"position: fixed"," over the page, not a block prepended to ",[203,875,876],{},"\u003Cbody>",[858,878,879,882],{},[840,880,881],{},"Never inject above existing content."," Appending below the fold is cheap; prepending is the most expensive shift a page can make.",[216,884,886],{"className":218,"code":885,"language":220,"meta":221,"style":221},"\u003C!-- Space reserved at build time; the script only fills it -->\n\u003Cdiv class=\"comments\" style=\"min-height:420px\" data-comments=\"post-42\">\u003C\u002Fdiv>\n",[203,887,888,893],{"__ignoreMap":221},[225,889,890],{"class":227,"line":228},[225,891,892],{"class":312},"\u003C!-- Space reserved at build time; the script only fills it -->\n",[225,894,895,897,900,903,905,908,911,913,916,919,921,924,927,929],{"class":227,"line":273},[225,896,232],{"class":231},[225,898,899],{"class":235},"div",[225,901,902],{"class":238}," class",[225,904,242],{"class":231},[225,906,907],{"class":245},"\"comments\"",[225,909,910],{"class":238}," style",[225,912,242],{"class":231},[225,914,915],{"class":245},"\"min-height:420px\"",[225,917,918],{"class":238}," data-comments",[225,920,242],{"class":231},[225,922,923],{"class":245},"\"post-42\"",[225,925,926],{"class":231},">\u003C\u002F",[225,928,899],{"class":235},[225,930,300],{"class":231},[14,932,933],{},"The comparison table below is from a real audit of a docs site that added three widgets over a year:",[483,935,936,949],{},[486,937,938],{},[489,939,940,943,946],{},[492,941,942],{},"Injected element",[492,944,945],{},"Placement",[492,947,948],{},"CLS contribution",[505,950,951,962,972,983],{},[489,952,953,956,959],{},[510,954,955],{},"Consent banner prepended to body",[510,957,958],{},"in flow, top",[510,960,961],{},"0.19",[489,963,964,967,970],{},[510,965,966],{},"Same banner as fixed overlay",[510,968,969],{},"out of flow",[510,971,538],{},[489,973,974,977,980],{},[510,975,976],{},"Comments loaded on scroll, no reserved box",[510,978,979],{},"in flow, mid-page",[510,981,982],{},"0.11",[489,984,985,992,994],{},[510,986,987,988,991],{},"Comments with ",[203,989,990],{},"min-height"," reserved",[510,993,979],{},[510,995,538],{},[14,997,998,999,1003,1004,564],{},"Lazy-loaded embeds need the same treatment, which is covered end to end in ",[21,1000,1002],{"href":1001},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-late-loading-embeds\u002F","Fixing CLS From Late-Loading Embeds"," and, from the byte-cost angle, in ",[21,1005,1007],{"href":1006},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002F","Third-Party Script Performance on Static Sites",[170,1009,1011],{"id":1010},"ship-css-before-the-first-render-not-after","Ship CSS Before the First Render, Not After",[14,1013,1014],{},"A stylesheet that arrives after the browser has painted causes a re-layout of everything it touches. On static sites this usually shows up in one of two ways: a deferred \"non-critical\" stylesheet that turns out to control layout, or a JavaScript-injected style block from a component library.",[14,1016,1017],{},"Inline the CSS that governs above-the-fold structure and load the rest with a non-blocking pattern that cannot reorder your layout:",[216,1019,1021],{"className":218,"code":1020,"language":220,"meta":221,"style":221},"\u003Cstyle>\u002F* critical: header, hero, typography scale, grid *\u002F\u003C\u002Fstyle>\n\u003Clink rel=\"preload\" href=\"\u002Fcss\u002Frest.css\" as=\"style\" onload=\"this.rel='stylesheet'\">\n\u003Cnoscript>\u003Clink rel=\"stylesheet\" href=\"\u002Fcss\u002Frest.css\">\u003C\u002Fnoscript>\n",[203,1022,1023,1043,1097],{"__ignoreMap":221},[225,1024,1025,1027,1030,1033,1036,1039,1041],{"class":227,"line":228},[225,1026,232],{"class":231},[225,1028,1029],{"class":235},"style",[225,1031,1032],{"class":231},">",[225,1034,1035],{"class":312},"\u002F* critical: header, hero, typography scale, grid *\u002F",[225,1037,1038],{"class":231},"\u003C\u002F",[225,1040,1029],{"class":235},[225,1042,300],{"class":231},[225,1044,1045,1047,1050,1053,1055,1058,1061,1063,1066,1069,1071,1074,1077,1079,1082,1085,1087,1090,1092,1095],{"class":227,"line":273},[225,1046,232],{"class":231},[225,1048,1049],{"class":235},"link",[225,1051,1052],{"class":238}," rel",[225,1054,242],{"class":231},[225,1056,1057],{"class":245},"\"preload\"",[225,1059,1060],{"class":238}," href",[225,1062,242],{"class":231},[225,1064,1065],{"class":245},"\"\u002Fcss\u002Frest.css\"",[225,1067,1068],{"class":238}," as",[225,1070,242],{"class":231},[225,1072,1073],{"class":245},"\"style\"",[225,1075,1076],{"class":238}," onload",[225,1078,242],{"class":231},[225,1080,1081],{"class":245},"\"",[225,1083,1084],{"class":327},"this",[225,1086,564],{"class":245},[225,1088,1089],{"class":231},"rel",[225,1091,242],{"class":337},[225,1093,1094],{"class":245},"'stylesheet'\"",[225,1096,300],{"class":231},[225,1098,1099,1101,1104,1107,1109,1111,1113,1116,1118,1120,1122,1124,1126],{"class":227,"line":704},[225,1100,232],{"class":231},[225,1102,1103],{"class":235},"noscript",[225,1105,1106],{"class":231},">\u003C",[225,1108,1049],{"class":235},[225,1110,1052],{"class":238},[225,1112,242],{"class":231},[225,1114,1115],{"class":245},"\"stylesheet\"",[225,1117,1060],{"class":238},[225,1119,242],{"class":231},[225,1121,1065],{"class":245},[225,1123,926],{"class":231},[225,1125,1103],{"class":235},[225,1127,300],{"class":231},[14,1129,1130,1131,564],{},"Keep the deferred file free of layout-defining rules — colours, hover states and print styles are safe; grid definitions and font sizes are not. The render-blocking side of this trade-off is covered in ",[21,1132,1134],{"href":1133},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Feliminating-render-blocking-css-on-static-sites\u002F","Eliminating Render-Blocking CSS on Static Sites",[170,1136,1138],{"id":1137},"find-shifts-instead-of-guessing","Find Shifts Instead of Guessing",[14,1140,1141,1142,1145,1146,1149],{},"Chrome DevTools shows the culprit directly: open ",[840,1143,1144],{},"Performance",", record a load with 4× CPU throttling, and look at the ",[32,1147,1148],{},"Layout Shifts"," track — each entry names the moved node and shows before\u002Fafter rectangles. For a whole-site sweep, script it with the Layout Instability API:",[216,1151,1155],{"className":1152,"code":1153,"language":1154,"meta":221,"style":221},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F node scripts\u002Fcls-audit.mjs — prints CLS and the worst node per URL\nimport puppeteer from 'puppeteer';\n\nconst browser = await puppeteer.launch();\nconst page = await browser.newPage();\nawait page.evaluateOnNewDocument(() => {\n  window.__shifts = [];\n  new PerformanceObserver((list) => {\n    for (const entry of list.getEntries()) {\n      if (!entry.hadRecentInput) window.__shifts.push(entry);\n    }\n  }).observe({ type: 'layout-shift', buffered: true });\n});\nawait page.goto(process.argv[2], { waitUntil: 'networkidle0' });\nconst total = await page.evaluate(() =>\n  window.__shifts.reduce((sum, s) => sum + s.value, 0));\nconsole.log(process.argv[2], total.toFixed(3));\nawait browser.close();\n","javascript",[203,1156,1157,1162,1167,1171,1176,1181,1186,1191,1196,1201,1206,1212,1218,1224,1230,1236,1242,1248],{"__ignoreMap":221},[225,1158,1159],{"class":227,"line":228},[225,1160,1161],{},"\u002F\u002F node scripts\u002Fcls-audit.mjs — prints CLS and the worst node per URL\n",[225,1163,1164],{"class":227,"line":273},[225,1165,1166],{},"import puppeteer from 'puppeteer';\n",[225,1168,1169],{"class":227,"line":704},[225,1170,792],{"emptyLinePlaceholder":791},[225,1172,1173],{"class":227,"line":723},[225,1174,1175],{},"const browser = await puppeteer.launch();\n",[225,1177,1178],{"class":227,"line":738},[225,1179,1180],{},"const page = await browser.newPage();\n",[225,1182,1183],{"class":227,"line":753},[225,1184,1185],{},"await page.evaluateOnNewDocument(() => {\n",[225,1187,1188],{"class":227,"line":768},[225,1189,1190],{},"  window.__shifts = [];\n",[225,1192,1193],{"class":227,"line":782},[225,1194,1195],{},"  new PerformanceObserver((list) => {\n",[225,1197,1198],{"class":227,"line":788},[225,1199,1200],{},"    for (const entry of list.getEntries()) {\n",[225,1202,1203],{"class":227,"line":795},[225,1204,1205],{},"      if (!entry.hadRecentInput) window.__shifts.push(entry);\n",[225,1207,1209],{"class":227,"line":1208},11,[225,1210,1211],{},"    }\n",[225,1213,1215],{"class":227,"line":1214},12,[225,1216,1217],{},"  }).observe({ type: 'layout-shift', buffered: true });\n",[225,1219,1221],{"class":227,"line":1220},13,[225,1222,1223],{},"});\n",[225,1225,1227],{"class":227,"line":1226},14,[225,1228,1229],{},"await page.goto(process.argv[2], { waitUntil: 'networkidle0' });\n",[225,1231,1233],{"class":227,"line":1232},15,[225,1234,1235],{},"const total = await page.evaluate(() =>\n",[225,1237,1239],{"class":227,"line":1238},16,[225,1240,1241],{},"  window.__shifts.reduce((sum, s) => sum + s.value, 0));\n",[225,1243,1245],{"class":227,"line":1244},17,[225,1246,1247],{},"console.log(process.argv[2], total.toFixed(3));\n",[225,1249,1251],{"class":227,"line":1250},18,[225,1252,1253],{},"await browser.close();\n",[14,1255,1256],{},"Run it over your sitemap in CI and fail the build when a page regresses past 0.05. Two details make the difference between a useful gate and a flaky one. Set a fixed viewport and device scale factor, because the distance fraction is normalised against viewport size and a different window silently changes every score. And take the median of three runs rather than a single sample: a cold-cache run that loses a font race can double a page's score without anything in the repository having changed. A gate that cries wolf gets disabled within a fortnight, which is worse than not having one.",[14,1258,1259,1260,1264],{},"Sample the pages that differ structurally rather than every URL — one article template, one index, one page with an embed and the homepage will surface a regression that a hundred near-identical article pages would only repeat. That keeps the check inside a minute even on a large site, so it can run on every pull request instead of nightly. Lab numbers are only half the story though: the field data from real devices is what Core Web Vitals reports, and it captures shifts a scripted run never triggers. ",[21,1261,1263],{"href":1262},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js\u002F","Measuring CLS in the Field With web-vitals.js"," covers the collection side, including how to attribute a field score to a specific element.",[37,1266,1267,1380],{},[40,1268,48,1272,48,1275,48,1278,48,1280],{"viewBox":580,"role":43,"ariaLabelledBy":1269,"xmlns":47},[1270,1271],"cls-budget-title","cls-budget-desc",[50,1273,1274],{"id":1270},"CLS budget burn-down across four fixes",[54,1276,1277],{"id":1271},"A stacked burn-down chart. The starting CLS of 0.42 falls to 0.23 after images get dimensions, to 0.12 after the consent banner becomes an overlay, to 0.04 after font metric overrides, and to 0.01 after the deferred stylesheet is inlined. A dashed line marks the 0.1 good threshold, crossed at the third fix.",[58,1279],{"x":60,"y":60,"width":61,"height":81,"fill":63},[65,1281,68,1283,68,1286,68,1288,68,1290,68,1295,68,1298,68,1302,68,1308,68,1315,68,1319,68,1325,68,1330,68,1336,68,1341,68,1346,68,1351,68,1356,68,1359,68,1364,68,1367,68,1370,68,1373,68,1376,48],{"style":1282},"font-family:system-ui, sans-serif;font-size:12px",[70,1284,1285],{"x":72,"y":596,"fill":74,"style":75},"Burning down 0.42 CLS on one docs template",[227,1287],{"x1":746,"y1":96,"x2":746,"y2":82,"stroke":86,"style":87},[227,1289],{"x1":746,"y1":82,"x2":606,"y2":82,"stroke":86,"style":87},[70,1291,1294],{"x":104,"y":1292,"fill":86,"style":1293},"66","font-size:11px","0.45",[70,1296,538],{"x":79,"y":1297,"fill":86,"style":1293},"234",[227,1299],{"x1":746,"y1":1300,"x2":606,"y2":1300,"stroke":652,"style":1301},"192","stroke-width:2px;stroke-dasharray:6 5",[70,1303,1307],{"x":1304,"y":1305,"fill":639,"style":1306},"600","186","font-size:11px;font-weight:700","0.10 good threshold",[58,1309],{"x":1310,"y":1311,"width":1312,"height":1313,"rx":105,"fill":145,"opacity":1314,"stroke":146,"style":87},"120","72","84","158","0.18",[70,1316,1318],{"x":1317,"y":1292,"fill":146,"style":111},"162","0.42",[58,1320],{"x":1321,"y":1322,"width":1312,"height":1323,"rx":105,"fill":145,"opacity":1324,"stroke":146,"style":87},"244","143","87","0.14",[70,1326,1329],{"x":1327,"y":1328,"fill":146,"style":111},"286","137","0.23",[58,1331],{"x":1332,"y":1333,"width":1312,"height":1334,"rx":105,"fill":135,"opacity":1335,"stroke":137,"style":87},"368","185","45","0.4",[70,1337,1340],{"x":1338,"y":1339,"fill":137,"style":111},"410","179","0.12",[58,1342],{"x":1343,"y":1344,"width":1312,"height":1345,"rx":100,"fill":652,"opacity":136,"stroke":639,"style":87},"492","215","15",[70,1347,1350],{"x":1348,"y":1349,"fill":639,"style":111},"534","209","0.04",[58,1352],{"x":1353,"y":1354,"width":1312,"height":100,"rx":1355,"fill":652,"opacity":1294,"stroke":639,"style":87},"616","226","2",[70,1357,518],{"x":1358,"y":660,"fill":639,"style":111},"658",[70,1360,1363],{"x":1317,"y":1361,"fill":86,"style":1362},"250","font-size:11px;text-anchor:middle","baseline",[70,1365,1366],{"x":1327,"y":1361,"fill":86,"style":1362},"image dims",[70,1368,1369],{"x":1338,"y":1361,"fill":86,"style":1362},"banner overlay",[70,1371,1372],{"x":1348,"y":1361,"fill":86,"style":1362},"font metrics",[70,1374,1375],{"x":1358,"y":1361,"fill":86,"style":1362},"inline CSS",[70,1377,1379],{"x":746,"y":1378,"fill":86,"style":1293},"278","Chrome 4× CPU throttle, fast 3G, median of 5 runs · lower is better",[166,1381,1382],{},"Fixes are not equal: images and the banner accounted for 71% of the score on this template, and the last two changes were what took it from \"needs improvement\" to effectively zero.",[170,1384,1386],{"id":1385},"what-each-generator-gives-you-for-free","What Each Generator Gives You for Free",[14,1388,1389],{},"The four causes above are universal, but how much your generator prevents by default varies enough to change where you spend the effort.",[14,1391,1392,1395,1396,1399,1400,1403,1404,1406,1407,1410],{},[840,1393,1394],{},"Astro"," is the strongest out of the box. Images imported through ",[203,1397,1398],{},"astro:assets"," carry their intrinsic dimensions into the markup, and island components are server-rendered first, so hydration replaces markup that already occupies the right space. The residual risk is a ",[203,1401,1402],{},"client:only"," island — it renders nothing on the server, so whatever it occupies appears after hydration. Give those a wrapper with a fixed ",[203,1405,990],{},", or move them to ",[203,1408,1409],{},"client:visible"," with server-rendered fallback content.",[14,1412,1413,1416,1417,383,1419,1421],{},[840,1414,1415],{},"Hugo"," gives you the dimensions but not the markup: ",[203,1418,395],{},[203,1420,398],{}," are available on every image resource, and it is on you to emit them. A render hook is the cheapest way to make that automatic for Markdown images:",[216,1423,1427],{"className":1424,"code":1425,"language":1426,"meta":221,"style":221},"language-go-html-template shiki shiki-themes github-light github-dark","{{\u002F* layouts\u002F_default\u002F_markup\u002Frender-image.html *\u002F}}\n{{- $img := .Page.Resources.GetMatch .Destination -}}\n{{- with $img -}}\n  \u003Cimg src=\"{{ .RelPermalink }}\" width=\"{{ .Width }}\" height=\"{{ .Height }}\"\n       alt=\"{{ $.Text }}\" loading=\"lazy\" decoding=\"async\">\n{{- else -}}\n  \u003Cimg src=\"{{ .Destination }}\" alt=\"{{ .Text }}\" loading=\"lazy\">\n{{- end -}}\n","go-html-template",[203,1428,1429,1434,1439,1444,1449,1454,1459,1464],{"__ignoreMap":221},[225,1430,1431],{"class":227,"line":228},[225,1432,1433],{},"{{\u002F* layouts\u002F_default\u002F_markup\u002Frender-image.html *\u002F}}\n",[225,1435,1436],{"class":227,"line":273},[225,1437,1438],{},"{{- $img := .Page.Resources.GetMatch .Destination -}}\n",[225,1440,1441],{"class":227,"line":704},[225,1442,1443],{},"{{- with $img -}}\n",[225,1445,1446],{"class":227,"line":723},[225,1447,1448],{},"  \u003Cimg src=\"{{ .RelPermalink }}\" width=\"{{ .Width }}\" height=\"{{ .Height }}\"\n",[225,1450,1451],{"class":227,"line":738},[225,1452,1453],{},"       alt=\"{{ $.Text }}\" loading=\"lazy\" decoding=\"async\">\n",[225,1455,1456],{"class":227,"line":753},[225,1457,1458],{},"{{- else -}}\n",[225,1460,1461],{"class":227,"line":768},[225,1462,1463],{},"  \u003Cimg src=\"{{ .Destination }}\" alt=\"{{ .Text }}\" loading=\"lazy\">\n",[225,1465,1466],{"class":227,"line":782},[225,1467,1468],{},"{{- end -}}\n",[14,1470,1471,1472,1476],{},"Once that hook exists, every image in every Markdown file gets a reserved box without an author remembering anything — the same leverage described in ",[21,1473,1475],{"href":1474},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Fspeeding-up-hugo-builds-with-render-hooks-and-caching\u002F","Speeding Up Hugo Builds With Render Hooks and Caching",", applied to layout stability instead of build time.",[14,1478,1479,1482,1483,1486,1487,1490,1491,1494,1495,1497,1498,564],{},[840,1480,1481],{},"Eleventy"," ships no image handling by default, so the eleventy-img plugin's shortcode is the place to enforce it; it returns fully formed ",[203,1484,1485],{},"\u003Cpicture>"," markup with dimensions on every ",[203,1488,1489],{},"\u003Csource>",". Because Eleventy templates are plain functions, a build-time guard is easy: walk the generated HTML in an ",[203,1492,1493],{},"eleventy.after"," hook and fail the build on any ",[203,1496,205],{}," missing ",[203,1499,209],{},[14,1501,1502,1505,1506,1509,1510,1513,1514,1516,1517,1520,1521,1525],{},[840,1503,1504],{},"Next.js static export"," covers images through ",[203,1507,1508],{},"next\u002Fimage",", but the export target disables the default image optimizer, so you configure ",[203,1511,1512],{},"images.unoptimized"," or a custom loader — and an unoptimized ",[203,1515,205],{}," fallback still needs explicit dimensions. The gotcha specific to this target is client-only content: anything rendered inside a ",[203,1518,1519],{},"useEffect"," is absent from the exported HTML and lands after hydration. See ",[21,1522,1524],{"href":1523},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fhandling-dynamic-routes-in-nextjs-static-export\u002F","Handling Dynamic Routes in Next.js Static Export"," for the related export-time constraints.",[483,1527,1528,1541],{},[486,1529,1530],{},[489,1531,1532,1535,1538],{},[492,1533,1534],{},"Generator",[492,1536,1537],{},"Dimensions by default",[492,1539,1540],{},"Main residual risk",[505,1542,1543,1557,1567,1580],{},[489,1544,1545,1547,1552],{},[510,1546,1394],{},[510,1548,1549,1550],{},"Yes, via ",[203,1551,1398],{},[510,1553,1554,1556],{},[203,1555,1402],{}," islands render late",[489,1558,1559,1561,1564],{},[510,1560,1415],{},[510,1562,1563],{},"Available, not emitted",[510,1565,1566],{},"Markdown images without a render hook",[489,1568,1569,1571,1574],{},[510,1570,1481],{},[510,1572,1573],{},"No",[510,1575,1576,1577,1579],{},"Plugin-free ",[203,1578,205],{}," in templates",[489,1581,1582,1585,1590],{},[510,1583,1584],{},"Next.js export",[510,1586,1587,1588],{},"Via ",[203,1589,1508],{},[510,1591,1592],{},"Effect-rendered content missing from HTML",[14,1594,1595,1596,564],{},"A useful rule when you are choosing between them for a content site: the generator that renders more of the page at build time has less surface for shift, which is one of the practical trade-offs weighed in the ",[21,1597,1599],{"href":1598},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002F","SSG Framework Selection Matrix",[170,1601,1603],{"id":1602},"common-pitfalls","Common Pitfalls",[1605,1606,1607,1613,1622,1638,1661,1667],"ul",{},[858,1608,1609,1612],{},[840,1610,1611],{},"Trusting a green lab score."," Lighthouse stops recording after the run; a widget that loads on scroll never fires. Always cross-check with field data.",[858,1614,1615,1621],{},[840,1616,1617,1620],{},[203,1618,1619],{},"height: auto"," without intrinsic dimensions."," The CSS is correct but the browser still has nothing to reserve until bytes arrive. The attributes are what create the box.",[858,1623,1624,1627,1628,1630,1631,1633,1634,210,1636,564],{},[840,1625,1626],{},"Aspect ratios that differ per breakpoint."," Art-directed ",[203,1629,1485],{}," sources with different ratios re-reserve space on resize; give each ",[203,1632,1489],{}," its own ",[203,1635,209],{},[203,1637,213],{},[858,1639,1640,1643,1644,811,1646,1649,1650,1653,1654,383,1657,1660],{},[840,1641,1642],{},"Animating layout properties."," Transitioning ",[203,1645,213],{},[203,1647,1648],{},"top"," or ",[203,1651,1652],{},"margin"," registers as shifts; animate ",[203,1655,1656],{},"transform",[203,1658,1659],{},"opacity"," instead, which never trigger layout.",[858,1662,1663,1666],{},[840,1664,1665],{},"Fixing the small shifts first."," CLS takes the worst burst, so clearing three tiny shifts inside a burst dominated by a fourth changes nothing.",[858,1668,1669,1672],{},[840,1670,1671],{},"Forgetting the scroll case."," Content revealed on scroll counts fully. If it is lazy, it still needs a reserved box.",[170,1674,1676],{"id":1675},"key-takeaways","Key Takeaways",[1605,1678,1679,1687,1693,1696,1699],{},[858,1680,1681,1682,383,1684,1686],{},"Emit ",[203,1683,209],{},[203,1685,213],{}," on every image and an aspect-ratio wrapper on every iframe — this alone usually halves a bad score.",[858,1688,1689,1690,1692],{},"Self-host fonts and metric-match the fallback with ",[203,1691,837],{},", so the swap changes glyphs and not line boxes.",[858,1694,1695],{},"Reserve space at build time for anything a script will inject, and put overlays out of flow.",[858,1697,1698],{},"Keep layout-defining CSS in the first render pass; defer only rules that cannot move anything.",[858,1700,1701],{},"Measure both ways: a scripted Layout Instability run in CI for regressions, plus field CLS for what readers actually experience.",[170,1703,1705],{"id":1704},"faq","FAQ",[1707,1708,1710],"h3",{"id":1709},"what-cls-score-should-a-static-site-aim-for","What CLS score should a static site aim for?",[14,1712,1713],{},"Under 0.1 at the 75th percentile of real page loads, which is the Core Web Vitals \"good\" threshold. A static site that reserves space for every image, embed and font has no structural reason to score above 0.02, so treat anything above 0.05 as a bug with a specific cause rather than as noise.",[1707,1715,1717],{"id":1716},"why-does-my-cls-look-fine-in-lighthouse-but-bad-in-field-data","Why does my CLS look fine in Lighthouse but bad in field data?",[14,1719,1720,1721,1724],{},"Lighthouse measures a single cold load in a fixed viewport and stops recording at the end of the run. Field data captures the whole page lifespan, including shifts caused by lazy-loaded embeds, consent banners, A\u002FB test swaps and content that arrives when a reader scrolls. Collect field CLS with the ",[203,1722,1723],{},"web-vitals"," library before assuming a lab score represents reality.",[1707,1726,1728],{"id":1727},"does-aspect-ratio-replace-width-and-height-attributes-on-images","Does aspect-ratio replace width and height attributes on images?",[14,1730,1731,1732,383,1734,1736,1737,1739],{},"No, use both. The ",[203,1733,209],{},[203,1735,213],{}," attributes give the browser an intrinsic aspect ratio before any CSS parses, which is what protects the very first layout. The CSS ",[203,1738,353],{}," property keeps that box correct when responsive rules override the rendered width.",[1707,1741,1743],{"id":1742},"do-layout-shifts-below-the-fold-count","Do layout shifts below the fold count?",[14,1745,1746],{},"Yes. CLS is measured across the whole viewport whenever content is visible, so a shift that happens while the reader has scrolled to that section counts fully. Shifts in areas the reader never sees are not recorded, which is why lazy-loaded embeds must reserve space too.",[1707,1748,1750],{"id":1749},"is-a-shift-caused-by-a-user-click-counted","Is a shift caused by a user click counted?",[14,1752,1753],{},"Shifts within 500 milliseconds of a user interaction are excluded, so an accordion that expands on click is free. That exclusion window is narrow and does not cover a script that reflows the page 800 milliseconds after a click, so do not rely on it to excuse slow interactions.",[1707,1755,1757],{"id":1756},"how-does-a-static-generator-help-with-cls-compared-with-a-server-rendered-app","How does a static generator help with CLS compared with a server-rendered app?",[14,1759,1760],{},"A static generator knows the final markup at build time, so it can stamp intrinsic dimensions, inline critical CSS and pre-render every element that would otherwise appear after hydration. Most CLS on a static site therefore comes from things added after the build, such as third-party embeds and remote fonts.",[170,1762,1764],{"id":1763},"related","Related",[1605,1766,1767,1776,1781,1788,1793,1798,1803],{},[858,1768,1769,1772,1773,1775],{},[840,1770,1771],{},"Parent:"," ",[21,1774,24],{"href":23}," — where CLS sits among the vitals.",[858,1777,1778,1780],{},[21,1779,563],{"href":562}," — the dimension recipe in full.",[858,1782,1783,1787],{},[21,1784,1786],{"href":1785},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Feliminating-layout-shift-from-web-fonts\u002F","Eliminating Layout Shift From Web Fonts"," — metric overrides, preloading and the swap window.",[858,1789,1790,1792],{},[21,1791,1002],{"href":1001}," — placeholders for iframes, ads and comments.",[858,1794,1795,1797],{},[21,1796,1263],{"href":1262}," — real-user collection and attribution.",[858,1799,1800,1802],{},[21,1801,29],{"href":28}," — the sibling metric your image work also moves.",[858,1804,1805,1807],{},[21,1806,833],{"href":832}," — the loading policy behind the swap.",[1029,1809,1810],{},"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 pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":221,"searchDepth":273,"depth":273,"links":1812},[1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1830],{"id":172,"depth":273,"text":173},{"id":197,"depth":273,"text":198},{"id":567,"depth":273,"text":568},{"id":846,"depth":273,"text":847},{"id":1010,"depth":273,"text":1011},{"id":1137,"depth":273,"text":1138},{"id":1385,"depth":273,"text":1386},{"id":1602,"depth":273,"text":1603},{"id":1675,"depth":273,"text":1676},{"id":1704,"depth":273,"text":1705,"children":1823},[1824,1825,1826,1827,1828,1829],{"id":1709,"depth":704,"text":1710},{"id":1716,"depth":704,"text":1717},{"id":1727,"depth":704,"text":1728},{"id":1742,"depth":704,"text":1743},{"id":1749,"depth":704,"text":1750},{"id":1756,"depth":704,"text":1757},{"id":1763,"depth":273,"text":1764},[1832,1834,1835],{"name":1833,"item":210},"Home",{"name":24,"item":23},{"name":5,"item":1836},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002F","2026-08-01","Find and fix the four things that move a static page after first paint: unsized media, late web fonts, injected embeds and CSS that lands after render. With measured before\u002Fafter CLS.","md",[1841,1842,1844,1846,1847,1848],{"q":1710,"a":1713},{"q":1717,"a":1843},"Lighthouse measures a single cold load in a fixed viewport and stops recording at the end of the run. Field data captures the whole page lifespan, including shifts caused by lazy-loaded embeds, consent banners, A\u002FB test swaps and content that arrives when a reader scrolls. Collect field CLS with the web-vitals library before assuming a lab score represents reality.",{"q":1728,"a":1845},"No, use both. The width and height attributes give the browser an intrinsic aspect ratio before any CSS parses, which is what protects the very first layout. The CSS aspect-ratio property keeps that box correct when responsive rules override the rendered width.",{"q":1743,"a":1746},{"q":1750,"a":1753},{"q":1757,"a":1760},{},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites",{"title":5,"description":1838},"performance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Findex","guide","Rbg1yBVm4RxGqzsEcuEOiOhZ_5-NpefLY5RhWhHrTQE",[1856,1859,1862,1865,1868,1871,1874,1877,1880,1883,1886,1889,1892,1895,1898,1901,1904,1907,1909,1912,1915,1918,1921,1924,1926,1929,1932,1935,1938,1941,1944,1946,1948,1949,1951,1954,1956,1959,1962,1965,1967,1970,1973,1976,1979,1982,1985,1988,1990,1992,1995,1998,2001,2003,2006,2009,2012,2015,2018,2021,2024,2027,2030,2033,2036,2039,2042,2045,2048,2051,2054,2057,2060,2063,2066,2069,2072,2075,2078,2081,2084,2087,2090],{"path":1857,"title":1858},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fchoosing-between-astro-and-eleventy-for-large-docs","Astro vs Eleventy for Large Docs (1000+ Pages)",{"path":1860,"title":1861},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fcontent-collections-vs-eleventy-data-cascade","Content Collections vs the Eleventy Data Cascade",{"path":1863,"title":1864},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1866,"title":1867},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Fhow-to-benchmark-hugo-vs-astro-build-speeds","How to Benchmark Hugo vs Astro Build Speeds",{"path":1869,"title":1870},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1872,"title":1873},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Fprofiling-hugo-templates-with-template-metrics","Profiling Hugo Templates With Template Metrics",{"path":1875,"title":1876},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Fspeeding-up-hugo-builds-with-render-hooks-and-caching","Speeding Up Hugo Builds with Render Hooks & Caching",{"path":1878,"title":1879},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":1881,"title":1882},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Feleventy-vs-jekyll-for-markdown-heavy-blogs","Eleventy vs Jekyll for Markdown-Heavy Blogs",{"path":1884,"title":1885},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":1887,"title":1888},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Freplacing-jekyll-plugins-when-migrating-to-eleventy","Replacing Jekyll Plugins When Migrating to Eleventy",{"path":1890,"title":1891},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Frunning-jekyll-on-github-pages-without-plugins","Running Jekyll on GitHub Pages Without Plugins",{"path":1893,"title":1894},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":1896,"title":1897},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fkeeping-redirects-working-after-an-ssg-migration","Keeping Redirects Working After an SSG Migration",{"path":1899,"title":1900},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-a-docs-site-from-jekyll-to-hugo","Migrating a Docs Site From Jekyll to Hugo",{"path":1902,"title":1903},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-hugo-to-astro-without-breaking-urls","Migrating From Hugo to Astro Without Breaking URLs",{"path":1905,"title":1906},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fporting-shortcodes-and-includes-between-generators","Porting Shortcodes and Includes Between Generators",{"path":1908,"title":1524},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fhandling-dynamic-routes-in-nextjs-static-export",{"path":1910,"title":1911},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1913,"title":1914},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fmigrating-from-gatsby-to-nextjs-static-export","Migrating from Gatsby to Next.js Static Export",{"path":1916,"title":1917},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fnextjs-static-export-vs-astro-for-marketing-sites","Next.js Static Export vs Astro for Marketing",{"path":1919,"title":1920},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fbest-ssg-for-technical-writers-without-coding-experience","Best SSG for Non-Developer Technical Writers",{"path":1922,"title":1923},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fchoosing-an-ssg-for-api-reference-documentation","Choosing an SSG for API Reference Documentation",{"path":1925,"title":1599},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix",{"path":1927,"title":1928},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fpicking-an-ssg-for-a-multi-language-documentation-site","Picking an SSG for a Multi-Language Docs Site",{"path":1930,"title":1931},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fssg-selection-checklist-for-engineering-teams","SSG Selection Checklist for Engineering Teams",{"path":1933,"title":1934},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":1936,"title":1937},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fpurging-the-cdn-cache-after-a-static-deploy","Purging the CDN Cache After a Static Deploy",{"path":1939,"title":1940},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fsetting-cache-control-headers-on-cloudflare-pages","Cache-Control Headers on Cloudflare Pages",{"path":1942,"title":1943},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fsetting-up-proper-cache-headers-on-netlify","Proper Cache Headers on Netlify for SSGs",{"path":1945,"title":1786},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Feliminating-layout-shift-from-web-fonts",{"path":1947,"title":1002},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-late-loading-embeds",{"path":1850,"title":5},{"path":1950,"title":1263},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js",{"path":1952,"title":1953},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Freserving-space-for-images-and-embeds-to-stop-layout-shift","Reserving Space for Images and Embeds",{"path":1955,"title":833},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites",{"path":1957,"title":1958},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fself-hosting-google-fonts-to-eliminate-layout-shift","Self-Host Google Fonts to Eliminate Layout Shift",{"path":1960,"title":1961},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fsubsetting-variable-fonts-for-faster-first-render","Subsetting Variable Fonts for Faster First Render",{"path":1963,"title":1964},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Fbuilding-an-image-cdn-pipeline-for-static-sites","Building an Image CDN Pipeline for Static Sites",{"path":1966,"title":391},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro",{"path":1968,"title":1969},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Foptimizing-webp-images-in-hugo-without-plugins","Optimizing WebP Images in Hugo Without Plugins",{"path":1971,"title":1972},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Fserving-avif-with-fallbacks-on-static-sites","Serving AVIF With Fallbacks on Static Sites",{"path":1974,"title":1975},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":1977,"title":1978},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":1980,"title":1981},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fhow-to-reduce-bundle-size-in-eleventy-builds","How to Reduce Bundle Size in Eleventy Builds",{"path":1983,"title":1984},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":1986,"title":1987},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fmeasuring-inp-on-static-sites-with-real-user-monitoring","Measuring INP on Static Sites with RUM",{"path":1989,"title":1134},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Feliminating-render-blocking-css-on-static-sites",{"path":1991,"title":29},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites",{"path":1993,"title":1994},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Foptimizing-lcp-on-astro-with-priority-hints","Optimizing LCP on Astro with Priority Hints",{"path":1996,"title":1997},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Freducing-lcp-from-hero-images-on-static-sites","Reducing LCP from Hero Images on Static Sites",{"path":1999,"title":2000},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fauditing-third-party-scripts-with-lighthouse","Auditing Third-Party Scripts With Lighthouse",{"path":2002,"title":1007},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites",{"path":2004,"title":2005},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Flazy-loading-youtube-embeds-on-static-sites","Lazy-Loading YouTube Embeds on Static Sites",{"path":2007,"title":2008},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests","Self-Hosting Analytics to Cut Third-Party Requests",{"path":2010,"title":2011},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":2013,"title":2014},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fdeploying-hugo-to-cloudflare-pages-and-workers","Deploying Hugo to Cloudflare Pages and Workers",{"path":2016,"title":2017},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":2019,"title":2020},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fdocs-as-code-review-workflow-for-writers","Docs-as-Code Review Workflow for Writers",{"path":2022,"title":2023},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":2025,"title":2026},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fscheduling-content-publication-with-cron-triggered-builds","Scheduling Content Publication With Cron-Triggered Builds",{"path":2028,"title":2029},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fwiring-a-headless-cms-to-a-static-build","Wiring a Headless CMS to a Static Build",{"path":2031,"title":2032},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fcaching-node-modules-in-github-actions-for-faster-ssg-builds","Caching node_modules in GitHub Actions",{"path":2034,"title":2035},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fdeploying-to-multiple-environments-from-one-workflow","Deploying to Multiple Environments From One Workflow",{"path":2037,"title":2038},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fhow-to-set-up-github-actions-for-hugo-deployments","GitHub Actions for Hugo Deployments",{"path":2040,"title":2041},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":2043,"title":2044},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fcaching-hugo-builds-in-github-actions","Caching Hugo Builds in GitHub Actions",{"path":2046,"title":2047},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":2049,"title":2050},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":2052,"title":2053},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fmeasuring-build-time-regressions-in-ci","Measuring Build-Time Regressions in CI",{"path":2055,"title":2056},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fsharing-build-cache-across-ci-runners","Sharing Build Cache Across CI Runners",{"path":2058,"title":2059},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":2061,"title":2062},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":2064,"title":2065},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":2067,"title":2068},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fsetting-up-deploy-previews-on-netlify-for-every-pull-request","Netlify Deploy Previews for Every Pull Request",{"path":2070,"title":2071},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fvercel-isr-vs-static-generation-for-ssgs","Vercel ISR vs Static Generation for SSGs",{"path":2073,"title":2074},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fautomating-preview-deploy-pipelines-with-github-actions","Automating Preview Deploy Pipelines with GitHub Actions",{"path":2076,"title":2077},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":2079,"title":2080},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":2082,"title":2083},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":2085,"title":2086},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":2088,"title":2089},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frolling-back-a-bad-static-deploy-in-under-a-minute","Rolling Back a Bad Static Deploy in Under a Minute",{"path":2091,"title":2092},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url","Running Smoke Tests Against a Preview URL",1785611670808]