[{"data":1,"prerenderedAt":1630},["ShallowReactive",2],{"page:\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js":3,"all-docs-nav":1386},{"id":4,"title":5,"body":6,"breadcrumb":1363,"dateModified":1370,"datePublished":1370,"description":1371,"extension":1372,"faq":1373,"meta":1380,"navigation":286,"path":1381,"seo":1382,"slug":1383,"stem":1384,"type":817,"__hash__":1385},"content\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js\u002Findex.md","Measuring CLS in the Field With web-vitals.js",{"type":7,"value":8,"toc":1341},"minimark",[9,13,26,40,45,58,62,217,221,226,495,502,513,517,726,729,733,736,778,781,805,809,812,827,830,834,841,947,954,958,961,1051,1165,1187,1191,1242,1246,1258,1262,1266,1269,1273,1276,1280,1286,1290,1293,1297,1300,1304,1337],[10,11,5],"h1",{"id":12},"measuring-cls-in-the-field-with-web-vitalsjs",[14,15,16,17,21,22,25],"p",{},"Lab tools tell you whether a page ",[18,19,20],"em",{},"can"," be stable. Only field data tells you whether it ",[18,23,24],{},"is",". A static site's real CLS is decided by things a scripted run never does: scrolling into a lazily loaded embed, accepting a consent dialog, loading on a cheap Android device where the font race resolves differently every time.",[14,27,28,29,33,34,39],{},"This guide sets up real-user CLS collection with the ",[30,31,32],"code",{},"web-vitals"," library's attribution build, a small edge endpoint to receive it, and the query that turns a 75th-percentile score into the name of an element you can fix. It closes out ",[35,36,38],"a",{"href":37},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002F","Cumulative Layout Shift Fixes for Static Sites",".",[41,42,44],"h2",{"id":43},"prerequisites","Prerequisites",[46,47,48,52,55],"ul",{},[49,50,51],"li",{},"A static site you can add one small script to, plus somewhere to receive beacons — a Worker, a serverless function, or an existing analytics pipeline.",[49,53,54],{},"Enough traffic for percentiles to settle: a few hundred views per template per week.",[49,56,57],{},"The lab fixes already applied, so what you collect is the residue rather than the obvious.",[41,59,61],{"id":60},"what-to-collect","What to Collect",[63,64,65,213],"figure",{},[66,67,74,75,74,79,74,83,74,90,74,198],"svg",{"viewBox":68,"role":69,"ariaLabelledBy":70,"xmlns":73},"0 0 760 300","img",[71,72],"fld-flow-title","fld-flow-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[76,77,78],"title",{"id":71},"Field CLS collection pipeline",[80,81,82],"desc",{"id":72},"A left-to-right flow. The browser runs the web-vitals attribution build, which reports a CLS value plus the largest shifted element on page hide. A sendBeacon request carries it to an edge function, which writes to an analytics store, which feeds a per-template 75th percentile dashboard.",[84,85],"rect",{"x":86,"y":86,"width":87,"height":88,"fill":89},"0","760","300","#ffffff",[91,92,94,95,94,103,94,113,94,119,94,124,94,128,94,132,94,136,94,139,94,142,94,148,94,152,94,155,94,158,94,165,94,169,94,172,94,175,94,190,94,194,74],"g",{"style":93},"font-family:system-ui, sans-serif;font-size:13px","\n    ",[96,97,102],"text",{"x":98,"y":99,"fill":100,"style":101},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","From a reader's device to a named element",[84,104],{"x":105,"y":106,"width":107,"height":108,"rx":109,"fill":110,"opacity":111,"stroke":110,"style":112},"30","90","150","86","12","#1982c4","0.14","stroke-width:2px",[96,114,118],{"x":115,"y":116,"fill":100,"style":117},"105","122","font-size:13px;font-weight:700;text-anchor:middle","Browser",[96,120,32],{"x":115,"y":121,"fill":122,"style":123},"142","#556071","font-size:12px;text-anchor:middle",[96,125,127],{"x":115,"y":126,"fill":122,"style":123},"160","attribution build",[84,129],{"x":130,"y":106,"width":107,"height":108,"rx":109,"fill":131,"opacity":111,"stroke":131,"style":112},"220","#6a4c93",[96,133,135],{"x":134,"y":116,"fill":100,"style":117},"295","sendBeacon",[96,137,138],{"x":134,"y":121,"fill":122,"style":123},"on visibilitychange",[96,140,141],{"x":134,"y":126,"fill":122,"style":123},"~400 byte payload",[84,143],{"x":144,"y":106,"width":107,"height":108,"rx":109,"fill":145,"opacity":146,"stroke":147,"style":112},"410","#ffca3a","0.28","#a97b00",[96,149,151],{"x":150,"y":116,"fill":100,"style":117},"485","Edge function",[96,153,154],{"x":150,"y":121,"fill":122,"style":123},"validate + enrich",[96,156,157],{"x":150,"y":126,"fill":122,"style":123},"write to store",[84,159],{"x":160,"y":106,"width":161,"height":108,"rx":109,"fill":162,"opacity":163,"stroke":164,"style":112},"600","140","#8ac926","0.18","#5a8a16",[96,166,168],{"x":167,"y":116,"fill":100,"style":117},"670","p75 by template",[96,170,171],{"x":167,"y":121,"fill":122,"style":123},"+ worst element",[96,173,174],{"x":167,"y":126,"fill":122,"style":123},"per route",[91,176,178,179,178,184,178,187,94],{"stroke":122,"fill":177,"style":112},"none","\n      ",[180,181],"path",{"d":182,"style":183},"M180 133 L218 133","marker-end:url(#fld-arrow)",[180,185],{"d":186,"style":183},"M370 133 L408 133",[180,188],{"d":189,"style":183},"M560 133 L598 133",[96,191,193],{"x":105,"y":130,"fill":122,"style":192},"font-size:12px","Payload carries: value, rating, navigation type, route, element selector, largest shift rectangles",[96,195,197],{"x":105,"y":196,"fill":122,"style":192},"244","Everything is anonymous — no identifiers, no cookies, one beacon per page view",[199,200,94,201,74],"defs",{},[202,203,178,210,94],"marker",{"id":204,"viewBox":205,"refX":206,"refY":207,"markerWidth":208,"markerHeight":208,"orient":209},"fld-arrow","0 0 10 10","8","5","7","auto-start-reverse",[180,211],{"d":212,"fill":122},"M0 0 L10 5 L0 10 z",[214,215,216],"figcaption",{},"The attribution build is what makes the pipeline actionable: without the element selector you learn that a template is unstable, not which node to reserve space for.",[41,218,220],{"id":219},"the-recipe","The Recipe",[222,223,225],"h3",{"id":224},"_1-add-the-attribution-build","1. Add the attribution build",[227,228,233],"pre",{"className":229,"code":230,"language":231,"meta":232,"style":232},"language-html shiki shiki-themes github-light github-dark","\u003Cscript type=\"module\">\n  import { onCLS, onLCP, onINP } from 'https:\u002F\u002Funpkg.com\u002Fweb-vitals@4\u002Fdist\u002Fweb-vitals.attribution.js';\n\n  const send = (metric) => {\n    const body = JSON.stringify({\n      name: metric.name,\n      value: Math.round(metric.value * 1000) \u002F 1000,\n      rating: metric.rating,\n      route: document.querySelector('meta[name=\"page-template\"]')?.content || location.pathname,\n      nav: metric.navigationType,\n      target: metric.attribution?.largestShiftTarget || metric.attribution?.element || null,\n    });\n    navigator.sendBeacon('\u002Fapi\u002Fvitals', body);\n  };\n\n  onCLS(send);\n  onLCP(send);\n  onINP(send);\n\u003C\u002Fscript>\n","html","",[30,234,235,262,281,288,316,339,345,373,379,403,409,427,433,449,455,460,469,477,485],{"__ignoreMap":232},[236,237,240,244,248,252,255,259],"span",{"class":238,"line":239},"line",1,[236,241,243],{"class":242},"sVt8B","\u003C",[236,245,247],{"class":246},"s9eBZ","script",[236,249,251],{"class":250},"sScJk"," type",[236,253,254],{"class":242},"=",[236,256,258],{"class":257},"sZZnC","\"module\"",[236,260,261],{"class":242},">\n",[236,263,265,269,272,275,278],{"class":238,"line":264},2,[236,266,268],{"class":267},"szBVR","  import",[236,270,271],{"class":242}," { onCLS, onLCP, onINP } ",[236,273,274],{"class":267},"from",[236,276,277],{"class":257}," 'https:\u002F\u002Funpkg.com\u002Fweb-vitals@4\u002Fdist\u002Fweb-vitals.attribution.js'",[236,279,280],{"class":242},";\n",[236,282,284],{"class":238,"line":283},3,[236,285,287],{"emptyLinePlaceholder":286},true,"\n",[236,289,291,294,297,300,303,307,310,313],{"class":238,"line":290},4,[236,292,293],{"class":267},"  const",[236,295,296],{"class":250}," send",[236,298,299],{"class":267}," =",[236,301,302],{"class":242}," (",[236,304,306],{"class":305},"s4XuR","metric",[236,308,309],{"class":242},") ",[236,311,312],{"class":267},"=>",[236,314,315],{"class":242}," {\n",[236,317,319,322,326,328,331,333,336],{"class":238,"line":318},5,[236,320,321],{"class":267},"    const",[236,323,325],{"class":324},"sj4cs"," body",[236,327,299],{"class":267},[236,329,330],{"class":324}," JSON",[236,332,39],{"class":242},[236,334,335],{"class":250},"stringify",[236,337,338],{"class":242},"({\n",[236,340,342],{"class":238,"line":341},6,[236,343,344],{"class":242},"      name: metric.name,\n",[236,346,348,351,354,357,360,363,365,368,370],{"class":238,"line":347},7,[236,349,350],{"class":242},"      value: Math.",[236,352,353],{"class":250},"round",[236,355,356],{"class":242},"(metric.value ",[236,358,359],{"class":267},"*",[236,361,362],{"class":324}," 1000",[236,364,309],{"class":242},[236,366,367],{"class":267},"\u002F",[236,369,362],{"class":324},[236,371,372],{"class":242},",\n",[236,374,376],{"class":238,"line":375},8,[236,377,378],{"class":242},"      rating: metric.rating,\n",[236,380,382,385,388,391,394,397,400],{"class":238,"line":381},9,[236,383,384],{"class":242},"      route: document.",[236,386,387],{"class":250},"querySelector",[236,389,390],{"class":242},"(",[236,392,393],{"class":257},"'meta[name=\"page-template\"]'",[236,395,396],{"class":242},")?.content ",[236,398,399],{"class":267},"||",[236,401,402],{"class":242}," location.pathname,\n",[236,404,406],{"class":238,"line":405},10,[236,407,408],{"class":242},"      nav: metric.navigationType,\n",[236,410,412,415,417,420,422,425],{"class":238,"line":411},11,[236,413,414],{"class":242},"      target: metric.attribution?.largestShiftTarget ",[236,416,399],{"class":267},[236,418,419],{"class":242}," metric.attribution?.element ",[236,421,399],{"class":267},[236,423,424],{"class":324}," null",[236,426,372],{"class":242},[236,428,430],{"class":238,"line":429},12,[236,431,432],{"class":242},"    });\n",[236,434,436,439,441,443,446],{"class":238,"line":435},13,[236,437,438],{"class":242},"    navigator.",[236,440,135],{"class":250},[236,442,390],{"class":242},[236,444,445],{"class":257},"'\u002Fapi\u002Fvitals'",[236,447,448],{"class":242},", body);\n",[236,450,452],{"class":238,"line":451},14,[236,453,454],{"class":242},"  };\n",[236,456,458],{"class":238,"line":457},15,[236,459,287],{"emptyLinePlaceholder":286},[236,461,463,466],{"class":238,"line":462},16,[236,464,465],{"class":250},"  onCLS",[236,467,468],{"class":242},"(send);\n",[236,470,472,475],{"class":238,"line":471},17,[236,473,474],{"class":250},"  onLCP",[236,476,468],{"class":242},[236,478,480,483],{"class":238,"line":479},18,[236,481,482],{"class":250},"  onINP",[236,484,468],{"class":242},[236,486,488,491,493],{"class":238,"line":487},19,[236,489,490],{"class":242},"\u003C\u002F",[236,492,247],{"class":246},[236,494,261],{"class":242},[14,496,497,498,39],{},"Self-host the file rather than loading it from a CDN in production — it removes a third-party connection from the critical path, exactly the argument made in ",[35,499,501],{"href":500},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests\u002F","Self-Hosting Analytics to Cut Third-Party Requests",[14,503,504,505,508,509,512],{},"The ",[30,506,507],{},"route"," field matters more than it looks. Reporting raw pathnames on a 500-page site gives you 500 sparse buckets; reporting the ",[18,510,511],{},"template"," gives you five dense ones, and templates are what you actually fix. Stamp the template name into a meta tag at build time.",[222,514,516],{"id":515},"_2-receive-the-beacon-at-the-edge","2. Receive the beacon at the edge",[227,518,522],{"className":519,"code":520,"language":521,"meta":232,"style":232},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F functions\u002Fapi\u002Fvitals.js — Cloudflare Pages Function \u002F Worker\nexport async function onRequestPost({ request, env }) {\n  const data = await request.json().catch(() => null);\n  if (!data || typeof data.value !== 'number') {\n    return new Response('bad request', { status: 400 });\n  }\n  await env.VITALS.writeDataPoint({\n    blobs: [data.name, data.route, data.rating, data.target ?? ''],\n    doubles: [data.value],\n    indexes: [data.route],\n  });\n  return new Response(null, { status: 204 });\n}\n","javascript",[30,523,524,530,559,593,623,648,653,671,685,690,695,700,721],{"__ignoreMap":232},[236,525,526],{"class":238,"line":239},[236,527,529],{"class":528},"sJ8bj","\u002F\u002F functions\u002Fapi\u002Fvitals.js — Cloudflare Pages Function \u002F Worker\n",[236,531,532,535,538,541,544,547,550,553,556],{"class":238,"line":264},[236,533,534],{"class":267},"export",[236,536,537],{"class":267}," async",[236,539,540],{"class":267}," function",[236,542,543],{"class":250}," onRequestPost",[236,545,546],{"class":242},"({ ",[236,548,549],{"class":305},"request",[236,551,552],{"class":242},", ",[236,554,555],{"class":305},"env",[236,557,558],{"class":242}," }) {\n",[236,560,561,563,566,568,571,574,577,580,583,586,588,590],{"class":238,"line":283},[236,562,293],{"class":267},[236,564,565],{"class":324}," data",[236,567,299],{"class":267},[236,569,570],{"class":267}," await",[236,572,573],{"class":242}," request.",[236,575,576],{"class":250},"json",[236,578,579],{"class":242},"().",[236,581,582],{"class":250},"catch",[236,584,585],{"class":242},"(() ",[236,587,312],{"class":267},[236,589,424],{"class":324},[236,591,592],{"class":242},");\n",[236,594,595,598,600,603,606,608,611,614,617,620],{"class":238,"line":290},[236,596,597],{"class":267},"  if",[236,599,302],{"class":242},[236,601,602],{"class":267},"!",[236,604,605],{"class":242},"data ",[236,607,399],{"class":267},[236,609,610],{"class":267}," typeof",[236,612,613],{"class":242}," data.value ",[236,615,616],{"class":267},"!==",[236,618,619],{"class":257}," 'number'",[236,621,622],{"class":242},") {\n",[236,624,625,628,631,634,636,639,642,645],{"class":238,"line":318},[236,626,627],{"class":267},"    return",[236,629,630],{"class":267}," new",[236,632,633],{"class":250}," Response",[236,635,390],{"class":242},[236,637,638],{"class":257},"'bad request'",[236,640,641],{"class":242},", { status: ",[236,643,644],{"class":324},"400",[236,646,647],{"class":242}," });\n",[236,649,650],{"class":238,"line":341},[236,651,652],{"class":242},"  }\n",[236,654,655,658,661,664,666,669],{"class":238,"line":347},[236,656,657],{"class":267},"  await",[236,659,660],{"class":242}," env.",[236,662,663],{"class":324},"VITALS",[236,665,39],{"class":242},[236,667,668],{"class":250},"writeDataPoint",[236,670,338],{"class":242},[236,672,673,676,679,682],{"class":238,"line":375},[236,674,675],{"class":242},"    blobs: [data.name, data.route, data.rating, data.target ",[236,677,678],{"class":267},"??",[236,680,681],{"class":257}," ''",[236,683,684],{"class":242},"],\n",[236,686,687],{"class":238,"line":381},[236,688,689],{"class":242},"    doubles: [data.value],\n",[236,691,692],{"class":238,"line":405},[236,693,694],{"class":242},"    indexes: [data.route],\n",[236,696,697],{"class":238,"line":411},[236,698,699],{"class":242},"  });\n",[236,701,702,705,707,709,711,714,716,719],{"class":238,"line":429},[236,703,704],{"class":267},"  return",[236,706,630],{"class":267},[236,708,633],{"class":250},[236,710,390],{"class":242},[236,712,713],{"class":324},"null",[236,715,641],{"class":242},[236,717,718],{"class":324},"204",[236,720,647],{"class":242},[236,722,723],{"class":238,"line":435},[236,724,725],{"class":242},"}\n",[14,727,728],{},"Return 204 with no body: the browser has already unloaded the page and nothing reads the response. Reject payloads that fail validation rather than storing them — a public endpoint will receive junk.",[222,730,732],{"id":731},"_3-query-the-75th-percentile-per-template","3. Query the 75th percentile per template",[14,734,735],{},"Core Web Vitals is assessed at p75, so that is the number to track:",[227,737,741],{"className":738,"code":739,"language":740,"meta":232,"style":232},"language-sql shiki shiki-themes github-light github-dark","SELECT blob2                          AS template,\n       quantile(0.75)(double1)        AS cls_p75,\n       count()                        AS samples\nFROM vitals\nWHERE blob1 = 'CLS' AND timestamp > now() - INTERVAL 7 DAY\nGROUP BY template\nORDER BY cls_p75 DESC;\n","sql",[30,742,743,748,753,758,763,768,773],{"__ignoreMap":232},[236,744,745],{"class":238,"line":239},[236,746,747],{},"SELECT blob2                          AS template,\n",[236,749,750],{"class":238,"line":264},[236,751,752],{},"       quantile(0.75)(double1)        AS cls_p75,\n",[236,754,755],{"class":238,"line":283},[236,756,757],{},"       count()                        AS samples\n",[236,759,760],{"class":238,"line":290},[236,761,762],{},"FROM vitals\n",[236,764,765],{"class":238,"line":318},[236,766,767],{},"WHERE blob1 = 'CLS' AND timestamp > now() - INTERVAL 7 DAY\n",[236,769,770],{"class":238,"line":341},[236,771,772],{},"GROUP BY template\n",[236,774,775],{"class":238,"line":347},[236,776,777],{},"ORDER BY cls_p75 DESC;\n",[14,779,780],{},"Then, for the worst template, group by the attribution target to get the element:",[227,782,784],{"className":738,"code":783,"language":740,"meta":232,"style":232},"SELECT blob4 AS element, count() AS n, quantile(0.75)(double1) AS cls_p75\nFROM vitals\nWHERE blob1 = 'CLS' AND blob2 = 'article' AND timestamp > now() - INTERVAL 7 DAY\nGROUP BY element ORDER BY n DESC LIMIT 10;\n",[30,785,786,791,795,800],{"__ignoreMap":232},[236,787,788],{"class":238,"line":239},[236,789,790],{},"SELECT blob4 AS element, count() AS n, quantile(0.75)(double1) AS cls_p75\n",[236,792,793],{"class":238,"line":264},[236,794,762],{},[236,796,797],{"class":238,"line":283},[236,798,799],{},"WHERE blob1 = 'CLS' AND blob2 = 'article' AND timestamp > now() - INTERVAL 7 DAY\n",[236,801,802],{"class":238,"line":290},[236,803,804],{},"GROUP BY element ORDER BY n DESC LIMIT 10;\n",[222,806,808],{"id":807},"_4-alert-on-regression-not-on-absolute-value","4. Alert on regression, not on absolute value",[14,810,811],{},"A static site's field CLS moves when a deploy changes markup. Compare the current 7-day p75 against the previous 7 days for the same template and alert on a relative jump, which catches regressions long before the absolute score crosses 0.1.",[14,813,814,815,818,819,822,823,39],{},"Tie the alert to deploys rather than to the clock. Record the build identifier in the beacon — a commit SHA stamped into a meta tag at build time costs nothing — and the query that says \"template ",[30,816,817],{},"article"," got 40% worse\" can also say \"starting with build ",[30,820,821],{},"a91f3c2","\". That turns a weekly dashboard review into a same-day fix, and it is the field-data equivalent of the deploy-tagged budgets described in ",[35,824,826],{"href":825},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fmeasuring-build-time-regressions-in-ci\u002F","Measuring Build-Time Regressions in CI",[14,828,829],{},"Give any new alert two weeks of shadow running before it can page anyone. Seasonal traffic shifts the device mix — a weekend skews mobile, a conference skews desktop — and a threshold tuned on a single quiet week will fire on the first busy Monday for reasons that have nothing to do with your markup.",[41,831,833],{"id":832},"reading-a-burst-correctly","Reading a Burst Correctly",[14,835,836,837,840],{},"The reported CLS is not the sum of every shift on the page — it is the largest ",[18,838,839],{},"burst",", where a burst is a run of shifts with no more than one second between consecutive entries and no more than five seconds from first to last. The attribution build reports the largest shift inside that winning burst, which is why the element it names is the one to fix and the others usually are not.",[63,842,843,944],{},[66,844,74,849,74,852,74,855,74,858],{"viewBox":845,"role":69,"ariaLabelledBy":846,"xmlns":73},"0 0 760 280",[847,848],"fld-burst-title","fld-burst-desc",[76,850,851],{"id":847},"How bursts decide the reported CLS",[80,853,854],{"id":848},"A timeline over eight seconds with six layout shifts. Three shifts between 0.6 and 1.9 seconds form one burst totalling 0.05. Two shifts at 4.2 and 4.6 seconds form a second burst totalling 0.13, which is the reported score. A lone shift at 7.4 seconds scores 0.02 and is ignored.",[84,856],{"x":86,"y":86,"width":87,"height":857,"fill":89},"280",[91,859,94,861,94,865,94,871,94,877,94,882,94,885,94,888,94,893,94,899,94,903,94,906,94,909,94,912,94,916,94,921,94,925,94,928,94,933,94,936,94,940,74],{"style":860},"font-family:system-ui, sans-serif;font-size:12px",[96,862,864],{"x":98,"y":863,"fill":100,"style":101},"26","The worst burst wins — the rest is not reported",[238,866],{"x1":867,"y1":868,"x2":869,"y2":868,"stroke":122,"style":870},"70","196","710","stroke-width:1.5px",[84,872],{"x":106,"y":867,"width":873,"height":874,"rx":206,"fill":875,"opacity":876,"stroke":122,"style":870},"180","112","#d9e2ef","0.45",[96,878,881],{"x":873,"y":879,"fill":100,"style":880},"92","font-size:12px;font-weight:700;text-anchor:middle","burst 1 · fonts + images",[96,883,884],{"x":873,"y":874,"fill":122,"style":123},"0.02 + 0.02 + 0.01",[96,886,887],{"x":873,"y":161,"fill":100,"style":117},"total 0.05",[96,889,892],{"x":873,"y":890,"fill":122,"style":891},"166","font-size:11px;text-anchor:middle","0.6 s – 1.9 s",[84,894],{"x":895,"y":867,"width":873,"height":874,"rx":206,"fill":896,"opacity":897,"stroke":898,"style":870},"330","#ff595e","0.15","#d83b41",[96,900,902],{"x":901,"y":879,"fill":100,"style":880},"420","burst 2 · comment thread",[96,904,905],{"x":901,"y":874,"fill":122,"style":123},"0.11 + 0.02",[96,907,908],{"x":901,"y":161,"fill":100,"style":117},"total 0.13 · reported",[96,910,911],{"x":901,"y":890,"fill":122,"style":891},"4.2 s – 4.6 s",[84,913],{"x":914,"y":915,"width":161,"height":108,"rx":206,"fill":875,"opacity":876,"stroke":122,"style":870},"570","96",[96,917,920],{"x":918,"y":919,"fill":100,"style":880},"640","120","lone shift",[96,922,924],{"x":918,"y":923,"fill":100,"style":117},"144","0.02",[96,926,927],{"x":918,"y":890,"fill":122,"style":891},"7.4 s",[96,929,932],{"x":106,"y":930,"fill":122,"style":931},"222","font-size:11px","0 s",[96,934,935],{"x":644,"y":930,"fill":122,"style":931},"4 s",[96,937,939],{"x":938,"y":930,"fill":122,"style":931},"680","8 s",[96,941,943],{"x":867,"y":942,"fill":122,"style":931},"252","Attribution names the 0.11 entry inside burst 2 — fixing the 0.05 burst would not change the score at all",[214,945,946],{},"This is why field data so often points at one element: the metric deliberately reports the worst window, not the accumulated total.",[14,948,949,950,953],{},"The practical consequence is that a fix can look like it did nothing. If two elements shift inside the same burst and you fix the smaller one, the burst — and therefore the score — barely moves. Always fix the element attribution names, re-measure for a week, and expect the ",[18,951,952],{},"next"," worst element to appear in its place.",[41,955,957],{"id":956},"measured-impact","Measured Impact",[14,959,960],{},"Two weeks of collection on a 60-page documentation site, roughly 40,000 page views:",[962,963,964,983],"table",{},[965,966,967],"thead",{},[968,969,970,974,977,980],"tr",{},[971,972,973],"th",{},"Template",[971,975,976],{},"Lab CLS",[971,978,979],{},"Field p75",[971,981,982],{},"Worst element (attribution)",[984,985,986,1003,1019,1035],"tbody",{},[968,987,988,992,995,997],{},[989,990,991],"td",{},"Article",[989,993,994],{},"0.01",[989,996,111],{},[989,998,999,1002],{},[30,1000,1001],{},".comments"," — loaded on scroll, unreserved",[968,1004,1005,1008,1011,1013],{},[989,1006,1007],{},"Index",[989,1009,1010],{},"0.00",[989,1012,924],{},[989,1014,1015,1018],{},[30,1016,1017],{},"img.card-thumb"," — one legacy card without dimensions",[968,1020,1021,1024,1026,1029],{},[989,1022,1023],{},"Homepage",[989,1025,1010],{},[989,1027,1028],{},"0.11",[989,1030,1031,1034],{},[30,1032,1033],{},".consent"," — banner prepended in flow",[968,1036,1037,1040,1042,1045],{},[989,1038,1039],{},"Search",[989,1041,924],{},[989,1043,1044],{},"0.03",[989,1046,1047,1050],{},[30,1048,1049],{},".results"," — reserved correctly",[63,1052,1053,1162],{},[66,1054,74,1059,74,1062,74,1065,74,1068],{"viewBox":1055,"role":69,"ariaLabelledBy":1056,"xmlns":73},"0 0 760 290",[1057,1058],"fld-gap-title","fld-gap-desc",[76,1060,1061],{"id":1057},"Lab score versus field 75th percentile by template",[80,1063,1064],{"id":1058},"A paired bar chart. For each of four templates the lab score is near zero while the field 75th percentile is higher: article 0.01 versus 0.14, index 0.00 versus 0.02, homepage 0.00 versus 0.11 and search 0.02 versus 0.03. The gap is largest where content loads on scroll.",[84,1066],{"x":86,"y":86,"width":87,"height":1067,"fill":89},"290",[91,1069,94,1070,94,1073,94,1078,94,1080,94,1087,94,1093,94,1098,94,1102,94,1105,94,1111,94,1114,94,1118,94,1121,94,1124,94,1127,94,1131,94,1134,94,1138,94,1142,94,1146,94,1151,94,1156,94,1158,74],{"style":860},[96,1071,1072],{"x":98,"y":863,"fill":100,"style":101},"The lab never sees what readers do",[238,1074],{"x1":1075,"y1":1076,"x2":1075,"y2":1077,"stroke":122,"style":870},"110","52","206",[238,1079],{"x1":1075,"y1":1077,"x2":869,"y2":1077,"stroke":122,"style":870},[84,1081],{"x":161,"y":1082,"width":1083,"height":1084,"rx":1085,"fill":110,"opacity":1086,"stroke":110,"style":870},"200","46","6","2","0.5",[84,1088],{"x":1089,"y":1090,"width":1083,"height":161,"rx":1091,"fill":896,"opacity":1092,"stroke":898,"style":870},"192","66","4","0.16",[96,1094,111],{"x":1095,"y":1096,"fill":898,"style":1097},"215","60","font-size:11px;font-weight:700;text-anchor:middle",[96,1099,817],{"x":1100,"y":1101,"fill":122,"style":891},"189","226",[84,1103],{"x":1067,"y":718,"width":1083,"height":1085,"rx":1104,"fill":110,"opacity":1086,"stroke":110,"style":870},"1",[84,1106],{"x":1107,"y":1108,"width":1083,"height":1109,"rx":1091,"fill":162,"opacity":1110,"stroke":164,"style":870},"342","186","20","0.24",[96,1112,924],{"x":1113,"y":873,"fill":164,"style":1097},"365",[96,1115,1117],{"x":1116,"y":1101,"fill":122,"style":891},"339","index",[84,1119],{"x":1120,"y":718,"width":1083,"height":1085,"rx":1104,"fill":110,"opacity":1086,"stroke":110,"style":870},"440",[84,1122],{"x":1123,"y":915,"width":1083,"height":1075,"rx":1091,"fill":896,"opacity":111,"stroke":898,"style":870},"492",[96,1125,1028],{"x":1126,"y":106,"fill":898,"style":1097},"515",[96,1128,1130],{"x":1129,"y":1101,"fill":122,"style":891},"489","homepage",[84,1132],{"x":1133,"y":1108,"width":1083,"height":1109,"rx":1091,"fill":110,"opacity":1086,"stroke":110,"style":870},"590",[84,1135],{"x":1136,"y":1137,"width":1083,"height":105,"rx":1091,"fill":162,"opacity":1110,"stroke":164,"style":870},"642","176",[96,1139,1044],{"x":1140,"y":1141,"fill":164,"style":1097},"665","170",[96,1143,1145],{"x":1144,"y":1101,"fill":122,"style":891},"639","search",[84,1147],{"x":161,"y":1148,"width":1149,"height":1150,"rx":1085,"fill":110,"opacity":1086,"stroke":110,"style":870},"250","14","10",[96,1152,1155],{"x":1153,"y":1154,"fill":122,"style":931},"162","259","lab (Lighthouse)",[84,1157],{"x":1067,"y":1148,"width":1149,"height":1150,"rx":1085,"fill":896,"opacity":1092,"stroke":898,"style":870},[96,1159,1161],{"x":1160,"y":1154,"fill":122,"style":931},"312","field p75 (40k page views, 14 days)",[214,1163,1164],{},"Both templates with a large gap had something that only appears when a real person scrolls or clicks — the comment thread and the consent banner.",[14,1166,1167,1168,1171,1172,1174,1175,1177,1178,1182,1183,1186],{},"Fixing the two elements the attribution named — a ",[30,1169,1170],{},"min-height"," on ",[30,1173,1001],{}," and a fixed overlay for ",[30,1176,1033],{}," — moved the article template from ",[1179,1180,1181],"strong",{},"0.14 to 0.02"," and the homepage from ",[1179,1184,1185],{},"0.11 to 0.01"," at p75 over the following fortnight. Neither change would have been prioritised from the lab numbers, which were already green.",[41,1188,1190],{"id":1189},"pitfalls-rollback","Pitfalls & Rollback",[46,1192,1193,1199,1215,1224,1230,1236],{},[49,1194,1195,1198],{},[1179,1196,1197],{},"Reporting raw pathnames."," Percentiles over hundreds of sparse URLs are noise. Group by template.",[49,1200,1201,1207,1208,1210,1211,1214],{},[1179,1202,1203,1204,39],{},"Firing on ",[30,1205,1206],{},"unload"," That event is unreliable on mobile Safari; ",[30,1209,32],{}," already reports on ",[30,1212,1213],{},"visibilitychange",", so keep its default.",[49,1216,1217,1223],{},[1179,1218,1219,1220,39],{},"Ignoring ",[30,1221,1222],{},"navigationType"," Back\u002Fforward-cache restores behave differently from cold loads; keep the field so you can separate them rather than chasing a phantom regression.",[49,1225,1226,1229],{},[1179,1227,1228],{},"Storing personal data."," Nothing here needs identifiers. Keep the payload anonymous so the endpoint stays outside consent-management scope.",[49,1231,1232,1235],{},[1179,1233,1234],{},"Chasing the mean."," CLS is assessed at p75; an average hides exactly the tail that fails the assessment.",[49,1237,1238,1241],{},[1179,1239,1240],{},"Rollback:"," remove the script tag. Collection stops immediately and nothing else on the page depends on it.",[41,1243,1245],{"id":1244},"conclusion","Conclusion",[14,1247,1248,1249,1253,1254,39],{},"Field measurement is what closes the loop on layout stability: it finds the shifts your build never reproduces and, with the attribution build, names the element responsible. Add the script, receive beacons at the edge, group by template, and alert on week-over-week regression. Then feed what you find back into the reservation patterns in ",[35,1250,1252],{"href":1251},"\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"," — the same pipeline also gives you real INP numbers, as covered in ",[35,1255,1257],{"href":1256},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fmeasuring-inp-on-static-sites-with-real-user-monitoring\u002F","Measuring INP on Static Sites With Real-User Monitoring",[41,1259,1261],{"id":1260},"faq","FAQ",[222,1263,1265],{"id":1264},"why-is-field-cls-worse-than-my-lighthouse-score","Why is field CLS worse than my Lighthouse score?",[14,1267,1268],{},"Lighthouse loads the page once in a fixed viewport and stops recording after a few seconds. Real visits scroll, trigger lazily loaded embeds, run consent flows and last minutes. Anything that moves during that longer window counts in the field score and never appears in the lab run.",[222,1270,1272],{"id":1271},"how-much-traffic-do-i-need-for-the-numbers-to-mean-anything","How much traffic do I need for the numbers to mean anything?",[14,1274,1275],{},"A few hundred page views per template per week is enough to see a 75th percentile stabilise. Below that, watch the raw entries and their attribution instead of the percentile, because a handful of slow sessions will dominate any aggregate.",[222,1277,1279],{"id":1278},"does-the-web-vitals-script-hurt-performance","Does the web-vitals script hurt performance?",[14,1281,1282,1283,1285],{},"The attribution build is about 3 kilobytes gzipped and runs entirely in idle time, reporting once per page via ",[30,1284,135],{},". It is one of the few third-party-shaped additions that does not measurably move the metrics it measures.",[222,1287,1289],{"id":1288},"what-is-the-difference-between-the-standard-and-attribution-builds","What is the difference between the standard and attribution builds?",[14,1291,1292],{},"The standard build reports a number. The attribution build also reports which element shifted the most, its previous and current rectangles, and the load state at the time, which is the difference between knowing a page is bad and knowing what to fix.",[222,1294,1296],{"id":1295},"should-i-sample-or-record-every-page-view","Should I sample or record every page view?",[14,1298,1299],{},"Record everything until the volume is inconvenient. CLS beacons are tiny, and full collection means a rare shift on a rare device is still visible. Sample only when the ingestion cost becomes real, and sample by session rather than by event so a session's score is never partial.",[41,1301,1303],{"id":1302},"related","Related",[46,1305,1306,1315,1320,1325,1330],{},[49,1307,1308,1311,1312,1314],{},[1179,1309,1310],{},"Parent:"," ",[35,1313,38],{"href":37}," — the fixes this data prioritises.",[49,1316,1317,1319],{},[35,1318,1252],{"href":1251}," — what the attribution usually names.",[49,1321,1322,1324],{},[35,1323,1257],{"href":1256}," — the same pipeline for interaction latency.",[49,1326,1327,1329],{},[35,1328,501],{"href":500}," — keeping the collector off a third-party connection.",[49,1331,1332,1336],{},[35,1333,1335],{"href":1334},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002F","Performance Optimization & Core Web Vitals for SSGs"," — how the three vitals fit together.",[1338,1339,1340],"style",{},"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 .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}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":232,"searchDepth":264,"depth":264,"links":1342},[1343,1344,1345,1351,1352,1353,1354,1355,1362],{"id":43,"depth":264,"text":44},{"id":60,"depth":264,"text":61},{"id":219,"depth":264,"text":220,"children":1346},[1347,1348,1349,1350],{"id":224,"depth":283,"text":225},{"id":515,"depth":283,"text":516},{"id":731,"depth":283,"text":732},{"id":807,"depth":283,"text":808},{"id":832,"depth":264,"text":833},{"id":956,"depth":264,"text":957},{"id":1189,"depth":264,"text":1190},{"id":1244,"depth":264,"text":1245},{"id":1260,"depth":264,"text":1261,"children":1356},[1357,1358,1359,1360,1361],{"id":1264,"depth":283,"text":1265},{"id":1271,"depth":283,"text":1272},{"id":1278,"depth":283,"text":1279},{"id":1288,"depth":283,"text":1289},{"id":1295,"depth":283,"text":1296},{"id":1302,"depth":264,"text":1303},[1364,1366,1367,1368],{"name":1365,"item":367},"Home",{"name":1335,"item":1334},{"name":38,"item":37},{"name":5,"item":1369},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js\u002F","2026-08-01","Collect real-user CLS from a static site with the web-vitals attribution build, send it to an edge endpoint, and turn a 75th-percentile score into a named element to fix.","md",[1374,1375,1376,1378,1379],{"q":1265,"a":1268},{"q":1272,"a":1275},{"q":1279,"a":1377},"The attribution build is about 3 kilobytes gzipped and runs entirely in idle time, reporting once per page via sendBeacon. It is one of the few third-party-shaped additions that does not measurably move the metrics it measures.",{"q":1289,"a":1292},{"q":1296,"a":1299},{},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js",{"title":5,"description":1371},"measuring-cls-in-the-field-with-web-vitals-js","performance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js\u002Findex","dnglU7hWuMmlJGnKPodSsqkN_be7V4GqJNDtZ8bVYuA",[1387,1390,1393,1396,1399,1402,1405,1408,1411,1414,1417,1420,1423,1426,1429,1432,1435,1438,1441,1444,1447,1450,1453,1456,1459,1462,1465,1468,1471,1474,1477,1480,1482,1484,1485,1488,1491,1494,1497,1500,1503,1506,1509,1512,1515,1518,1521,1524,1527,1530,1533,1536,1539,1542,1545,1547,1550,1553,1556,1559,1562,1565,1568,1571,1574,1577,1580,1583,1586,1589,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627],{"path":1388,"title":1389},"\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":1391,"title":1392},"\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":1394,"title":1395},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1397,"title":1398},"\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":1400,"title":1401},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1403,"title":1404},"\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":1406,"title":1407},"\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":1409,"title":1410},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":1412,"title":1413},"\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":1415,"title":1416},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":1418,"title":1419},"\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":1421,"title":1422},"\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":1424,"title":1425},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":1427,"title":1428},"\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":1430,"title":1431},"\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":1433,"title":1434},"\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":1436,"title":1437},"\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":1439,"title":1440},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fhandling-dynamic-routes-in-nextjs-static-export","Handling Dynamic Routes in Next.js Static Export",{"path":1442,"title":1443},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1445,"title":1446},"\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":1448,"title":1449},"\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":1451,"title":1452},"\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":1454,"title":1455},"\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":1457,"title":1458},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":1460,"title":1461},"\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":1463,"title":1464},"\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":1466,"title":1467},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":1469,"title":1470},"\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":1472,"title":1473},"\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":1475,"title":1476},"\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":1478,"title":1479},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Feliminating-layout-shift-from-web-fonts","Eliminating Layout Shift From Web Fonts",{"path":1481,"title":1252},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-late-loading-embeds",{"path":1483,"title":38},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites",{"path":1381,"title":5},{"path":1486,"title":1487},"\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":1489,"title":1490},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":1492,"title":1493},"\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":1495,"title":1496},"\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":1498,"title":1499},"\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":1501,"title":1502},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":1504,"title":1505},"\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":1507,"title":1508},"\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":1510,"title":1511},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":1513,"title":1514},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":1516,"title":1517},"\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":1519,"title":1520},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":1522,"title":1523},"\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":1525,"title":1526},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Feliminating-render-blocking-css-on-static-sites","Eliminating Render-Blocking CSS on Static Sites",{"path":1528,"title":1529},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":1531,"title":1532},"\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":1534,"title":1535},"\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":1537,"title":1538},"\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":1540,"title":1541},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":1543,"title":1544},"\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":1546,"title":501},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests",{"path":1548,"title":1549},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":1551,"title":1552},"\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":1554,"title":1555},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":1557,"title":1558},"\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":1560,"title":1561},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":1563,"title":1564},"\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":1566,"title":1567},"\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":1569,"title":1570},"\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":1572,"title":1573},"\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":1575,"title":1576},"\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":1578,"title":1579},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":1581,"title":1582},"\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":1584,"title":1585},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":1587,"title":1588},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":1590,"title":826},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fmeasuring-build-time-regressions-in-ci",{"path":1592,"title":1593},"\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":1595,"title":1596},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":1598,"title":1599},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":1601,"title":1602},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":1604,"title":1605},"\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":1607,"title":1608},"\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":1610,"title":1611},"\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":1613,"title":1614},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":1616,"title":1617},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":1619,"title":1620},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":1622,"title":1623},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":1625,"title":1626},"\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":1628,"title":1629},"\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",1785611671075]