[{"data":1,"prerenderedAt":2222},["ShallowReactive",2],{"page:\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites":3,"all-docs-nav":1981},{"id":4,"title":5,"body":6,"breadcrumb":1955,"dateModified":1962,"datePublished":1962,"description":1963,"extension":1964,"faq":1965,"meta":1974,"navigation":1975,"path":1976,"seo":1977,"slug":12,"stem":1978,"type":1979,"__hash__":1980},"content\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Findex.md","Third-Party Script Performance on Static Sites",{"type":7,"value":8,"toc":1934},"minimark",[9,13,17,31,210,215,218,221,287,295,388,392,395,436,613,616,713,717,724,947,950,962,966,969,1031,1043,1047,1050,1058,1237,1240,1248,1298,1302,1305,1386,1389,1396,1400,1407,1511,1518,1522,1525,1531,1537,1759,1762,1772,1776,1819,1823,1843,1847,1852,1855,1859,1862,1866,1869,1873,1876,1880,1883,1887,1890,1894,1930],[10,11,5],"h1",{"id":12},"third-party-script-performance-on-static-sites",[14,15,16],"p",{},"A static site ships pre-rendered HTML, cached at the edge, with no server work per request. Then someone adds an analytics tag, a chat widget, a consent manager and two embeds, and the page that scored 99 in the lab now takes four seconds to become interactive on a mid-tier Android phone. Every one of those additions was reasonable in isolation; the page's performance is decided by their sum.",[14,18,19,20,25,26,30],{},"This guide covers how to budget third-party code, how to defer or replace the expensive parts, when proxying through your own domain pays off, and how to measure each script's real cost in main-thread time rather than bytes. It sits under ",[21,22,24],"a",{"href":23},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002F","Performance Optimization & Core Web Vitals for SSGs",", next to ",[21,27,29],{"href":28},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002F","JavaScript Hydration & Partial Rendering",", which deals with the JavaScript you wrote yourself.",[32,33,34,206],"figure",{},[35,36,43,44,43,48,43,52,43,59],"svg",{"viewBox":37,"role":38,"ariaLabelledBy":39,"xmlns":42},"0 0 760 320","img",[40,41],"tp-cost-title","tp-cost-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[45,46,47],"title",{"id":40},"Main-thread time by origin on a documentation page",[49,50,51],"desc",{"id":41},"A stacked bar showing 940 milliseconds of main thread work on a mid-tier mobile device. First-party code accounts for 180 milliseconds, the tag manager 120, the analytics library 90, the chat widget 340 and the video player 210. Third-party code is four fifths of the total.",[53,54],"rect",{"x":55,"y":55,"width":56,"height":57,"fill":58},"0","760","320","#ffffff",[60,61,63,64,63,72,63,82,63,88,63,92,63,99,63,103,63,106,63,111,63,115,63,118,63,125,63,129,63,132,63,137,63,141,63,144,63,150,63,155,63,160,63,165,63,170,63,174,63,178,63,182,63,185,63,188,63,192,63,196,63,199,63,202,43],"g",{"style":62},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[65,66,71],"text",{"x":67,"y":68,"fill":69,"style":70},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","Who is using the main thread on a \"static\" page",[53,73],{"x":74,"y":74,"width":75,"height":76,"rx":77,"fill":78,"opacity":79,"stroke":80,"style":81},"60","122","54","8","#8ac926","0.2","#5a8a16","stroke-width:1.5px",[65,83,87],{"x":84,"y":85,"fill":69,"style":86},"121","84","font-size:12px;font-weight:700;text-anchor:middle","First party",[65,89,91],{"x":84,"y":90,"fill":69,"style":86},"102","180 ms",[53,93],{"x":94,"y":74,"width":95,"height":76,"rx":77,"fill":96,"opacity":97,"stroke":98,"style":81},"192","82","#ffca3a","0.34","#a97b00",[65,100,102],{"x":101,"y":85,"fill":69,"style":86},"233","Tag mgr",[65,104,105],{"x":101,"y":90,"fill":98,"style":86},"120 ms",[53,107],{"x":108,"y":74,"width":109,"height":76,"rx":77,"fill":96,"opacity":110,"stroke":98,"style":81},"284","64","0.26",[65,112,114],{"x":113,"y":85,"fill":69,"style":86},"316","Analytics",[65,116,117],{"x":113,"y":90,"fill":98,"style":86},"90 ms",[53,119],{"x":120,"y":74,"width":121,"height":76,"rx":77,"fill":122,"opacity":123,"stroke":124,"style":81},"358","220","#ff595e","0.16","#d83b41",[65,126,128],{"x":127,"y":85,"fill":69,"style":86},"468","Chat widget",[65,130,131],{"x":127,"y":90,"fill":69,"style":86},"340 ms",[53,133],{"x":134,"y":74,"width":135,"height":76,"rx":77,"fill":122,"opacity":136,"stroke":124,"style":81},"588","136","0.12",[65,138,140],{"x":139,"y":85,"fill":69,"style":86},"656","Video player",[65,142,143],{"x":139,"y":90,"fill":69,"style":86},"210 ms",[65,145,149],{"x":74,"y":146,"fill":147,"style":148},"146","#556071","font-size:12px","940 ms total · 760 ms of it third party · measured with the Lighthouse third-party summary",[65,151,154],{"x":74,"y":152,"fill":69,"style":153},"186","font-size:13px;font-weight:700","What the reader gets for that time",[53,156],{"x":74,"y":157,"width":157,"height":158,"rx":159,"fill":78,"opacity":123,"stroke":80,"style":81},"200","66","10",[65,161,164],{"x":162,"y":163,"fill":69,"style":86},"160","224","Page content",[65,166,169],{"x":162,"y":167,"fill":147,"style":168},"244","font-size:12px;text-anchor:middle","rendered from HTML",[65,171,173],{"x":162,"y":172,"fill":147,"style":168},"260","before any script runs",[53,175],{"x":176,"y":157,"width":157,"height":158,"rx":159,"fill":96,"opacity":177,"stroke":98,"style":81},"290","0.24",[65,179,181],{"x":180,"y":163,"fill":69,"style":86},"390","Measurement",[65,183,184],{"x":180,"y":167,"fill":147,"style":168},"page views, vitals",[65,186,187],{"x":180,"y":172,"fill":147,"style":168},"3-5 KB is enough",[53,189],{"x":190,"y":157,"width":191,"height":158,"rx":159,"fill":122,"opacity":136,"stroke":124,"style":81},"520","204",[65,193,195],{"x":194,"y":163,"fill":69,"style":86},"622","Support chat",[65,197,198],{"x":194,"y":167,"fill":147,"style":168},"used by \u003C1% of readers",[65,200,201],{"x":194,"y":172,"fill":147,"style":168},"loaded by 100%",[65,203,205],{"x":74,"y":204,"fill":147,"style":148},"296","The widget nobody opens is usually the largest single cost on the page",[207,208,209],"figcaption",{},"Third-party code accounted for 81% of main-thread time on this page, and the largest contributor served a feature fewer than one in a hundred readers used.",[211,212,214],"h2",{"id":213},"budget-in-milliseconds-not-kilobytes","Budget in Milliseconds, Not Kilobytes",[14,216,217],{},"Bytes are easy to measure and a poor proxy for cost. A 40 KB analytics library that parses a config and sends one beacon might use 30 ms of main thread; a 40 KB widget that builds a shadow DOM, injects styles and installs a dozen listeners can use 400 ms. Interaction to Next Paint punishes the second and barely notices the first.",[14,219,220],{},"Set the budget as main-thread time on a mid-tier device — 150 ms of third-party execution is a workable ceiling for a content site — and get the number from Lighthouse's third-party summary or a Performance recording's Bottom-Up view filtered by URL:",[222,223,228],"pre",{"className":224,"code":225,"language":226,"meta":227,"style":227},"language-bash shiki shiki-themes github-light github-dark","npx lighthouse https:\u002F\u002Fexample.com --only-audits=third-party-summary \\\n  --form-factor=mobile --throttling-method=simulate --output=json \\\n  | jq '.audits[\"third-party-summary\"].details.items[]\n        | {entity: .entity, blocking: .blockingTime, transfer: .transferSize}'\n","bash","",[229,230,231,254,268,281],"code",{"__ignoreMap":227},[232,233,236,240,244,247,251],"span",{"class":234,"line":235},"line",1,[232,237,239],{"class":238},"sScJk","npx",[232,241,243],{"class":242},"sZZnC"," lighthouse",[232,245,246],{"class":242}," https:\u002F\u002Fexample.com",[232,248,250],{"class":249},"sj4cs"," --only-audits=third-party-summary",[232,252,253],{"class":249}," \\\n",[232,255,257,260,263,266],{"class":234,"line":256},2,[232,258,259],{"class":249},"  --form-factor=mobile",[232,261,262],{"class":249}," --throttling-method=simulate",[232,264,265],{"class":249}," --output=json",[232,267,253],{"class":249},[232,269,271,275,278],{"class":234,"line":270},3,[232,272,274],{"class":273},"szBVR","  |",[232,276,277],{"class":238}," jq",[232,279,280],{"class":242}," '.audits[\"third-party-summary\"].details.items[]\n",[232,282,284],{"class":234,"line":283},4,[232,285,286],{"class":242},"        | {entity: .entity, blocking: .blockingTime, transfer: .transferSize}'\n",[14,288,289,290,294],{},"Run it against a preview deploy on every pull request and fail the build when the total crosses the budget — the wiring is the same as any other CI gate, as described in ",[21,291,293],{"href":292},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002F","GitHub Actions for Automated SSG Builds",".",[296,297,298,317],"table",{},[299,300,301],"thead",{},[302,303,304,308,311,314],"tr",{},[305,306,307],"th",{},"Script",[305,309,310],{},"Transfer",[305,312,313],{},"Blocking time",[305,315,316],{},"Cost per KB",[318,319,320,335,349,362,376],"tbody",{},[302,321,322,326,329,332],{},[323,324,325],"td",{},"Self-hosted vitals beacon",[323,327,328],{},"3 KB",[323,330,331],{},"4 ms",[323,333,334],{},"1.3 ms\u002FKB",[302,336,337,340,343,346],{},[323,338,339],{},"Privacy-first analytics",[323,341,342],{},"6 KB",[323,344,345],{},"18 ms",[323,347,348],{},"3.0 ms\u002FKB",[302,350,351,354,357,359],{},[323,352,353],{},"Tag manager (empty container)",[323,355,356],{},"34 KB",[323,358,105],{},[323,360,361],{},"3.5 ms\u002FKB",[302,363,364,367,370,373],{},[323,365,366],{},"Consent platform",[323,368,369],{},"58 KB",[323,371,372],{},"190 ms",[323,374,375],{},"3.3 ms\u002FKB",[302,377,378,381,384,386],{},[323,379,380],{},"Support chat widget",[323,382,383],{},"96 KB",[323,385,131],{},[323,387,361],{},[211,389,391],{"id":390},"load-order-is-a-policy-decision","Load Order Is a Policy Decision",[14,393,394],{},"Every third party belongs in one of four tiers, and the tier determines how it loads:",[396,397,398,406,420,426],"ol",{},[399,400,401,405],"li",{},[402,403,404],"strong",{},"Blocking-critical"," — the consent manager, because everything else waits on its verdict. Load it early, keep it small, and make the banner a fixed overlay so it cannot shift the page.",[399,407,408,411,412,415,416,419],{},[402,409,410],{},"Deferred-useful"," — analytics and vitals collection. ",[229,413,414],{},"defer"," or load on ",[229,417,418],{},"requestIdleCallback","; they lose nothing by starting after first paint.",[399,421,422,425],{},[402,423,424],{},"Interaction-gated"," — chat, feedback, share widgets. Load on first click or hover of a static placeholder, so the 340 ms is paid only by readers who use it.",[399,427,428,431,432,435],{},[402,429,430],{},"Viewport-gated"," — heavy embeds below the fold. Load when an ",[229,433,434],{},"IntersectionObserver"," says they are close, with the space already reserved.",[222,437,441],{"className":438,"code":439,"language":440,"meta":227,"style":227},"language-html shiki shiki-themes github-light github-dark","\u003C!-- Tier 3: the widget costs nothing until someone wants it -->\n\u003Cbutton id=\"chat-launcher\" class=\"chat-launcher\">Chat with support\u003C\u002Fbutton>\n\u003Cscript>\n  document.getElementById('chat-launcher').addEventListener('click', () => {\n    const s = document.createElement('script');\n    s.src = 'https:\u002F\u002Fwidget.example-chat.com\u002Fembed.js';\n    s.async = true;\n    document.head.appendChild(s);\n  }, { once: true });\n\u003C\u002Fscript>\n","html",[229,442,443,449,483,492,526,552,566,579,591,603],{"__ignoreMap":227},[232,444,445],{"class":234,"line":235},[232,446,448],{"class":447},"sJ8bj","\u003C!-- Tier 3: the widget costs nothing until someone wants it -->\n",[232,450,451,455,459,462,465,468,471,473,475,478,480],{"class":234,"line":256},[232,452,454],{"class":453},"sVt8B","\u003C",[232,456,458],{"class":457},"s9eBZ","button",[232,460,461],{"class":238}," id",[232,463,464],{"class":453},"=",[232,466,467],{"class":242},"\"chat-launcher\"",[232,469,470],{"class":238}," class",[232,472,464],{"class":453},[232,474,467],{"class":242},[232,476,477],{"class":453},">Chat with support\u003C\u002F",[232,479,458],{"class":457},[232,481,482],{"class":453},">\n",[232,484,485,487,490],{"class":234,"line":270},[232,486,454],{"class":453},[232,488,489],{"class":457},"script",[232,491,482],{"class":453},[232,493,494,497,500,503,506,509,512,514,517,520,523],{"class":234,"line":283},[232,495,496],{"class":453},"  document.",[232,498,499],{"class":238},"getElementById",[232,501,502],{"class":453},"(",[232,504,505],{"class":242},"'chat-launcher'",[232,507,508],{"class":453},").",[232,510,511],{"class":238},"addEventListener",[232,513,502],{"class":453},[232,515,516],{"class":242},"'click'",[232,518,519],{"class":453},", () ",[232,521,522],{"class":273},"=>",[232,524,525],{"class":453}," {\n",[232,527,529,532,535,538,541,544,546,549],{"class":234,"line":528},5,[232,530,531],{"class":273},"    const",[232,533,534],{"class":249}," s",[232,536,537],{"class":273}," =",[232,539,540],{"class":453}," document.",[232,542,543],{"class":238},"createElement",[232,545,502],{"class":453},[232,547,548],{"class":242},"'script'",[232,550,551],{"class":453},");\n",[232,553,555,558,560,563],{"class":234,"line":554},6,[232,556,557],{"class":453},"    s.src ",[232,559,464],{"class":273},[232,561,562],{"class":242}," 'https:\u002F\u002Fwidget.example-chat.com\u002Fembed.js'",[232,564,565],{"class":453},";\n",[232,567,569,572,574,577],{"class":234,"line":568},7,[232,570,571],{"class":453},"    s.async ",[232,573,464],{"class":273},[232,575,576],{"class":249}," true",[232,578,565],{"class":453},[232,580,582,585,588],{"class":234,"line":581},8,[232,583,584],{"class":453},"    document.head.",[232,586,587],{"class":238},"appendChild",[232,589,590],{"class":453},"(s);\n",[232,592,594,597,600],{"class":234,"line":593},9,[232,595,596],{"class":453},"  }, { once: ",[232,598,599],{"class":249},"true",[232,601,602],{"class":453}," });\n",[232,604,606,609,611],{"class":234,"line":605},10,[232,607,608],{"class":453},"\u003C\u002F",[232,610,489],{"class":457},[232,612,482],{"class":453},[14,614,615],{},"That five-line pattern is the highest-value change on most content sites: it removed 340 ms of blocking time and 96 KB from every page load on the site measured above, while keeping the feature for the readers who use it.",[32,617,618,710],{},[35,619,43,624,43,627,43,630,43,633],{"viewBox":620,"role":38,"ariaLabelledBy":621,"xmlns":42},"0 0 760 300",[622,623],"tp-tiers-title","tp-tiers-desc",[45,625,626],{"id":622},"Four loading tiers for third-party code",[49,628,629],{"id":623},"A timeline divided into four tiers. Blocking-critical scripts run before first paint. Deferred-useful scripts run after first paint in idle time. Interaction-gated scripts run only on a click. Viewport-gated scripts run when the reader scrolls the element into view.",[53,631],{"x":55,"y":55,"width":56,"height":632,"fill":58},"300",[60,634,63,635,63,639,63,646,63,651,63,657,63,663,63,668,63,672,63,676,63,680,63,685,63,690,63,694,63,697,63,702,63,706,43],{"style":62},[65,636,638],{"x":67,"y":637,"fill":69,"style":70},"26","Decide the tier once, and the loading code follows",[234,640],{"x1":641,"y1":642,"x2":641,"y2":643,"stroke":644,"style":645},"280","46","248","#d9e2ef","stroke-width:2px;stroke-dasharray:5 4",[65,647,650],{"x":648,"y":74,"fill":147,"style":649},"288","font-size:11px","first paint",[65,652,656],{"x":653,"y":654,"fill":124,"style":655},"40","92","font-size:12px;font-weight:700","1 · blocking-critical",[53,658],{"x":157,"y":659,"width":660,"height":661,"rx":662,"fill":122,"opacity":123,"stroke":124,"style":81},"72","76","30","6",[65,664,667],{"x":665,"y":654,"fill":69,"style":666},"238","font-size:11px;text-anchor:middle","consent",[65,669,671],{"x":653,"y":670,"fill":98,"style":655},"144","2 · deferred-useful",[53,673],{"x":632,"y":674,"width":675,"height":661,"rx":662,"fill":96,"opacity":97,"stroke":98,"style":81},"124","150",[65,677,679],{"x":678,"y":670,"fill":69,"style":666},"375","analytics · idle",[65,681,684],{"x":653,"y":682,"fill":683,"style":655},"196","#166a9e","3 · interaction-gated",[53,686],{"x":687,"y":688,"width":162,"height":661,"rx":662,"fill":689,"opacity":123,"stroke":689,"style":81},"470","176","#1982c4",[65,691,693],{"x":692,"y":682,"fill":69,"style":666},"550","chat · on click",[65,695,696],{"x":653,"y":643,"fill":80,"style":655},"4 · viewport-gated",[53,698],{"x":699,"y":700,"width":701,"height":661,"rx":662,"fill":78,"opacity":79,"stroke":80,"style":81},"410","228","180",[65,703,705],{"x":704,"y":643,"fill":69,"style":666},"500","embed · on scroll",[65,707,709],{"x":653,"y":708,"fill":147,"style":649},"286","Only tier 1 is paid by every reader on every page — everything else is conditional",[207,711,712],{},"Most third-party code sits in tier 1 by accident rather than by decision. Moving each script to the latest tier that still works is where the time comes back.",[211,714,716],{"id":715},"proxy-what-you-can-replace-what-you-cannot","Proxy What You Can, Replace What You Cannot",[14,718,719,720,294],{},"Each third-party origin costs a DNS lookup, a TLS handshake and a fresh connection — 100 to 300 ms on a cold mobile network before a single byte of script arrives. Serving the file from your own domain removes all three and lets you set your own cache headers, which pairs directly with the policies in ",[21,721,723],{"href":722},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002F","CDN Caching Rules for SSGs",[222,725,729],{"className":726,"code":727,"language":728,"meta":227,"style":227},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F Cloudflare Worker: serve a pinned vendor script from your own origin\nexport default {\n  async fetch(request, env, ctx) {\n    const upstream = 'https:\u002F\u002Fcdn.example-analytics.com\u002Fv4.2.1\u002Fscript.js';\n    const cache = caches.default;\n    let res = await cache.match(request);\n    if (!res) {\n      res = new Response((await fetch(upstream)).body, {\n        headers: { 'content-type': 'text\u002Fjavascript',\n                   'cache-control': 'public, max-age=86400, immutable' },\n      });\n      ctx.waitUntil(cache.put(request, res.clone()));\n    }\n    return res;\n  },\n};\n","javascript",[229,730,731,736,746,774,788,800,822,836,860,877,890,896,920,926,935,941],{"__ignoreMap":227},[232,732,733],{"class":234,"line":235},[232,734,735],{"class":447},"\u002F\u002F Cloudflare Worker: serve a pinned vendor script from your own origin\n",[232,737,738,741,744],{"class":234,"line":256},[232,739,740],{"class":273},"export",[232,742,743],{"class":273}," default",[232,745,525],{"class":453},[232,747,748,751,754,756,760,763,766,768,771],{"class":234,"line":270},[232,749,750],{"class":273},"  async",[232,752,753],{"class":238}," fetch",[232,755,502],{"class":453},[232,757,759],{"class":758},"s4XuR","request",[232,761,762],{"class":453},", ",[232,764,765],{"class":758},"env",[232,767,762],{"class":453},[232,769,770],{"class":758},"ctx",[232,772,773],{"class":453},") {\n",[232,775,776,778,781,783,786],{"class":234,"line":283},[232,777,531],{"class":273},[232,779,780],{"class":249}," upstream",[232,782,537],{"class":273},[232,784,785],{"class":242}," 'https:\u002F\u002Fcdn.example-analytics.com\u002Fv4.2.1\u002Fscript.js'",[232,787,565],{"class":453},[232,789,790,792,795,797],{"class":234,"line":528},[232,791,531],{"class":273},[232,793,794],{"class":249}," cache",[232,796,537],{"class":273},[232,798,799],{"class":453}," caches.default;\n",[232,801,802,805,808,810,813,816,819],{"class":234,"line":554},[232,803,804],{"class":273},"    let",[232,806,807],{"class":453}," res ",[232,809,464],{"class":273},[232,811,812],{"class":273}," await",[232,814,815],{"class":453}," cache.",[232,817,818],{"class":238},"match",[232,820,821],{"class":453},"(request);\n",[232,823,824,827,830,833],{"class":234,"line":568},[232,825,826],{"class":273},"    if",[232,828,829],{"class":453}," (",[232,831,832],{"class":273},"!",[232,834,835],{"class":453},"res) {\n",[232,837,838,841,843,846,849,852,855,857],{"class":234,"line":581},[232,839,840],{"class":453},"      res ",[232,842,464],{"class":273},[232,844,845],{"class":273}," new",[232,847,848],{"class":238}," Response",[232,850,851],{"class":453},"((",[232,853,854],{"class":273},"await",[232,856,753],{"class":238},[232,858,859],{"class":453},"(upstream)).body, {\n",[232,861,862,865,868,871,874],{"class":234,"line":593},[232,863,864],{"class":453},"        headers: { ",[232,866,867],{"class":242},"'content-type'",[232,869,870],{"class":453},": ",[232,872,873],{"class":242},"'text\u002Fjavascript'",[232,875,876],{"class":453},",\n",[232,878,879,882,884,887],{"class":234,"line":605},[232,880,881],{"class":242},"                   'cache-control'",[232,883,870],{"class":453},[232,885,886],{"class":242},"'public, max-age=86400, immutable'",[232,888,889],{"class":453}," },\n",[232,891,893],{"class":234,"line":892},11,[232,894,895],{"class":453},"      });\n",[232,897,899,902,905,908,911,914,917],{"class":234,"line":898},12,[232,900,901],{"class":453},"      ctx.",[232,903,904],{"class":238},"waitUntil",[232,906,907],{"class":453},"(cache.",[232,909,910],{"class":238},"put",[232,912,913],{"class":453},"(request, res.",[232,915,916],{"class":238},"clone",[232,918,919],{"class":453},"()));\n",[232,921,923],{"class":234,"line":922},13,[232,924,925],{"class":453},"    }\n",[232,927,929,932],{"class":234,"line":928},14,[232,930,931],{"class":273},"    return",[232,933,934],{"class":453}," res;\n",[232,936,938],{"class":234,"line":937},15,[232,939,940],{"class":453},"  },\n",[232,942,944],{"class":234,"line":943},16,[232,945,946],{"class":453},"};\n",[14,948,949],{},"Pin the version rather than proxying a floating URL: an unpinned proxy silently ships whatever the vendor pushed, which is the same supply-chain exposure as the original script plus the illusion of control. Check for upstream changes on a schedule and bump deliberately.",[14,951,952,953,956,957,961],{},"Where proxying is not allowed by the vendor's terms, the question becomes whether you need the script at all. A first-party page-view counter and the ",[229,954,955],{},"web-vitals"," beacon cover what most content teams actually look at — the approach in ",[21,958,960],{"href":959},"\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"," — and it removes an entire connection from the critical path.",[211,963,965],{"id":964},"facades-for-heavy-embeds","Facades for Heavy Embeds",[14,967,968],{},"Video players, maps and social embeds ship hundreds of kilobytes to render something the reader may never interact with. A facade — a static image or styled box that loads the real embed on click — keeps the feature and removes the cost:",[296,970,971,987],{},[299,972,973],{},[302,974,975,978,981,984],{},[305,976,977],{},"Embed",[305,979,980],{},"Real embed",[305,982,983],{},"Facade",[305,985,986],{},"Saved on first load",[318,988,989,1003,1017],{},[302,990,991,994,997,1000],{},[323,992,993],{},"YouTube player",[323,995,996],{},"780 KB, 310 ms",[323,998,999],{},"24 KB poster, 2 ms",[323,1001,1002],{},"756 KB, 308 ms",[302,1004,1005,1008,1011,1014],{},[323,1006,1007],{},"Interactive map",[323,1009,1010],{},"640 KB, 280 ms",[323,1012,1013],{},"40 KB static tile, 3 ms",[323,1015,1016],{},"600 KB, 277 ms",[302,1018,1019,1022,1025,1028],{},[323,1020,1021],{},"Social post embed",[323,1023,1024],{},"210 KB, 140 ms",[323,1026,1027],{},"6 KB blockquote, 0 ms",[323,1029,1030],{},"204 KB, 140 ms",[14,1032,1033,1034,1038,1039,294],{},"The step-by-step version, including keeping the facade accessible to keyboard and screen-reader users, is in ",[21,1035,1037],{"href":1036},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Flazy-loading-youtube-embeds-on-static-sites\u002F","Lazy-Loading YouTube Embeds on Static Sites",". Because a facade and its replacement rarely have identical sizes, pair it with the reservation patterns from ",[21,1040,1042],{"href":1041},"\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",[211,1044,1046],{"id":1045},"measure-the-interaction-cost-not-just-the-load","Measure the Interaction Cost, Not Just the Load",[14,1048,1049],{},"Load-time metrics undersell third-party code, because the expensive part often happens later. A chat widget that polls every ten seconds, an analytics library that recomputes on every scroll event, a personalisation script that re-renders a rail when the tab regains focus — none of these appear in a Lighthouse run that ends at five seconds, and all of them show up in Interaction to Next Paint.",[14,1051,1052,1053,1057],{},"Record a 30-second Performance trace with the page idle and read the ",[1054,1055,1056],"em",{},"Bottom-Up"," view grouped by URL. Anything that appears while nothing is happening is a background cost you are paying on every page for the whole session:",[222,1059,1061],{"className":726,"code":1060,"language":728,"meta":227,"style":227},"\u002F\u002F Log long tasks and their attribution for a minute — paste in the console\nnew PerformanceObserver((list) => {\n  for (const task of list.getEntries()) {\n    if (task.duration \u003C 50) continue;\n    const src = task.attribution?.[0]?.containerSrc || task.attribution?.[0]?.name || 'unknown';\n    console.log(`${Math.round(task.duration)} ms  ${src}`);\n  }\n}).observe({ type: 'longtask', buffered: true });\n",[229,1062,1063,1068,1088,1113,1132,1166,1211,1216],{"__ignoreMap":227},[232,1064,1065],{"class":234,"line":235},[232,1066,1067],{"class":447},"\u002F\u002F Log long tasks and their attribution for a minute — paste in the console\n",[232,1069,1070,1073,1076,1078,1081,1084,1086],{"class":234,"line":256},[232,1071,1072],{"class":273},"new",[232,1074,1075],{"class":238}," PerformanceObserver",[232,1077,851],{"class":453},[232,1079,1080],{"class":758},"list",[232,1082,1083],{"class":453},") ",[232,1085,522],{"class":273},[232,1087,525],{"class":453},[232,1089,1090,1093,1095,1098,1101,1104,1107,1110],{"class":234,"line":270},[232,1091,1092],{"class":273},"  for",[232,1094,829],{"class":453},[232,1096,1097],{"class":273},"const",[232,1099,1100],{"class":249}," task",[232,1102,1103],{"class":273}," of",[232,1105,1106],{"class":453}," list.",[232,1108,1109],{"class":238},"getEntries",[232,1111,1112],{"class":453},"()) {\n",[232,1114,1115,1117,1120,1122,1125,1127,1130],{"class":234,"line":283},[232,1116,826],{"class":273},[232,1118,1119],{"class":453}," (task.duration ",[232,1121,454],{"class":273},[232,1123,1124],{"class":249}," 50",[232,1126,1083],{"class":453},[232,1128,1129],{"class":273},"continue",[232,1131,565],{"class":453},[232,1133,1134,1136,1139,1141,1144,1146,1149,1152,1154,1156,1159,1161,1164],{"class":234,"line":528},[232,1135,531],{"class":273},[232,1137,1138],{"class":249}," src",[232,1140,537],{"class":273},[232,1142,1143],{"class":453}," task.attribution?.[",[232,1145,55],{"class":249},[232,1147,1148],{"class":453},"]?.containerSrc ",[232,1150,1151],{"class":273},"||",[232,1153,1143],{"class":453},[232,1155,55],{"class":249},[232,1157,1158],{"class":453},"]?.name ",[232,1160,1151],{"class":273},[232,1162,1163],{"class":242}," 'unknown'",[232,1165,565],{"class":453},[232,1167,1168,1171,1174,1176,1179,1182,1184,1187,1189,1192,1194,1197,1200,1203,1206,1209],{"class":234,"line":554},[232,1169,1170],{"class":453},"    console.",[232,1172,1173],{"class":238},"log",[232,1175,502],{"class":453},[232,1177,1178],{"class":242},"`${",[232,1180,1181],{"class":453},"Math",[232,1183,294],{"class":242},[232,1185,1186],{"class":238},"round",[232,1188,502],{"class":242},[232,1190,1191],{"class":453},"task",[232,1193,294],{"class":242},[232,1195,1196],{"class":453},"duration",[232,1198,1199],{"class":242},")",[232,1201,1202],{"class":242},"} ms  ${",[232,1204,1205],{"class":453},"src",[232,1207,1208],{"class":242},"}`",[232,1210,551],{"class":453},[232,1212,1213],{"class":234,"line":568},[232,1214,1215],{"class":453},"  }\n",[232,1217,1218,1221,1224,1227,1230,1233,1235],{"class":234,"line":581},[232,1219,1220],{"class":453},"}).",[232,1222,1223],{"class":238},"observe",[232,1225,1226],{"class":453},"({ type: ",[232,1228,1229],{"class":242},"'longtask'",[232,1231,1232],{"class":453},", buffered: ",[232,1234,599],{"class":249},[232,1236,602],{"class":453},[14,1238,1239],{},"On the documentation site above, this surfaced a 90 ms task firing every 10 seconds from the chat vendor's presence poll — invisible in every lab report, and enough to make the page feel sticky on a mid-tier device while a reader scrolled. The fix was the interaction gate: no chat script, no poll.",[14,1241,1242,1243,1247],{},"Field data closes the loop, because it captures the devices and session lengths a lab run never reproduces. The collection pipeline in ",[21,1244,1246],{"href":1245},"\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"," reports the interaction target as well as the number, which is usually enough to name the vendor responsible.",[296,1249,1250,1263],{},[299,1251,1252],{},[302,1253,1254,1257,1260],{},[305,1255,1256],{},"Signal",[305,1258,1259],{},"What it catches",[305,1261,1262],{},"What it misses",[318,1264,1265,1276,1287],{},[302,1266,1267,1270,1273],{},[323,1268,1269],{},"Lighthouse third-party summary",[323,1271,1272],{},"Load-time bytes and blocking time",[323,1274,1275],{},"Anything after the run ends",[302,1277,1278,1281,1284],{},[323,1279,1280],{},"Long-task observer (idle page)",[323,1282,1283],{},"Polling, timers, background re-renders",[323,1285,1286],{},"One-off costs during load",[302,1288,1289,1292,1295],{},[323,1290,1291],{},"Field INP with attribution",[323,1293,1294],{},"Real interaction latency on real devices",[323,1296,1297],{},"Cause, unless attribution is enabled",[211,1299,1301],{"id":1300},"keep-an-inventory-and-audit-it","Keep an Inventory and Audit It",[14,1303,1304],{},"Third-party code accumulates. A quarterly audit answers three questions per script: who owns it, what decision it informs, and what it costs in main-thread time. Anything without a clear answer to the first two is a candidate for deletion, and deletion is the only optimisation that always works.",[222,1306,1308],{"className":224,"code":1307,"language":226,"meta":227,"style":227},"# List every third-party origin the built site references\ngrep -rhoE 'https?:\u002F\u002F[a-z0-9.-]+' dist --include='*.html' \\\n  | sed -E 's#https?:\u002F\u002F([^\u002F]+).*#\\1#' | sort | uniq -c | sort -rn \\\n  | grep -v 'example\\.com'\n",[229,1309,1310,1315,1337,1373],{"__ignoreMap":227},[232,1311,1312],{"class":234,"line":235},[232,1313,1314],{"class":447},"# List every third-party origin the built site references\n",[232,1316,1317,1320,1323,1326,1329,1332,1335],{"class":234,"line":256},[232,1318,1319],{"class":238},"grep",[232,1321,1322],{"class":249}," -rhoE",[232,1324,1325],{"class":242}," 'https?:\u002F\u002F[a-z0-9.-]+'",[232,1327,1328],{"class":242}," dist",[232,1330,1331],{"class":249}," --include=",[232,1333,1334],{"class":242},"'*.html'",[232,1336,253],{"class":249},[232,1338,1339,1341,1344,1347,1350,1353,1356,1358,1361,1364,1366,1368,1371],{"class":234,"line":270},[232,1340,274],{"class":273},[232,1342,1343],{"class":238}," sed",[232,1345,1346],{"class":249}," -E",[232,1348,1349],{"class":242}," 's#https?:\u002F\u002F([^\u002F]+).*#\\1#'",[232,1351,1352],{"class":273}," |",[232,1354,1355],{"class":238}," sort",[232,1357,1352],{"class":273},[232,1359,1360],{"class":238}," uniq",[232,1362,1363],{"class":249}," -c",[232,1365,1352],{"class":273},[232,1367,1355],{"class":238},[232,1369,1370],{"class":249}," -rn",[232,1372,253],{"class":249},[232,1374,1375,1377,1380,1383],{"class":234,"line":283},[232,1376,274],{"class":273},[232,1378,1379],{"class":238}," grep",[232,1381,1382],{"class":249}," -v",[232,1384,1385],{"class":242}," 'example\\.com'\n",[14,1387,1388],{},"Run it in CI and diff against a committed allowlist so a new origin appears in code review rather than in a Lighthouse report three months later. Keep the allowlist in the repository next to the content, with a one-line comment per origin naming the owner and the reason. When someone leaves the team, the file is the only record of why a script exists, and an unexplained entry is the easiest deletion you will ever make.",[14,1390,1391,1392,294],{},"The audit is also the moment to check what each vendor has become. Scripts grow: an analytics library that was 12 KB two years ago is often 40 KB today, with features nobody on your team enabled. Compare the current transfer size and blocking time against the numbers you recorded at adoption, and treat a doubling as a reason to re-open the decision rather than a fact of life. The full audit workflow, including reading the Lighthouse treemap to attribute bytes to entities, is in ",[21,1393,1395],{"href":1394},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fauditing-third-party-scripts-with-lighthouse\u002F","Auditing Third-Party Scripts With Lighthouse",[211,1397,1399],{"id":1398},"consent-managers-sit-on-the-critical-path","Consent Managers Sit on the Critical Path",[14,1401,1402,1403,1406],{},"A consent platform is the one third party that genuinely belongs early, and that makes it the most expensive one to get wrong. Every gated script waits for its verdict, so its own load time is added to the start time of everything downstream. On the site measured above, a 190 ms consent script delayed analytics by 190 ms — and delayed the ",[1054,1404,1405],{},"decision"," about the chat widget by the same amount, which pushed the widget's 340 ms into the window where the reader was trying to scroll.",[32,1408,1409,1508],{},[35,1410,43,1414,43,1417,43,1420,43,1422],{"viewBox":620,"role":38,"ariaLabelledBy":1411,"xmlns":42},[1412,1413],"tp-consent-title","tp-consent-desc",[45,1415,1416],{"id":1412},"How a consent gate serialises downstream scripts",[49,1418,1419],{"id":1413},"Two sequences. In the naive setup the consent script loads for 190 milliseconds, then analytics runs, then the chat widget loads, finishing at 720 milliseconds. In the improved setup consent is self-hosted and 40 milliseconds, analytics runs in idle time and chat is gated behind a click, so the main thread is clear by 260 milliseconds.",[53,1421],{"x":55,"y":55,"width":56,"height":632,"fill":58},[60,1423,63,1424,63,1427,63,1430,63,1435,63,1440,63,1444,63,1448,63,1452,63,1456,63,1460,63,1463,63,1467,63,1471,63,1474,63,1479,63,1483,63,1486,63,1490,63,1494,63,1497,63,1501,63,1505,43],{"style":62},[65,1425,1426],{"x":67,"y":637,"fill":69,"style":70},"The gate decides when everything else may start",[65,1428,1429],{"x":653,"y":158,"fill":124,"style":655},"Vendor consent, everything gated",[53,1431],{"x":74,"y":1432,"width":1433,"height":661,"rx":662,"fill":122,"opacity":1434,"stroke":124,"style":81},"78","190","0.18",[65,1436,1439],{"x":1437,"y":1438,"fill":69,"style":666},"155","98","consent 190 ms",[53,1441],{"x":1442,"y":1432,"width":1443,"height":661,"rx":662,"fill":96,"opacity":97,"stroke":98,"style":81},"256","90",[65,1445,1447],{"x":1446,"y":1438,"fill":69,"style":666},"301","analytics",[53,1449],{"x":1450,"y":1432,"width":1451,"height":661,"rx":662,"fill":122,"opacity":136,"stroke":124,"style":81},"352","340",[65,1453,1455],{"x":1454,"y":1438,"fill":69,"style":666},"522","chat widget 340 ms · main thread busy to 720 ms",[65,1457,1459],{"x":653,"y":1458,"fill":80,"style":655},"166","Self-hosted consent, tiered loading",[53,1461],{"x":74,"y":1462,"width":74,"height":661,"rx":662,"fill":78,"opacity":79,"stroke":80,"style":81},"178",[65,1464,1466],{"x":1443,"y":1465,"fill":69,"style":666},"198","40 ms",[53,1468],{"x":1469,"y":1462,"width":1470,"height":661,"rx":662,"fill":96,"opacity":97,"stroke":98,"style":81},"126","120",[65,1472,1473],{"x":152,"y":1465,"fill":69,"style":666},"analytics idle",[53,1475],{"x":1476,"y":1462,"width":1477,"height":661,"rx":662,"fill":644,"opacity":1478,"stroke":147,"style":81},"252","130","0.5",[65,1480,1482],{"x":1481,"y":1465,"fill":69,"style":666},"317","thread clear",[53,1484],{"x":687,"y":1462,"width":121,"height":661,"rx":662,"fill":689,"opacity":1485,"stroke":689,"style":81},"0.14",[65,1487,1489],{"x":1488,"y":1465,"fill":69,"style":666},"580","chat only if clicked",[234,1491],{"x1":74,"y1":1492,"x2":1493,"y2":1492,"stroke":147,"style":81},"236","700",[65,1495,1496],{"x":74,"y":1442,"fill":147,"style":649},"0 ms",[65,1498,1500],{"x":1499,"y":1442,"fill":147,"style":649},"360","400 ms",[65,1502,1504],{"x":1503,"y":1442,"fill":147,"style":649},"650","800 ms",[65,1506,1507],{"x":74,"y":108,"fill":147,"style":649},"Same features, same consent guarantees — 460 ms less main-thread work before the reader can interact",[207,1509,1510],{},"Gating is not the problem; serialising is. Shrinking the gate and moving the gated work out of the load path recovers most of the time without changing what the reader consents to.",[14,1512,1513,1514,1517],{},"Three things reliably help. Self-host the consent bundle if the vendor's licence allows, so it does not pay for its own connection. Keep the ",[1054,1515,1516],{},"categories"," small — a site with three cookie categories needs a fraction of the logic a site with twelve does. And separate the gate from the load: let the consent state resolve into a simple flag, then let each script decide for itself when to start, rather than having the platform inject all of them the moment consent lands. That last change alone converts a burst of simultaneous script execution into a sequence the browser can schedule around.",[211,1519,1521],{"id":1520},"when-you-cannot-remove-it","When You Cannot Remove It",[14,1523,1524],{},"Sometimes a script is non-negotiable: a legal requirement, a vendor contract, a marketing system the team depends on. Three techniques limit the damage without removing the script.",[14,1526,1527,1530],{},[402,1528,1529],{},"Run it off the main thread."," Tools in the Partytown family relocate a third-party script into a web worker and proxy its DOM access back to the main thread. It works well for analytics and tag managers, which mostly read a few globals and send requests, and badly for anything that renders UI. Expect to spend a day validating that the vendor still receives what it expects.",[14,1532,1533,1536],{},[402,1534,1535],{},"Bound it in time."," Wrap the loader so a script that has not initialised within a few seconds is abandoned rather than left retrying:",[222,1538,1540],{"className":726,"code":1539,"language":728,"meta":227,"style":227},"const withTimeout = (src, ms = 4000) => new Promise((resolve, reject) => {\n  const s = document.createElement('script');\n  const timer = setTimeout(() => { s.remove(); reject(new Error(`timeout: ${src}`)); }, ms);\n  s.src = src;\n  s.async = true;\n  s.onload = () => { clearTimeout(timer); resolve(); };\n  s.onerror = () => { clearTimeout(timer); reject(new Error(`failed: ${src}`)); };\n  document.head.appendChild(s);\n});\n",[229,1541,1542,1590,1609,1656,1666,1677,1706,1745,1754],{"__ignoreMap":227},[232,1543,1544,1546,1549,1551,1553,1555,1557,1560,1562,1565,1567,1569,1571,1574,1576,1579,1581,1584,1586,1588],{"class":234,"line":235},[232,1545,1097],{"class":273},[232,1547,1548],{"class":238}," withTimeout",[232,1550,537],{"class":273},[232,1552,829],{"class":453},[232,1554,1205],{"class":758},[232,1556,762],{"class":453},[232,1558,1559],{"class":758},"ms",[232,1561,537],{"class":273},[232,1563,1564],{"class":249}," 4000",[232,1566,1083],{"class":453},[232,1568,522],{"class":273},[232,1570,845],{"class":273},[232,1572,1573],{"class":249}," Promise",[232,1575,851],{"class":453},[232,1577,1578],{"class":758},"resolve",[232,1580,762],{"class":453},[232,1582,1583],{"class":758},"reject",[232,1585,1083],{"class":453},[232,1587,522],{"class":273},[232,1589,525],{"class":453},[232,1591,1592,1595,1597,1599,1601,1603,1605,1607],{"class":234,"line":256},[232,1593,1594],{"class":273},"  const",[232,1596,534],{"class":249},[232,1598,537],{"class":273},[232,1600,540],{"class":453},[232,1602,543],{"class":238},[232,1604,502],{"class":453},[232,1606,548],{"class":242},[232,1608,551],{"class":453},[232,1610,1611,1613,1616,1618,1621,1624,1626,1629,1632,1635,1637,1639,1641,1644,1646,1649,1651,1653],{"class":234,"line":270},[232,1612,1594],{"class":273},[232,1614,1615],{"class":249}," timer",[232,1617,537],{"class":273},[232,1619,1620],{"class":238}," setTimeout",[232,1622,1623],{"class":453},"(() ",[232,1625,522],{"class":273},[232,1627,1628],{"class":453}," { s.",[232,1630,1631],{"class":238},"remove",[232,1633,1634],{"class":453},"(); ",[232,1636,1583],{"class":238},[232,1638,502],{"class":453},[232,1640,1072],{"class":273},[232,1642,1643],{"class":238}," Error",[232,1645,502],{"class":453},[232,1647,1648],{"class":242},"`timeout: ${",[232,1650,1205],{"class":453},[232,1652,1208],{"class":242},[232,1654,1655],{"class":453},")); }, ms);\n",[232,1657,1658,1661,1663],{"class":234,"line":283},[232,1659,1660],{"class":453},"  s.src ",[232,1662,464],{"class":273},[232,1664,1665],{"class":453}," src;\n",[232,1667,1668,1671,1673,1675],{"class":234,"line":528},[232,1669,1670],{"class":453},"  s.async ",[232,1672,464],{"class":273},[232,1674,576],{"class":249},[232,1676,565],{"class":453},[232,1678,1679,1682,1685,1687,1690,1692,1695,1698,1701,1703],{"class":234,"line":554},[232,1680,1681],{"class":453},"  s.",[232,1683,1684],{"class":238},"onload",[232,1686,537],{"class":273},[232,1688,1689],{"class":453}," () ",[232,1691,522],{"class":273},[232,1693,1694],{"class":453}," { ",[232,1696,1697],{"class":238},"clearTimeout",[232,1699,1700],{"class":453},"(timer); ",[232,1702,1578],{"class":238},[232,1704,1705],{"class":453},"(); };\n",[232,1707,1708,1710,1713,1715,1717,1719,1721,1723,1725,1727,1729,1731,1733,1735,1738,1740,1742],{"class":234,"line":568},[232,1709,1681],{"class":453},[232,1711,1712],{"class":238},"onerror",[232,1714,537],{"class":273},[232,1716,1689],{"class":453},[232,1718,522],{"class":273},[232,1720,1694],{"class":453},[232,1722,1697],{"class":238},[232,1724,1700],{"class":453},[232,1726,1583],{"class":238},[232,1728,502],{"class":453},[232,1730,1072],{"class":273},[232,1732,1643],{"class":238},[232,1734,502],{"class":453},[232,1736,1737],{"class":242},"`failed: ${",[232,1739,1205],{"class":453},[232,1741,1208],{"class":242},[232,1743,1744],{"class":453},")); };\n",[232,1746,1747,1750,1752],{"class":234,"line":581},[232,1748,1749],{"class":453},"  document.head.",[232,1751,587],{"class":238},[232,1753,590],{"class":453},[232,1755,1756],{"class":234,"line":593},[232,1757,1758],{"class":453},"});\n",[14,1760,1761],{},"A vendor outage then costs a rejected promise instead of a page that never settles — the reason a hung third-party connection can dominate a bad-day percentile even when the median looks healthy.",[14,1763,1764,1767,1768,1771],{},[402,1765,1766],{},"Contain what it can reach."," A Content Security Policy with an explicit ",[229,1769,1770],{},"script-src"," allowlist keeps a compromised or rewritten vendor script from pulling in a second, unreviewed payload. It costs nothing at runtime and turns \"our analytics vendor was compromised\" into a blocked request rather than an incident. Ship it as a header from your edge layer, which on a static host is the same place your cache rules live.",[211,1773,1775],{"id":1774},"common-pitfalls","Common Pitfalls",[1777,1778,1779,1789,1795,1801,1807,1813],"ul",{},[399,1780,1781,1788],{},[402,1782,1783,1784,1787],{},"Trusting ",[229,1785,1786],{},"async"," to make a script free."," It only unblocks parsing; execution still competes with rendering and interaction on the same thread.",[399,1790,1791,1794],{},[402,1792,1793],{},"Tag managers as a bypass."," A container that anyone can edit turns performance work into a race against configuration changes. Audit it like code.",[399,1796,1797,1800],{},[402,1798,1799],{},"Measuring on a fast machine."," A desktop absorbs 340 ms without a visible symptom; a mid-tier phone does not. Always throttle to 4× CPU.",[399,1802,1803,1806],{},[402,1804,1805],{},"Proxying a floating version."," You inherit the vendor's changes with none of their testing. Pin and bump deliberately.",[399,1808,1809,1812],{},[402,1810,1811],{},"Loading the consent manager late."," Everything downstream waits on it, so a late banner delays every gated script and, worse, shifts the page when it appears.",[399,1814,1815,1818],{},[402,1816,1817],{},"Optimising the small ones."," Rank by blocking time and fix the top two; the tail rarely matters.",[211,1820,1822],{"id":1821},"key-takeaways","Key Takeaways",[1777,1824,1825,1828,1831,1834,1837,1840],{},[399,1826,1827],{},"Budget third-party code in main-thread milliseconds, not kilobytes — 150 ms is a workable ceiling for a content site.",[399,1829,1830],{},"Assign every script a loading tier, and move each one to the latest tier that still delivers its value.",[399,1832,1833],{},"Interaction-gating a chat widget is usually the single largest available win.",[399,1835,1836],{},"Proxy pinned vendor files through your own domain to remove a connection, or replace them with a first-party equivalent.",[399,1838,1839],{},"Use facades for video, maps and social embeds, and reserve their space so the swap costs no layout shift.",[399,1841,1842],{},"Keep an origin allowlist in CI so new third parties arrive through code review.",[211,1844,1846],{"id":1845},"faq","FAQ",[1848,1849,1851],"h3",{"id":1850},"how-much-third-party-javascript-is-too-much-on-a-static-site","How much third-party JavaScript is too much on a static site?",[14,1853,1854],{},"Budget by main-thread time rather than bytes: 150 milliseconds of third-party execution on a mid-tier mobile device is a reasonable ceiling for a content site. That usually works out at 60 to 90 kilobytes compressed, but two scripts of identical size can differ tenfold in execution cost, so time is the honest unit.",[1848,1856,1858],{"id":1857},"does-loading-a-script-with-async-or-defer-make-it-free","Does loading a script with async or defer make it free?",[14,1860,1861],{},"No. Both keep the script from blocking parsing, but the execution still happens on the main thread and still competes with rendering and interaction. Deferring changes when you pay, not whether you pay.",[1848,1863,1865],{"id":1864},"is-proxying-a-third-party-script-through-my-own-domain-worth-it","Is proxying a third-party script through my own domain worth it?",[14,1867,1868],{},"It removes a DNS lookup, a TLS handshake and a separate connection, which typically saves 100 to 300 milliseconds on a cold mobile connection, and it lets you cache the file on your own terms. The trade-off is that you own updating it, so pin a version and check for changes on a schedule.",[1848,1870,1872],{"id":1871},"where-should-a-consent-banner-script-go","Where should a consent banner script go?",[14,1874,1875],{},"In the head, loaded early, because everything else waits on its decision. Keep it small and self-hosted if the vendor allows it. The banner element itself must be a fixed overlay so that showing it never shifts the page.",[1848,1877,1879],{"id":1878},"can-a-static-site-use-a-tag-manager-without-wrecking-performance","Can a static site use a tag manager without wrecking performance?",[14,1881,1882],{},"It can, but a tag manager converts a code review into a runtime configuration change, which is how sites end up with scripts nobody remembers adding. If you keep one, audit its container quarterly and treat each contained tag as part of your performance budget.",[1848,1884,1886],{"id":1885},"what-is-the-cheapest-analytics-option-for-a-static-site","What is the cheapest analytics option for a static site?",[14,1888,1889],{},"A first-party endpoint that receives a beacon, or a lightweight self-hosted script in the 1 to 5 kilobyte range. Both avoid a third-party connection entirely and give you the page-view and vitals data most content sites actually use.",[211,1891,1893],{"id":1892},"related","Related",[1777,1895,1896,1905,1910,1915,1920,1925],{},[399,1897,1898,1901,1902,1904],{},[402,1899,1900],{},"Parent:"," ",[21,1903,24],{"href":23}," — where third-party cost fits the vitals picture.",[399,1906,1907,1909],{},[21,1908,960],{"href":959}," — replacing a vendor connection with your own.",[399,1911,1912,1914],{},[21,1913,1037],{"href":1036}," — the facade pattern in detail.",[399,1916,1917,1919],{},[21,1918,1395],{"href":1394}," — attributing bytes and blocking time to entities.",[399,1921,1922,1924],{},[21,1923,29],{"href":28}," — the same budget applied to your own code.",[399,1926,1927,1929],{},[21,1928,1042],{"href":1041}," — keeping late arrivals from moving the page.",[1931,1932,1933],"style",{},"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 .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 .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}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":227,"searchDepth":256,"depth":256,"links":1935},[1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1954],{"id":213,"depth":256,"text":214},{"id":390,"depth":256,"text":391},{"id":715,"depth":256,"text":716},{"id":964,"depth":256,"text":965},{"id":1045,"depth":256,"text":1046},{"id":1300,"depth":256,"text":1301},{"id":1398,"depth":256,"text":1399},{"id":1520,"depth":256,"text":1521},{"id":1774,"depth":256,"text":1775},{"id":1821,"depth":256,"text":1822},{"id":1845,"depth":256,"text":1846,"children":1947},[1948,1949,1950,1951,1952,1953],{"id":1850,"depth":270,"text":1851},{"id":1857,"depth":270,"text":1858},{"id":1864,"depth":270,"text":1865},{"id":1871,"depth":270,"text":1872},{"id":1878,"depth":270,"text":1879},{"id":1885,"depth":270,"text":1886},{"id":1892,"depth":256,"text":1893},[1956,1959,1960],{"name":1957,"item":1958},"Home","\u002F",{"name":24,"item":23},{"name":5,"item":1961},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002F","2026-08-01","Analytics, chat widgets, embeds and consent tools undo a fast static build. Budget them, defer them, proxy them, and measure what each one actually costs in main-thread time.","md",[1966,1969,1970,1971,1972,1973],{"q":1851,"a":1967},{"Budget by main-thread time rather than bytes":1968},"150 milliseconds of third-party execution on a mid-tier mobile device is a reasonable ceiling for a content site. That usually works out at 60 to 90 kilobytes compressed, but two scripts of identical size can differ tenfold in execution cost, so time is the honest unit.",{"q":1858,"a":1861},{"q":1865,"a":1868},{"q":1872,"a":1875},{"q":1879,"a":1882},{"q":1886,"a":1889},{},true,"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites",{"title":5,"description":1963},"performance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Findex","guide","hqr6D9TrHbPsSTPwcBzfa6Bic07OX-XXW5MX9nAEqWY",[1982,1985,1988,1991,1994,1997,2000,2003,2006,2009,2012,2015,2018,2021,2024,2027,2030,2033,2036,2039,2042,2045,2048,2051,2054,2057,2060,2062,2065,2068,2071,2074,2076,2079,2082,2085,2088,2091,2094,2097,2100,2103,2106,2109,2112,2115,2117,2120,2123,2126,2129,2132,2134,2135,2137,2139,2142,2145,2148,2151,2154,2157,2160,2163,2166,2169,2171,2174,2177,2180,2183,2186,2189,2192,2195,2198,2201,2204,2207,2210,2213,2216,2219],{"path":1983,"title":1984},"\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":1986,"title":1987},"\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":1989,"title":1990},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1992,"title":1993},"\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":1995,"title":1996},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1998,"title":1999},"\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":2001,"title":2002},"\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":2004,"title":2005},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":2007,"title":2008},"\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":2010,"title":2011},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":2013,"title":2014},"\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":2016,"title":2017},"\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":2019,"title":2020},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":2022,"title":2023},"\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":2025,"title":2026},"\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":2028,"title":2029},"\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":2031,"title":2032},"\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":2034,"title":2035},"\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":2037,"title":2038},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":2040,"title":2041},"\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":2043,"title":2044},"\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":2046,"title":2047},"\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":2049,"title":2050},"\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":2052,"title":2053},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":2055,"title":2056},"\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":2058,"title":2059},"\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":2061,"title":723},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs",{"path":2063,"title":2064},"\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":2066,"title":2067},"\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":2069,"title":2070},"\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":2072,"title":2073},"\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":2075,"title":1042},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-late-loading-embeds",{"path":2077,"title":2078},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":2080,"title":2081},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js","Measuring CLS in the Field With web-vitals.js",{"path":2083,"title":2084},"\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":2086,"title":2087},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":2089,"title":2090},"\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":2092,"title":2093},"\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":2095,"title":2096},"\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":2098,"title":2099},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":2101,"title":2102},"\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":2104,"title":2105},"\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":2107,"title":2108},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":2110,"title":2111},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":2113,"title":2114},"\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":2116,"title":29},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering",{"path":2118,"title":2119},"\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":2121,"title":2122},"\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":2124,"title":2125},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":2127,"title":2128},"\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":2130,"title":2131},"\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":2133,"title":1395},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fauditing-third-party-scripts-with-lighthouse",{"path":1976,"title":5},{"path":2136,"title":1037},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Flazy-loading-youtube-embeds-on-static-sites",{"path":2138,"title":960},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests",{"path":2140,"title":2141},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":2143,"title":2144},"\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":2146,"title":2147},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":2149,"title":2150},"\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":2152,"title":2153},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":2155,"title":2156},"\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":2158,"title":2159},"\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":2161,"title":2162},"\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":2164,"title":2165},"\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":2167,"title":2168},"\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":2170,"title":293},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds",{"path":2172,"title":2173},"\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":2175,"title":2176},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":2178,"title":2179},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":2181,"title":2182},"\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":2184,"title":2185},"\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":2187,"title":2188},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":2190,"title":2191},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":2193,"title":2194},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":2196,"title":2197},"\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":2199,"title":2200},"\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":2202,"title":2203},"\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":2205,"title":2206},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":2208,"title":2209},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":2211,"title":2212},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":2214,"title":2215},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":2217,"title":2218},"\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":2220,"title":2221},"\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]