[{"data":1,"prerenderedAt":1651},["ShallowReactive",2],{"page:\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests":3,"all-docs-nav":1407},{"id":4,"title":5,"body":6,"breadcrumb":1383,"dateModified":1392,"datePublished":1392,"description":1393,"extension":1394,"faq":1395,"meta":1401,"navigation":440,"path":1402,"seo":1403,"slug":12,"stem":1404,"type":1405,"__hash__":1406},"content\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests\u002Findex.md","Self-Hosting Analytics to Cut Third-Party Requests",{"type":7,"value":8,"toc":1361},"minimark",[9,13,17,26,31,44,48,201,205,210,213,551,554,565,569,882,885,889,901,905,932,935,939,942,1036,1051,1054,1058,1061,1140,1225,1228,1232,1270,1274,1279,1283,1287,1290,1294,1297,1301,1304,1308,1311,1315,1318,1322,1357],[10,11,5],"h1",{"id":12},"self-hosting-analytics-to-cut-third-party-requests",[14,15,16],"p",{},"A hosted analytics script costs more than its file size. It opens a connection to another origin, which means a DNS lookup, a TLS handshake and a fresh TCP round trip before the first byte arrives — 100 to 300 ms on a cold mobile network. It is usually gated behind a consent decision, so it also inherits that platform's latency. And it ships features most content teams never look at.",[14,18,19,20,25],{},"For a documentation or content site, the data that gets acted on is small: which pages are read, where readers came from, what device class they are on, and what the Core Web Vitals look like per template. All of that fits in a first-party script of a few kilobytes and an endpoint on your own domain. This guide builds it, measures the difference, and is the concrete alternative recommended in ",[21,22,24],"a",{"href":23},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002F","Third-Party Script Performance on Static Sites",".",[27,28,30],"h2",{"id":29},"prerequisites","Prerequisites",[32,33,34,38,41],"ul",{},[35,36,37],"li",{},"An edge function platform in front of your static site — Cloudflare Workers or Pages Functions, Netlify Functions, or Vercel Functions all work.",[35,39,40],{},"A store for the events: an analytics engine dataset, a managed database, or an object store you batch into.",[35,42,43],{},"Agreement with whoever owns privacy at your organisation on what you will and will not collect.",[27,45,47],{"id":46},"what-the-extra-origin-actually-costs","What the Extra Origin Actually Costs",[49,50,51,197],"figure",{},[52,53,60,61,60,65,60,69,60,76],"svg",{"viewBox":54,"role":55,"ariaLabelledBy":56,"xmlns":59},"0 0 760 300","img",[57,58],"sha-conn-title","sha-conn-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[62,63,64],"title",{"id":57},"Connection cost of a third-party analytics origin",[66,67,68],"desc",{"id":58},"Two request timelines. The third-party path spends 48 milliseconds on DNS, 92 on TLS, 60 on the request and 130 on script execution, finishing at 330 milliseconds. The first-party path reuses the existing connection, spends 18 milliseconds on the request and 18 on execution, finishing at 36 milliseconds.",[70,71],"rect",{"x":72,"y":72,"width":73,"height":74,"fill":75},"0","760","300","#ffffff",[77,78,80,81,80,89,80,96,80,106,80,112,80,117,80,121,80,128,80,132,80,137,80,141,80,146,80,152,80,157,80,161,80,165,80,172,80,176,80,181,80,186,80,189,80,193,60],"g",{"style":79},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[82,83,88],"text",{"x":84,"y":85,"fill":86,"style":87},"380","26","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","Most of the cost is the connection, not the code",[82,90,95],{"x":91,"y":92,"fill":93,"style":94},"40","66","#d83b41","font-size:12px;font-weight:700","Third-party origin",[70,97],{"x":98,"y":99,"width":100,"height":101,"rx":102,"fill":103,"opacity":104,"stroke":93,"style":105},"60","78","110","30","6","#ff595e","0.2","stroke-width:1.5px",[82,107,111],{"x":108,"y":109,"fill":86,"style":110},"115","98","font-size:11px;text-anchor:middle","DNS 48 ms",[70,113],{"x":114,"y":99,"width":115,"height":101,"rx":102,"fill":103,"opacity":116,"stroke":93,"style":105},"176","180","0.16",[82,118,120],{"x":119,"y":109,"fill":86,"style":110},"266","TLS handshake 92 ms",[70,122],{"x":123,"y":99,"width":124,"height":101,"rx":102,"fill":125,"opacity":126,"stroke":127,"style":105},"362","120","#ffca3a","0.34","#a97b00",[82,129,131],{"x":130,"y":109,"fill":86,"style":110},"422","fetch 60 ms",[70,133],{"x":134,"y":99,"width":135,"height":101,"rx":102,"fill":103,"opacity":136,"stroke":93,"style":105},"488","230","0.12",[82,138,140],{"x":139,"y":109,"fill":86,"style":110},"603","execute 130 ms · 96 KB",[82,142,145],{"x":91,"y":143,"fill":144,"style":94},"166","#5a8a16","First party, existing connection",[70,147],{"x":98,"y":148,"width":149,"height":101,"rx":102,"fill":150,"opacity":151,"stroke":144,"style":105},"178","80","#8ac926","0.22",[82,153,156],{"x":154,"y":155,"fill":86,"style":110},"100","198","fetch 18 ms",[70,158],{"x":159,"y":148,"width":149,"height":101,"rx":102,"fill":150,"opacity":160,"stroke":144,"style":105},"146","0.18",[82,162,164],{"x":163,"y":155,"fill":86,"style":110},"186","run 18 ms",[70,166],{"x":167,"y":148,"width":168,"height":101,"rx":102,"fill":169,"opacity":170,"stroke":171,"style":105},"232","200","#d9e2ef","0.5","#556071",[82,173,175],{"x":174,"y":155,"fill":86,"style":110},"332","nothing else needed",[177,178],"line",{"x1":98,"y1":179,"x2":180,"y2":179,"stroke":171,"style":105},"234","720",[82,182,185],{"x":98,"y":183,"fill":171,"style":184},"254","font-size:11px","0 ms",[82,187,188],{"x":84,"y":183,"fill":171,"style":184},"180 ms",[82,190,192],{"x":191,"y":183,"fill":171,"style":184},"670","360 ms",[82,194,196],{"x":98,"y":195,"fill":171,"style":184},"282","Cold connection, mid-tier Android on a fast 3G profile · the handshake alone outweighs the whole first-party path",[198,199,200],"figcaption",{},"The script's bytes are the smallest part of the bill. Removing the origin removes the DNS lookup and the handshake, which is where most of the 330 ms actually went.",[27,202,204],{"id":203},"the-recipe","The Recipe",[206,207,209],"h3",{"id":208},"_1-the-client-script","1. The client script",[14,211,212],{},"Everything the collector needs fits comfortably under two kilobytes before compression:",[214,215,220],"pre",{"className":216,"code":217,"language":218,"meta":219,"style":219},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F public\u002Fjs\u002Fmeasure.js — served from your own domain, cached for a year\n(function () {\n  const send = (type, extra) => {\n    const body = JSON.stringify({\n      type,\n      path: document.querySelector('meta[name=\"page-template\"]')?.content || location.pathname,\n      url: location.pathname,\n      ref: document.referrer ? new URL(document.referrer).hostname : '',\n      vw: Math.round(window.innerWidth \u002F 100) * 100,   \u002F\u002F bucketed, not exact\n      ...extra,\n    });\n    navigator.sendBeacon('\u002Fe', body);\n  };\n\n  addEventListener('DOMContentLoaded', () => send('view'));\n  addEventListener('visibilitychange', () => {\n    if (document.visibilityState === 'hidden') {\n      send('leave', { ms: Math.round(performance.now()) });\n    }\n  }, { once: true });\n})();\n","javascript","",[221,222,223,231,245,280,303,309,333,339,366,397,406,412,429,435,442,468,484,502,527,533,545],"code",{"__ignoreMap":219},[224,225,227],"span",{"class":177,"line":226},1,[224,228,230],{"class":229},"sJ8bj","\u002F\u002F public\u002Fjs\u002Fmeasure.js — served from your own domain, cached for a year\n",[224,232,234,238,242],{"class":177,"line":233},2,[224,235,237],{"class":236},"sVt8B","(",[224,239,241],{"class":240},"szBVR","function",[224,243,244],{"class":236}," () {\n",[224,246,248,251,255,258,261,265,268,271,274,277],{"class":177,"line":247},3,[224,249,250],{"class":240},"  const",[224,252,254],{"class":253},"sScJk"," send",[224,256,257],{"class":240}," =",[224,259,260],{"class":236}," (",[224,262,264],{"class":263},"s4XuR","type",[224,266,267],{"class":236},", ",[224,269,270],{"class":263},"extra",[224,272,273],{"class":236},") ",[224,275,276],{"class":240},"=>",[224,278,279],{"class":236}," {\n",[224,281,283,286,290,292,295,297,300],{"class":177,"line":282},4,[224,284,285],{"class":240},"    const",[224,287,289],{"class":288},"sj4cs"," body",[224,291,257],{"class":240},[224,293,294],{"class":288}," JSON",[224,296,25],{"class":236},[224,298,299],{"class":253},"stringify",[224,301,302],{"class":236},"({\n",[224,304,306],{"class":177,"line":305},5,[224,307,308],{"class":236},"      type,\n",[224,310,312,315,318,320,324,327,330],{"class":177,"line":311},6,[224,313,314],{"class":236},"      path: document.",[224,316,317],{"class":253},"querySelector",[224,319,237],{"class":236},[224,321,323],{"class":322},"sZZnC","'meta[name=\"page-template\"]'",[224,325,326],{"class":236},")?.content ",[224,328,329],{"class":240},"||",[224,331,332],{"class":236}," location.pathname,\n",[224,334,336],{"class":177,"line":335},7,[224,337,338],{"class":236},"      url: location.pathname,\n",[224,340,342,345,348,351,354,357,360,363],{"class":177,"line":341},8,[224,343,344],{"class":236},"      ref: document.referrer ",[224,346,347],{"class":240},"?",[224,349,350],{"class":240}," new",[224,352,353],{"class":253}," URL",[224,355,356],{"class":236},"(document.referrer).hostname ",[224,358,359],{"class":240},":",[224,361,362],{"class":322}," ''",[224,364,365],{"class":236},",\n",[224,367,369,372,375,378,381,384,386,389,391,394],{"class":177,"line":368},9,[224,370,371],{"class":236},"      vw: Math.",[224,373,374],{"class":253},"round",[224,376,377],{"class":236},"(window.innerWidth ",[224,379,380],{"class":240},"\u002F",[224,382,383],{"class":288}," 100",[224,385,273],{"class":236},[224,387,388],{"class":240},"*",[224,390,383],{"class":288},[224,392,393],{"class":236},",   ",[224,395,396],{"class":229},"\u002F\u002F bucketed, not exact\n",[224,398,400,403],{"class":177,"line":399},10,[224,401,402],{"class":240},"      ...",[224,404,405],{"class":236},"extra,\n",[224,407,409],{"class":177,"line":408},11,[224,410,411],{"class":236},"    });\n",[224,413,415,418,421,423,426],{"class":177,"line":414},12,[224,416,417],{"class":236},"    navigator.",[224,419,420],{"class":253},"sendBeacon",[224,422,237],{"class":236},[224,424,425],{"class":322},"'\u002Fe'",[224,427,428],{"class":236},", body);\n",[224,430,432],{"class":177,"line":431},13,[224,433,434],{"class":236},"  };\n",[224,436,438],{"class":177,"line":437},14,[224,439,441],{"emptyLinePlaceholder":440},true,"\n",[224,443,445,448,450,453,456,458,460,462,465],{"class":177,"line":444},15,[224,446,447],{"class":253},"  addEventListener",[224,449,237],{"class":236},[224,451,452],{"class":322},"'DOMContentLoaded'",[224,454,455],{"class":236},", () ",[224,457,276],{"class":240},[224,459,254],{"class":253},[224,461,237],{"class":236},[224,463,464],{"class":322},"'view'",[224,466,467],{"class":236},"));\n",[224,469,471,473,475,478,480,482],{"class":177,"line":470},16,[224,472,447],{"class":253},[224,474,237],{"class":236},[224,476,477],{"class":322},"'visibilitychange'",[224,479,455],{"class":236},[224,481,276],{"class":240},[224,483,279],{"class":236},[224,485,487,490,493,496,499],{"class":177,"line":486},17,[224,488,489],{"class":240},"    if",[224,491,492],{"class":236}," (document.visibilityState ",[224,494,495],{"class":240},"===",[224,497,498],{"class":322}," 'hidden'",[224,500,501],{"class":236},") {\n",[224,503,505,508,510,513,516,518,521,524],{"class":177,"line":504},18,[224,506,507],{"class":253},"      send",[224,509,237],{"class":236},[224,511,512],{"class":322},"'leave'",[224,514,515],{"class":236},", { ms: Math.",[224,517,374],{"class":253},[224,519,520],{"class":236},"(performance.",[224,522,523],{"class":253},"now",[224,525,526],{"class":236},"()) });\n",[224,528,530],{"class":177,"line":529},19,[224,531,532],{"class":236},"    }\n",[224,534,536,539,542],{"class":177,"line":535},20,[224,537,538],{"class":236},"  }, { once: ",[224,540,541],{"class":288},"true",[224,543,544],{"class":236}," });\n",[224,546,548],{"class":177,"line":547},21,[224,549,550],{"class":236},"})();\n",[14,552,553],{},"Note what is missing: no cookie, no local storage, no device identifier, no exact viewport. Bucketing the viewport to the nearest hundred pixels keeps the device-class signal while removing a fingerprinting vector.",[14,555,556,557,560,561,25],{},"Load it with ",[221,558,559],{},"defer"," from your own origin, and give the file a hashed name so you can cache it immutably — the same policy the rest of your assets use, as described in ",[21,562,564],{"href":563},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002F","CDN Caching Rules for SSGs",[206,566,568],{"id":567},"_2-the-edge-endpoint","2. The edge endpoint",[214,570,572],{"className":216,"code":571,"language":218,"meta":219,"style":219},"\u002F\u002F functions\u002Fe.js — Cloudflare Pages Function\nexport async function onRequestPost({ request, env }) {\n  const data = await request.json().catch(() => null);\n  if (!data || typeof data.type !== 'string') return new Response(null, { status: 400 });\n\n  const ua = request.headers.get('user-agent') || '';\n  if (\u002Fbot|crawler|spider|preview|headless\u002Fi.test(ua)) return new Response(null, { status: 204 });\n\n  await env.EVENTS.writeDataPoint({\n    blobs: [data.type, data.path, data.ref || '', request.cf?.country || ''],\n    doubles: [data.ms || 0, data.vw || 0],\n    indexes: [data.path],\n  });\n  return new Response(null, { status: 204 });\n}\n",[221,573,574,579,607,642,692,696,725,788,792,810,829,848,853,858,877],{"__ignoreMap":219},[224,575,576],{"class":177,"line":226},[224,577,578],{"class":229},"\u002F\u002F functions\u002Fe.js — Cloudflare Pages Function\n",[224,580,581,584,587,590,593,596,599,601,604],{"class":177,"line":233},[224,582,583],{"class":240},"export",[224,585,586],{"class":240}," async",[224,588,589],{"class":240}," function",[224,591,592],{"class":253}," onRequestPost",[224,594,595],{"class":236},"({ ",[224,597,598],{"class":263},"request",[224,600,267],{"class":236},[224,602,603],{"class":263},"env",[224,605,606],{"class":236}," }) {\n",[224,608,609,611,614,616,619,622,625,628,631,634,636,639],{"class":177,"line":247},[224,610,250],{"class":240},[224,612,613],{"class":288}," data",[224,615,257],{"class":240},[224,617,618],{"class":240}," await",[224,620,621],{"class":236}," request.",[224,623,624],{"class":253},"json",[224,626,627],{"class":236},"().",[224,629,630],{"class":253},"catch",[224,632,633],{"class":236},"(() ",[224,635,276],{"class":240},[224,637,638],{"class":288}," null",[224,640,641],{"class":236},");\n",[224,643,644,647,649,652,655,657,660,663,666,669,671,674,676,679,681,684,687,690],{"class":177,"line":282},[224,645,646],{"class":240},"  if",[224,648,260],{"class":236},[224,650,651],{"class":240},"!",[224,653,654],{"class":236},"data ",[224,656,329],{"class":240},[224,658,659],{"class":240}," typeof",[224,661,662],{"class":236}," data.type ",[224,664,665],{"class":240},"!==",[224,667,668],{"class":322}," 'string'",[224,670,273],{"class":236},[224,672,673],{"class":240},"return",[224,675,350],{"class":240},[224,677,678],{"class":253}," Response",[224,680,237],{"class":236},[224,682,683],{"class":288},"null",[224,685,686],{"class":236},", { status: ",[224,688,689],{"class":288},"400",[224,691,544],{"class":236},[224,693,694],{"class":177,"line":305},[224,695,441],{"emptyLinePlaceholder":440},[224,697,698,700,703,705,708,711,713,716,718,720,722],{"class":177,"line":311},[224,699,250],{"class":240},[224,701,702],{"class":288}," ua",[224,704,257],{"class":240},[224,706,707],{"class":236}," request.headers.",[224,709,710],{"class":253},"get",[224,712,237],{"class":236},[224,714,715],{"class":322},"'user-agent'",[224,717,273],{"class":236},[224,719,329],{"class":240},[224,721,362],{"class":322},[224,723,724],{"class":236},";\n",[224,726,727,729,731,733,737,740,743,745,748,750,753,755,758,760,763,765,768,771,773,775,777,779,781,783,786],{"class":177,"line":335},[224,728,646],{"class":240},[224,730,260],{"class":236},[224,732,380],{"class":322},[224,734,736],{"class":735},"sA_wV","bot",[224,738,739],{"class":240},"|",[224,741,742],{"class":735},"crawler",[224,744,739],{"class":240},[224,746,747],{"class":735},"spider",[224,749,739],{"class":240},[224,751,752],{"class":735},"preview",[224,754,739],{"class":240},[224,756,757],{"class":735},"headless",[224,759,380],{"class":322},[224,761,762],{"class":240},"i",[224,764,25],{"class":236},[224,766,767],{"class":253},"test",[224,769,770],{"class":236},"(ua)) ",[224,772,673],{"class":240},[224,774,350],{"class":240},[224,776,678],{"class":253},[224,778,237],{"class":236},[224,780,683],{"class":288},[224,782,686],{"class":236},[224,784,785],{"class":288},"204",[224,787,544],{"class":236},[224,789,790],{"class":177,"line":341},[224,791,441],{"emptyLinePlaceholder":440},[224,793,794,797,800,803,805,808],{"class":177,"line":368},[224,795,796],{"class":240},"  await",[224,798,799],{"class":236}," env.",[224,801,802],{"class":288},"EVENTS",[224,804,25],{"class":236},[224,806,807],{"class":253},"writeDataPoint",[224,809,302],{"class":236},[224,811,812,815,817,819,822,824,826],{"class":177,"line":399},[224,813,814],{"class":236},"    blobs: [data.type, data.path, data.ref ",[224,816,329],{"class":240},[224,818,362],{"class":322},[224,820,821],{"class":236},", request.cf?.country ",[224,823,329],{"class":240},[224,825,362],{"class":322},[224,827,828],{"class":236},"],\n",[224,830,831,834,836,839,842,844,846],{"class":177,"line":408},[224,832,833],{"class":236},"    doubles: [data.ms ",[224,835,329],{"class":240},[224,837,838],{"class":288}," 0",[224,840,841],{"class":236},", data.vw ",[224,843,329],{"class":240},[224,845,838],{"class":288},[224,847,828],{"class":236},[224,849,850],{"class":177,"line":414},[224,851,852],{"class":236},"    indexes: [data.path],\n",[224,854,855],{"class":177,"line":431},[224,856,857],{"class":236},"  });\n",[224,859,860,863,865,867,869,871,873,875],{"class":177,"line":437},[224,861,862],{"class":240},"  return",[224,864,350],{"class":240},[224,866,678],{"class":253},[224,868,237],{"class":236},[224,870,683],{"class":288},[224,872,686],{"class":236},[224,874,785],{"class":288},[224,876,544],{"class":236},[224,878,879],{"class":177,"line":444},[224,880,881],{"class":236},"}\n",[14,883,884],{},"Return 204 with an empty body — the page is often already unloading and nothing consumes the response. Reject malformed payloads instead of storing them: a public endpoint receives junk within hours of going live.",[206,886,888],{"id":887},"_3-add-vitals-to-the-same-beacon","3. Add vitals to the same beacon",[14,890,891,892,895,896,900],{},"The ",[221,893,894],{},"web-vitals"," library reports LCP, CLS and INP through the same channel, so one endpoint covers both traffic and performance. The collection details are in ",[21,897,899],{"href":898},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js\u002F","Measuring CLS in the Field With web-vitals.js","; the important part here is that self-hosting the library removes the last third-party connection from the measurement path.",[206,902,904],{"id":903},"_4-query-what-you-will-actually-read","4. Query what you will actually read",[214,906,910],{"className":907,"code":908,"language":909,"meta":219,"style":219},"language-sql shiki shiki-themes github-light github-dark","SELECT blob2 AS template, count() AS views, avg(double1) \u002F 1000 AS avg_seconds\nFROM events\nWHERE blob1 = 'view' AND timestamp > now() - INTERVAL 7 DAY\nGROUP BY template ORDER BY views DESC LIMIT 20;\n","sql",[221,911,912,917,922,927],{"__ignoreMap":219},[224,913,914],{"class":177,"line":226},[224,915,916],{},"SELECT blob2 AS template, count() AS views, avg(double1) \u002F 1000 AS avg_seconds\n",[224,918,919],{"class":177,"line":233},[224,920,921],{},"FROM events\n",[224,923,924],{"class":177,"line":247},[224,925,926],{},"WHERE blob1 = 'view' AND timestamp > now() - INTERVAL 7 DAY\n",[224,928,929],{"class":177,"line":282},[224,930,931],{},"GROUP BY template ORDER BY views DESC LIMIT 20;\n",[14,933,934],{},"Resist rebuilding a product analytics suite. Two queries — top pages and top referrers — plus the vitals dashboard cover the decisions a content team makes weekly.",[27,936,938],{"id":937},"decide-the-schema-before-you-ship-it","Decide the Schema Before You Ship It",[14,940,941],{},"The temptation with a collector you own is to log everything \"just in case\". Resist it: every extra field is a privacy question you now have to answer, a column you have to migrate, and a reason for the endpoint to fall inside consent scope. Decide up front which side of the line each field sits on.",[49,943,944,1033],{},[52,945,60,949,60,952,60,955,60,957],{"viewBox":54,"role":55,"ariaLabelledBy":946,"xmlns":59},[947,948],"sha-schema-title","sha-schema-desc",[62,950,951],{"id":947},"Fields to collect and fields to skip",[66,953,954],{"id":948},"Two columns. The collect column lists template name, path, referrer hostname, country, bucketed viewport width, time on page and Core Web Vitals. The skip column lists full IP address, user identifier, cookie or local storage value, exact viewport, full referrer URL with query string, and mouse movement.",[70,956],{"x":72,"y":72,"width":73,"height":74,"fill":75},[77,958,80,959,80,963,80,969,80,975,80,981,80,985,80,989,80,992,80,995,80,999,80,1003,80,1007,80,1011,80,1015,80,1018,80,1021,80,1024,80,1027,80,1030,60],{"style":79},[82,960,962],{"x":84,"y":961,"fill":86,"style":87},"28","A schema small enough to explain in one sentence",[70,964],{"x":91,"y":965,"width":966,"height":967,"rx":968,"fill":150,"opacity":136,"stroke":144,"style":105},"50","330","212","12",[82,970,974],{"x":971,"y":972,"fill":86,"style":973},"205","76","font-size:13px;font-weight:700;text-anchor:middle","Collect",[82,976,980],{"x":977,"y":978,"fill":86,"style":979},"64","104","font-size:12px","template name — what to group by",[82,982,984],{"x":977,"y":983,"fill":86,"style":979},"128","path — which page",[82,986,988],{"x":977,"y":987,"fill":86,"style":979},"152","referrer hostname only",[82,990,991],{"x":977,"y":114,"fill":86,"style":979},"country from the edge",[82,993,994],{"x":977,"y":168,"fill":86,"style":979},"viewport bucketed to 100 px",[82,996,998],{"x":977,"y":997,"fill":86,"style":979},"224","time on page, rounded",[82,1000,1002],{"x":977,"y":1001,"fill":86,"style":979},"248","LCP, CLS, INP values",[70,1004],{"x":689,"y":965,"width":1005,"height":967,"rx":968,"fill":103,"opacity":1006,"stroke":93,"style":105},"320","0.10",[82,1008,1010],{"x":1009,"y":972,"fill":86,"style":973},"560","Skip",[82,1012,1014],{"x":1013,"y":978,"fill":86,"style":979},"424","full IP address",[82,1016,1017],{"x":1013,"y":983,"fill":86,"style":979},"any user or session identifier",[82,1019,1020],{"x":1013,"y":987,"fill":86,"style":979},"cookies and local storage",[82,1022,1023],{"x":1013,"y":114,"fill":86,"style":979},"exact viewport and screen size",[82,1025,1026],{"x":1013,"y":168,"fill":86,"style":979},"referrer query strings",[82,1028,1029],{"x":1013,"y":997,"fill":86,"style":979},"pointer and scroll traces",[82,1031,1032],{"x":1013,"y":1001,"fill":86,"style":979},"anything you cannot justify",[198,1034,1035],{},"The left column answers every question a content team asks in a weekly review. The right column is what turns an anonymous counter into a system with a compliance surface.",[14,1037,1038,1039,1043,1044,1046,1047,1050],{},"Two of these deserve a note. ",[1040,1041,1042],"strong",{},"Referrer hostname only"," keeps the \"where did readers come from\" answer while dropping query strings that frequently contain search terms or campaign identifiers you did not intend to store. And ",[1040,1045,991],{}," — ",[221,1048,1049],{},"request.cf.country"," or the equivalent header — gives you geography without ever touching the IP address itself, because the platform resolves it before your code runs.",[14,1052,1053],{},"Write the schema down in the repository next to the endpoint. When someone proposes a new field six months from now, the discussion starts from a documented position rather than from whatever is easiest to add.",[27,1055,1057],{"id":1056},"measured-impact","Measured Impact",[14,1059,1060],{},"Measured on a documentation site's article template, mid-tier Android profile, Chrome 4× CPU throttle, fast 3G, median of five runs:",[1062,1063,1064,1086],"table",{},[1065,1066,1067],"thead",{},[1068,1069,1070,1074,1077,1080,1083],"tr",{},[1071,1072,1073],"th",{},"Setup",[1071,1075,1076],{},"Transfer",[1071,1078,1079],{},"Blocking time",[1071,1081,1082],{},"Origins",[1071,1084,1085],{},"INP",[1087,1088,1089,1107,1123],"tbody",{},[1068,1090,1091,1095,1098,1101,1104],{},[1092,1093,1094],"td",{},"Hosted vendor analytics",[1092,1096,1097],{},"96 KB",[1092,1099,1100],{},"340 ms",[1092,1102,1103],{},"3",[1092,1105,1106],{},"210 ms",[1068,1108,1109,1112,1114,1117,1120],{},[1092,1110,1111],{},"Vendor proxied through own domain",[1092,1113,1097],{},[1092,1115,1116],{},"330 ms",[1092,1118,1119],{},"2",[1092,1121,1122],{},"205 ms",[1068,1124,1125,1128,1131,1134,1137],{},[1092,1126,1127],{},"First-party collector + web-vitals",[1092,1129,1130],{},"6 KB",[1092,1132,1133],{},"22 ms",[1092,1135,1136],{},"1",[1092,1138,1139],{},"130 ms",[49,1141,1142,1222],{},[52,1143,60,1148,60,1151,60,1154,60,1157],{"viewBox":1144,"role":55,"ariaLabelledBy":1145,"xmlns":59},"0 0 760 280",[1146,1147],"sha-bars-title","sha-bars-desc",[62,1149,1150],{"id":1146},"Blocking time and transfer by analytics setup",[66,1152,1153],{"id":1147},"Three grouped bars on a shared scale. Hosted vendor analytics uses 340 milliseconds of blocking time and 96 kilobytes. Proxying it saves 10 milliseconds. The first-party collector uses 22 milliseconds and 6 kilobytes, a 94 percent reduction in blocking time.",[70,1155],{"x":72,"y":72,"width":73,"height":1156,"fill":75},"280",[77,1158,80,1159,80,1162,80,1167,80,1171,80,1174,80,1178,80,1184,80,1188,80,1192,80,1197,80,1201,80,1205,80,1209,80,1212,80,1217,60],{"style":79},[82,1160,1161],{"x":84,"y":85,"fill":86,"style":87},"Proxying helps a little; replacing helps a lot",[177,1163],{"x1":1164,"y1":1165,"x2":1164,"y2":1166,"stroke":169,"style":105},"260","46","216",[82,1168,1170],{"x":98,"y":1169,"fill":86,"style":94},"82","Hosted vendor",[82,1172,1173],{"x":98,"y":109,"fill":171,"style":184},"3 origins",[70,1175],{"x":1164,"y":92,"width":1176,"height":101,"rx":1177,"fill":103,"opacity":116,"stroke":93,"style":105},"430","5",[82,1179,1183],{"x":1180,"y":1181,"fill":93,"style":1182},"475","86","font-size:12px;font-weight:700;text-anchor:middle","340 ms · 96 KB",[82,1185,1187],{"x":98,"y":1186,"fill":86,"style":94},"142","Proxied vendor",[82,1189,1191],{"x":98,"y":1190,"fill":171,"style":184},"158","2 origins",[70,1193],{"x":1164,"y":1194,"width":1195,"height":101,"rx":1177,"fill":125,"opacity":1196,"stroke":127,"style":105},"126","418","0.36",[82,1198,1200],{"x":1199,"y":159,"fill":127,"style":1182},"469","330 ms · 96 KB",[82,1202,1204],{"x":98,"y":1203,"fill":86,"style":94},"202","First-party",[82,1206,1208],{"x":98,"y":1207,"fill":171,"style":184},"218","1 origin",[70,1210],{"x":1164,"y":163,"width":961,"height":101,"rx":1177,"fill":150,"opacity":1211,"stroke":144,"style":105},"0.28",[82,1213,1216],{"x":1214,"y":1215,"fill":144,"style":94},"310","206","22 ms · 6 KB",[82,1218,1221],{"x":1219,"y":1220,"fill":171,"style":184},"243","252","Shared linear scale · blocking time on a mid-tier Android profile, median of 5 runs",[198,1223,1224],{},"Proxying removes the handshake but keeps the vendor's execution cost. Only replacing the script removes both, which is why the third bar is a different order of magnitude.",[14,1226,1227],{},"The INP row is the one that changed reader experience: 210 ms to 130 ms moved the template from \"needs improvement\" into the good band without touching a line of the site's own JavaScript.",[27,1229,1231],{"id":1230},"pitfalls-rollback","Pitfalls & Rollback",[32,1233,1234,1240,1246,1252,1258,1264],{},[35,1235,1236,1239],{},[1040,1237,1238],{},"Rebuilding the vendor."," Sessionisation, funnels and cohorts are where the complexity lives. If you genuinely need them, keep a vendor and gate it behind interaction instead.",[35,1241,1242,1245],{},[1040,1243,1244],{},"Logging full IP addresses."," Truncate or drop at the edge. Raw addresses turn an anonymous counter into personal data and pull the whole endpoint into consent scope.",[35,1247,1248,1251],{},[1040,1249,1250],{},"Forgetting bots."," Crawler traffic can be a third of raw page views on a public docs site. Filter at the edge, before the write.",[35,1253,1254,1257],{},[1040,1255,1256],{},"An unversioned client script."," Cache it immutably with a hashed filename; a long-cached mutable file is how you end up with two versions of the schema in the same dataset.",[35,1259,1260,1263],{},[1040,1261,1262],{},"No back-pressure."," A viral page can multiply event volume overnight. Confirm your store's write limits and add sampling before you need it, not after.",[35,1265,1266,1269],{},[1040,1267,1268],{},"Rollback:"," the collector is one script tag and one function. Remove the tag and collection stops; the vendor tag, if you kept it in a branch, can go back in the same deploy.",[27,1271,1273],{"id":1272},"conclusion","Conclusion",[14,1275,1276,1277,25],{},"For a static content site, first-party measurement is both faster and simpler than the hosted alternative: one connection instead of three, six kilobytes instead of ninety-six, and a schema you control. Ship the small client script, receive beacons at the edge, put vitals through the same pipe, and keep the queries to the handful you actually read each week. Then apply the same scrutiny to whatever third parties remain, using the tiering and budgets in ",[21,1278,24],{"href":23},[27,1280,1282],{"id":1281},"faq","FAQ",[206,1284,1286],{"id":1285},"does-self-hosting-analytics-avoid-ad-blockers","Does self-hosting analytics avoid ad blockers?",[14,1288,1289],{},"Partly, and that is not the point. Blockers match on known script names and paths as well as domains, so a proxied vendor bundle is often still blocked. A genuinely first-party collector with your own naming is rarely matched, but treat any recovered traffic as a bonus rather than a reason to route around a reader's choice.",[206,1291,1293],{"id":1292},"what-do-i-lose-compared-with-a-hosted-analytics-product","What do I lose compared with a hosted analytics product?",[14,1295,1296],{},"Sessionisation, funnels, cohort analysis and a polished dashboard. What you keep is page views, referrers, device class, and Core Web Vitals per template, which is what most content teams actually act on. Start there and add only what you find yourself missing.",[206,1298,1300],{"id":1299},"does-a-first-party-collector-need-a-cookie-banner","Does a first-party collector need a cookie banner?",[14,1302,1303],{},"If it stores nothing on the device and collects no identifier, most regimes treat it as out of scope, but this depends on your jurisdiction and what you log. Keep the payload anonymous, avoid cookies and local storage, truncate IP addresses at the edge, and confirm the position with whoever owns privacy compliance.",[206,1305,1307],{"id":1306},"how-do-i-handle-bots","How do I handle bots?",[14,1309,1310],{},"Filter at the edge on user agent and on requests that arrive without a matching page view, and drop anything from known crawler ranges. Bot traffic inflates page-view counts far more than it inflates vitals, so filtering matters most for the counting side.",[206,1312,1314],{"id":1313},"can-i-keep-my-existing-vendor-and-still-do-this","Can I keep my existing vendor and still do this?",[14,1316,1317],{},"Yes, and it is a sensible transition. Run the first-party collector alongside the vendor for a month, compare the numbers, and only then decide whether the vendor is still earning its 96 kilobytes and its connection.",[27,1319,1321],{"id":1320},"related","Related",[32,1323,1324,1333,1340,1345,1350],{},[35,1325,1326,1329,1330,1332],{},[1040,1327,1328],{},"Parent:"," ",[21,1331,24],{"href":23}," — budgets and tiers for everything else.",[35,1334,1335,1339],{},[21,1336,1338],{"href":1337},"\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"," — find what to replace next.",[35,1341,1342,1344],{},[21,1343,899],{"href":898}," — the vitals half of the same pipeline.",[35,1346,1347,1349],{},[21,1348,564],{"href":563}," — cache the client script immutably.",[35,1351,1352,1356],{},[21,1353,1355],{"href":1354},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fsetting-cache-control-headers-on-cloudflare-pages\u002F","Setting Cache-Control Headers on Cloudflare Pages"," — the header syntax for the same host that runs the endpoint.",[1358,1359,1360],"style",{},"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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":219,"searchDepth":233,"depth":233,"links":1362},[1363,1364,1365,1371,1372,1373,1374,1375,1382],{"id":29,"depth":233,"text":30},{"id":46,"depth":233,"text":47},{"id":203,"depth":233,"text":204,"children":1366},[1367,1368,1369,1370],{"id":208,"depth":247,"text":209},{"id":567,"depth":247,"text":568},{"id":887,"depth":247,"text":888},{"id":903,"depth":247,"text":904},{"id":937,"depth":233,"text":938},{"id":1056,"depth":233,"text":1057},{"id":1230,"depth":233,"text":1231},{"id":1272,"depth":233,"text":1273},{"id":1281,"depth":233,"text":1282,"children":1376},[1377,1378,1379,1380,1381],{"id":1285,"depth":247,"text":1286},{"id":1292,"depth":247,"text":1293},{"id":1299,"depth":247,"text":1300},{"id":1306,"depth":247,"text":1307},{"id":1313,"depth":247,"text":1314},{"id":1320,"depth":233,"text":1321},[1384,1386,1389,1390],{"name":1385,"item":380},"Home",{"name":1387,"item":1388},"Performance Optimization & Core Web Vitals for SSGs","\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002F",{"name":24,"item":23},{"name":5,"item":1391},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests\u002F","2026-08-01","Replace a vendor analytics connection with a first-party script and an edge endpoint: no extra DNS or TLS, no consent-blocking dependency, and 6 KB instead of 96 KB.","md",[1396,1397,1398,1399,1400],{"q":1286,"a":1289},{"q":1293,"a":1296},{"q":1300,"a":1303},{"q":1307,"a":1310},{"q":1314,"a":1317},{},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests",{"title":5,"description":1393},"performance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests\u002Findex","article","8jcHTuTOWl5QI1oTeIDCLJDv2GNkmuLgdMCTfuhmk2M",[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,1483,1486,1488,1491,1494,1497,1500,1503,1506,1508,1511,1514,1517,1520,1523,1526,1529,1532,1535,1538,1541,1544,1547,1550,1553,1556,1559,1561,1563,1566,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648],{"path":1409,"title":1410},"\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":1412,"title":1413},"\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":1415,"title":1416},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1418,"title":1419},"\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":1421,"title":1422},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1424,"title":1425},"\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":1427,"title":1428},"\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":1430,"title":1431},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":1433,"title":1434},"\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":1436,"title":1437},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":1439,"title":1440},"\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":1442,"title":1443},"\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":1445,"title":1446},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":1448,"title":1449},"\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":1451,"title":1452},"\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":1454,"title":1455},"\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":1457,"title":1458},"\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":1460,"title":1461},"\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":1463,"title":1464},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1466,"title":1467},"\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":1469,"title":1470},"\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":1472,"title":1473},"\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":1475,"title":1476},"\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":1478,"title":1479},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":1481,"title":1482},"\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":1484,"title":1485},"\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":1487,"title":564},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs",{"path":1489,"title":1490},"\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":1492,"title":1493},"\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":1495,"title":1496},"\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":1498,"title":1499},"\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":1501,"title":1502},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-late-loading-embeds","Fixing CLS From Late-Loading Embeds",{"path":1504,"title":1505},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":1507,"title":899},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js",{"path":1509,"title":1510},"\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":1512,"title":1513},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":1515,"title":1516},"\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":1518,"title":1519},"\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":1521,"title":1522},"\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":1524,"title":1525},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":1527,"title":1528},"\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":1530,"title":1531},"\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":1533,"title":1534},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":1536,"title":1537},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":1539,"title":1540},"\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":1542,"title":1543},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":1545,"title":1546},"\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":1548,"title":1549},"\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":1551,"title":1552},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":1554,"title":1555},"\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":1557,"title":1558},"\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":1560,"title":1338},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fauditing-third-party-scripts-with-lighthouse",{"path":1562,"title":24},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites",{"path":1564,"title":1565},"\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":1402,"title":5},{"path":1568,"title":1569},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":1571,"title":1572},"\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":1574,"title":1575},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":1577,"title":1578},"\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":1580,"title":1581},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":1583,"title":1584},"\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":1586,"title":1587},"\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":1589,"title":1590},"\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":1592,"title":1593},"\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":1595,"title":1596},"\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":1598,"title":1599},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":1601,"title":1602},"\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":1604,"title":1605},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":1607,"title":1608},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":1610,"title":1611},"\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":1613,"title":1614},"\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":1616,"title":1617},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":1619,"title":1620},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":1622,"title":1623},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":1625,"title":1626},"\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":1628,"title":1629},"\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":1631,"title":1632},"\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":1634,"title":1635},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":1637,"title":1638},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":1640,"title":1641},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":1643,"title":1644},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":1646,"title":1647},"\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":1649,"title":1650},"\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",1785611671575]