[{"data":1,"prerenderedAt":2268},["ShallowReactive",2],{"page:\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fproxying-third-party-apis-from-an-edge-function":3,"all-docs-nav":1726},{"id":4,"title":5,"body":6,"breadcrumb":1702,"dateModified":1712,"datePublished":1712,"description":1713,"extension":1714,"faq":1715,"meta":1720,"navigation":121,"path":1721,"seo":1722,"slug":12,"stem":1723,"type":1724,"__hash__":1725},"content\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fproxying-third-party-apis-from-an-edge-function\u002Findex.md","Proxying Third-Party APIs from an Edge Function",{"type":7,"value":8,"toc":1684},"minimark",[9,13,17,26,31,44,48,51,740,743,919,923,926,1270,1281,1285,1288,1374,1378,1381,1468,1528,1531,1535,1543,1550,1554,1561,1565,1597,1601,1604,1608,1613,1616,1620,1623,1627,1630,1634,1637,1641,1680],[10,11,5],"h1",{"id":12},"proxying-third-party-apis-from-an-edge-function",[14,15,16],"p",{},"Static pages often want a little live data: the latest release from GitHub, current service status, a product's stock level, search results from a hosted index with a private key, weather for an event page. Fetching it directly from the browser seems simplest and fails in four ways. The API key is visible to anyone who opens DevTools. The API may not allow cross-origin requests. Every reader shares one rate limit. And the response usually carries kilobytes of fields the page ignores.",[14,18,19,20,25],{},"An edge function between the page and the API fixes all four. It holds the key server-side, calls exactly one upstream endpoint per route, returns only the fields the page uses, caches the result at the edge, and falls back gracefully when the upstream is slow or down. This guide builds that proxy on Cloudflare Workers for a docs site's release widget and status banner. It is part of ",[21,22,24],"a",{"href":23},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002F","Serverless Functions for Static Sites",".",[27,28,30],"h2",{"id":29},"prerequisites","Prerequisites",[32,33,34,38,41],"ul",{},[35,36,37],"li",{},"A static site on a host with edge functions (the pattern is the same on Netlify Edge Functions and Vercel Edge).",[35,39,40],{},"The upstream API's key, stored as a secret, never in the repository.",[35,42,43],{},"A clear list of the data each page needs — the proxy should return nothing more.",[27,45,47],{"id":46},"step-1-one-route-one-upstream-request","Step 1: One Route, One Upstream Request",[14,49,50],{},"The core rule: each proxy route maps to a single, fixed upstream request. Parameters the reader may vary are validated against an allow-list; everything else is hard-coded.",[52,53,58],"pre",{"className":54,"code":55,"language":56,"meta":57,"style":57},"language-js shiki shiki-themes github-light github-dark","\u002F\u002F functions\u002Fapi\u002Freleases\u002F[repo].js\nconst REPOS = new Set(['cli', 'sdk-js', 'sdk-python']);   \u002F\u002F the only repos this site shows\n\nexport async function onRequestGet({ params, env, waitUntil }) {\n  const repo = params.repo;\n  if (!REPOS.has(repo)) return new Response('Not found', { status: 404 });\n\n  const cacheKey = new Request(`https:\u002F\u002Fproxy.cache\u002Freleases\u002F${repo}`);\n  const cache = caches.default;\n  const cached = await cache.match(cacheKey);\n  if (cached) return cached;\n\n  let body;\n  try {\n    const up = await fetch(`https:\u002F\u002Fapi.github.com\u002Frepos\u002Facme\u002F${repo}\u002Freleases?per_page=3`, {\n      headers: { authorization: `Bearer ${env.GH_TOKEN}`, accept: 'application\u002Fvnd.github+json', 'user-agent': 'acme-docs' },\n      signal: AbortSignal.timeout(3000),\n    });\n    if (!up.ok) throw new Error(`upstream ${up.status}`);\n    body = (await up.json()).map((r) => ({ tag: r.tag_name, date: r.published_at.slice(0, 10), url: r.html_url }));\n  } catch {\n    const stale = await cache.match(new Request(`https:\u002F\u002Fproxy.cache\u002Freleases\u002F${repo}?stale`));\n    return stale ?? Response.json([], { status: 200, headers: { 'cache-control': 'no-store', 'x-proxy': 'fallback' } });\n  }\n\n  const res = Response.json(body, { headers: { 'cache-control': 'public, max-age=300, stale-while-revalidate=3600' } });\n  waitUntil(cache.put(cacheKey, res.clone()));\n  waitUntil(cache.put(new Request(`https:\u002F\u002Fproxy.cache\u002Freleases\u002F${repo}?stale`),\n    new Response(JSON.stringify(body), { headers: { 'content-type': 'application\u002Fjson', 'cache-control': 'public, max-age=604800' } })));\n  return res;\n}\n","js","",[59,60,61,70,116,123,158,172,218,223,252,265,287,300,305,314,323,352,390,407,413,451,508,519,554,601,607,612,638,659,684,725,734],"code",{"__ignoreMap":57},[62,63,66],"span",{"class":64,"line":65},"line",1,[62,67,69],{"class":68},"sJ8bj","\u002F\u002F functions\u002Fapi\u002Freleases\u002F[repo].js\n",[62,71,73,77,81,84,87,91,95,99,102,105,107,110,113],{"class":64,"line":72},2,[62,74,76],{"class":75},"szBVR","const",[62,78,80],{"class":79},"sj4cs"," REPOS",[62,82,83],{"class":75}," =",[62,85,86],{"class":75}," new",[62,88,90],{"class":89},"sScJk"," Set",[62,92,94],{"class":93},"sVt8B","([",[62,96,98],{"class":97},"sZZnC","'cli'",[62,100,101],{"class":93},", ",[62,103,104],{"class":97},"'sdk-js'",[62,106,101],{"class":93},[62,108,109],{"class":97},"'sdk-python'",[62,111,112],{"class":93},"]);   ",[62,114,115],{"class":68},"\u002F\u002F the only repos this site shows\n",[62,117,119],{"class":64,"line":118},3,[62,120,122],{"emptyLinePlaceholder":121},true,"\n",[62,124,126,129,132,135,138,141,145,147,150,152,155],{"class":64,"line":125},4,[62,127,128],{"class":75},"export",[62,130,131],{"class":75}," async",[62,133,134],{"class":75}," function",[62,136,137],{"class":89}," onRequestGet",[62,139,140],{"class":93},"({ ",[62,142,144],{"class":143},"s4XuR","params",[62,146,101],{"class":93},[62,148,149],{"class":143},"env",[62,151,101],{"class":93},[62,153,154],{"class":143},"waitUntil",[62,156,157],{"class":93}," }) {\n",[62,159,161,164,167,169],{"class":64,"line":160},5,[62,162,163],{"class":75},"  const",[62,165,166],{"class":79}," repo",[62,168,83],{"class":75},[62,170,171],{"class":93}," params.repo;\n",[62,173,175,178,181,184,187,189,192,195,198,200,203,206,209,212,215],{"class":64,"line":174},6,[62,176,177],{"class":75},"  if",[62,179,180],{"class":93}," (",[62,182,183],{"class":75},"!",[62,185,186],{"class":79},"REPOS",[62,188,25],{"class":93},[62,190,191],{"class":89},"has",[62,193,194],{"class":93},"(repo)) ",[62,196,197],{"class":75},"return",[62,199,86],{"class":75},[62,201,202],{"class":89}," Response",[62,204,205],{"class":93},"(",[62,207,208],{"class":97},"'Not found'",[62,210,211],{"class":93},", { status: ",[62,213,214],{"class":79},"404",[62,216,217],{"class":93}," });\n",[62,219,221],{"class":64,"line":220},7,[62,222,122],{"emptyLinePlaceholder":121},[62,224,226,228,231,233,235,238,240,243,246,249],{"class":64,"line":225},8,[62,227,163],{"class":75},[62,229,230],{"class":79}," cacheKey",[62,232,83],{"class":75},[62,234,86],{"class":75},[62,236,237],{"class":89}," Request",[62,239,205],{"class":93},[62,241,242],{"class":97},"`https:\u002F\u002Fproxy.cache\u002Freleases\u002F${",[62,244,245],{"class":93},"repo",[62,247,248],{"class":97},"}`",[62,250,251],{"class":93},");\n",[62,253,255,257,260,262],{"class":64,"line":254},9,[62,256,163],{"class":75},[62,258,259],{"class":79}," cache",[62,261,83],{"class":75},[62,263,264],{"class":93}," caches.default;\n",[62,266,268,270,273,275,278,281,284],{"class":64,"line":267},10,[62,269,163],{"class":75},[62,271,272],{"class":79}," cached",[62,274,83],{"class":75},[62,276,277],{"class":75}," await",[62,279,280],{"class":93}," cache.",[62,282,283],{"class":89},"match",[62,285,286],{"class":93},"(cacheKey);\n",[62,288,290,292,295,297],{"class":64,"line":289},11,[62,291,177],{"class":75},[62,293,294],{"class":93}," (cached) ",[62,296,197],{"class":75},[62,298,299],{"class":93}," cached;\n",[62,301,303],{"class":64,"line":302},12,[62,304,122],{"emptyLinePlaceholder":121},[62,306,308,311],{"class":64,"line":307},13,[62,309,310],{"class":75},"  let",[62,312,313],{"class":93}," body;\n",[62,315,317,320],{"class":64,"line":316},14,[62,318,319],{"class":75},"  try",[62,321,322],{"class":93}," {\n",[62,324,326,329,332,334,336,339,341,344,346,349],{"class":64,"line":325},15,[62,327,328],{"class":75},"    const",[62,330,331],{"class":79}," up",[62,333,83],{"class":75},[62,335,277],{"class":75},[62,337,338],{"class":89}," fetch",[62,340,205],{"class":93},[62,342,343],{"class":97},"`https:\u002F\u002Fapi.github.com\u002Frepos\u002Facme\u002F${",[62,345,245],{"class":93},[62,347,348],{"class":97},"}\u002Freleases?per_page=3`",[62,350,351],{"class":93},", {\n",[62,353,355,358,361,363,365,368,370,373,376,378,381,384,387],{"class":64,"line":354},16,[62,356,357],{"class":93},"      headers: { authorization: ",[62,359,360],{"class":97},"`Bearer ${",[62,362,149],{"class":93},[62,364,25],{"class":97},[62,366,367],{"class":79},"GH_TOKEN",[62,369,248],{"class":97},[62,371,372],{"class":93},", accept: ",[62,374,375],{"class":97},"'application\u002Fvnd.github+json'",[62,377,101],{"class":93},[62,379,380],{"class":97},"'user-agent'",[62,382,383],{"class":93},": ",[62,385,386],{"class":97},"'acme-docs'",[62,388,389],{"class":93}," },\n",[62,391,393,396,399,401,404],{"class":64,"line":392},17,[62,394,395],{"class":93},"      signal: AbortSignal.",[62,397,398],{"class":89},"timeout",[62,400,205],{"class":93},[62,402,403],{"class":79},"3000",[62,405,406],{"class":93},"),\n",[62,408,410],{"class":64,"line":409},18,[62,411,412],{"class":93},"    });\n",[62,414,416,419,421,423,426,429,431,434,436,439,442,444,447,449],{"class":64,"line":415},19,[62,417,418],{"class":75},"    if",[62,420,180],{"class":93},[62,422,183],{"class":75},[62,424,425],{"class":93},"up.ok) ",[62,427,428],{"class":75},"throw",[62,430,86],{"class":75},[62,432,433],{"class":89}," Error",[62,435,205],{"class":93},[62,437,438],{"class":97},"`upstream ${",[62,440,441],{"class":93},"up",[62,443,25],{"class":97},[62,445,446],{"class":93},"status",[62,448,248],{"class":97},[62,450,251],{"class":93},[62,452,454,457,460,462,465,468,471,474,477,480,483,486,489,492,495,497,500,502,505],{"class":64,"line":453},20,[62,455,456],{"class":93},"    body ",[62,458,459],{"class":75},"=",[62,461,180],{"class":93},[62,463,464],{"class":75},"await",[62,466,467],{"class":93}," up.",[62,469,470],{"class":89},"json",[62,472,473],{"class":93},"()).",[62,475,476],{"class":89},"map",[62,478,479],{"class":93},"((",[62,481,482],{"class":143},"r",[62,484,485],{"class":93},") ",[62,487,488],{"class":75},"=>",[62,490,491],{"class":93}," ({ tag: r.tag_name, date: r.published_at.",[62,493,494],{"class":89},"slice",[62,496,205],{"class":93},[62,498,499],{"class":79},"0",[62,501,101],{"class":93},[62,503,504],{"class":79},"10",[62,506,507],{"class":93},"), url: r.html_url }));\n",[62,509,511,514,517],{"class":64,"line":510},21,[62,512,513],{"class":93},"  } ",[62,515,516],{"class":75},"catch",[62,518,322],{"class":93},[62,520,522,524,527,529,531,533,535,537,540,542,544,546,548,551],{"class":64,"line":521},22,[62,523,328],{"class":75},[62,525,526],{"class":79}," stale",[62,528,83],{"class":75},[62,530,277],{"class":75},[62,532,280],{"class":93},[62,534,283],{"class":89},[62,536,205],{"class":93},[62,538,539],{"class":75},"new",[62,541,237],{"class":89},[62,543,205],{"class":93},[62,545,242],{"class":97},[62,547,245],{"class":93},[62,549,550],{"class":97},"}?stale`",[62,552,553],{"class":93},"));\n",[62,555,557,560,563,566,569,571,574,577,580,583,585,588,590,593,595,598],{"class":64,"line":556},23,[62,558,559],{"class":75},"    return",[62,561,562],{"class":93}," stale ",[62,564,565],{"class":75},"??",[62,567,568],{"class":93}," Response.",[62,570,470],{"class":89},[62,572,573],{"class":93},"([], { status: ",[62,575,576],{"class":79},"200",[62,578,579],{"class":93},", headers: { ",[62,581,582],{"class":97},"'cache-control'",[62,584,383],{"class":93},[62,586,587],{"class":97},"'no-store'",[62,589,101],{"class":93},[62,591,592],{"class":97},"'x-proxy'",[62,594,383],{"class":93},[62,596,597],{"class":97},"'fallback'",[62,599,600],{"class":93}," } });\n",[62,602,604],{"class":64,"line":603},24,[62,605,606],{"class":93},"  }\n",[62,608,610],{"class":64,"line":609},25,[62,611,122],{"emptyLinePlaceholder":121},[62,613,615,617,620,622,624,626,629,631,633,636],{"class":64,"line":614},26,[62,616,163],{"class":75},[62,618,619],{"class":79}," res",[62,621,83],{"class":75},[62,623,568],{"class":93},[62,625,470],{"class":89},[62,627,628],{"class":93},"(body, { headers: { ",[62,630,582],{"class":97},[62,632,383],{"class":93},[62,634,635],{"class":97},"'public, max-age=300, stale-while-revalidate=3600'",[62,637,600],{"class":93},[62,639,641,644,647,650,653,656],{"class":64,"line":640},27,[62,642,643],{"class":89},"  waitUntil",[62,645,646],{"class":93},"(cache.",[62,648,649],{"class":89},"put",[62,651,652],{"class":93},"(cacheKey, res.",[62,654,655],{"class":89},"clone",[62,657,658],{"class":93},"()));\n",[62,660,662,664,666,668,670,672,674,676,678,680,682],{"class":64,"line":661},28,[62,663,643],{"class":89},[62,665,646],{"class":93},[62,667,649],{"class":89},[62,669,205],{"class":93},[62,671,539],{"class":75},[62,673,237],{"class":89},[62,675,205],{"class":93},[62,677,242],{"class":97},[62,679,245],{"class":93},[62,681,550],{"class":97},[62,683,406],{"class":93},[62,685,687,690,692,694,697,699,702,705,708,710,713,715,717,719,722],{"class":64,"line":686},29,[62,688,689],{"class":75},"    new",[62,691,202],{"class":89},[62,693,205],{"class":93},[62,695,696],{"class":79},"JSON",[62,698,25],{"class":93},[62,700,701],{"class":89},"stringify",[62,703,704],{"class":93},"(body), { headers: { ",[62,706,707],{"class":97},"'content-type'",[62,709,383],{"class":93},[62,711,712],{"class":97},"'application\u002Fjson'",[62,714,101],{"class":93},[62,716,582],{"class":97},[62,718,383],{"class":93},[62,720,721],{"class":97},"'public, max-age=604800'",[62,723,724],{"class":93}," } })));\n",[62,726,728,731],{"class":64,"line":727},30,[62,729,730],{"class":75},"  return",[62,732,733],{"class":93}," res;\n",[62,735,737],{"class":64,"line":736},31,[62,738,739],{"class":93},"}\n",[14,741,742],{},"The route accepts one parameter from a three-item allow-list. The upstream URL, headers and query are fixed. A second, long-lived cache entry keeps the last good response for a week, served when the upstream fails.",[744,745,746,915],"figure",{},[747,748,755,756,755,760,755,764,755,770,755,900],"svg",{"viewBox":749,"role":750,"ariaLabelledBy":751,"xmlns":754},"0 0 760 290","img",[752,753],"prx-flow-title","prx-flow-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[757,758,759],"title",{"id":752},"Request flow through the proxy",[761,762,763],"desc",{"id":753},"The browser requests \u002Fapi\u002Freleases\u002Fcli. The function checks the parameter against an allow-list, then the edge cache. On a hit it returns immediately. On a miss it calls the GitHub API with the secret key and a 3 second timeout, trims the response to three fields, caches it for five minutes and returns it. If the upstream fails, it serves the last good copy kept for a week, or an empty fallback.",[765,766],"rect",{"x":499,"y":499,"width":767,"height":768,"fill":769},"760","290","#ffffff",[771,772,774,775,774,783,774,792,774,798,774,804,774,809,774,813,774,816,774,822,774,825,774,828,774,834,774,839,774,843,774,846,774,850,774,853,774,859,774,864,774,868,774,890,774,896,755],"g",{"style":773},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[776,777,782],"text",{"x":778,"y":779,"fill":780,"style":781},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","Allow-list, cache, fixed upstream, fallback",[765,784],{"x":785,"y":786,"width":787,"height":788,"rx":504,"fill":789,"stroke":790,"style":791},"20","110","120","56","#f8fafc","#d9e2ef","stroke-width:1.5px",[776,793,797],{"x":794,"y":795,"fill":780,"style":796},"80","134","font-weight:700;text-anchor:middle","browser",[776,799,803],{"x":794,"y":800,"fill":801,"style":802},"152","#556071","font-size:11px;text-anchor:middle","\u002Fapi\u002Freleases\u002Fcli",[765,805],{"x":806,"y":786,"width":787,"height":788,"rx":504,"fill":807,"opacity":808,"stroke":807,"style":791},"170","#6a4c93","0.14",[776,810,812],{"x":811,"y":795,"fill":780,"style":796},"230","allow-list",[776,814,815],{"x":811,"y":800,"fill":801,"style":802},"3 repos only",[765,817],{"x":818,"y":786,"width":787,"height":788,"rx":504,"fill":819,"opacity":820,"stroke":821,"style":791},"320","#8ac926","0.18","#5a8a16",[776,823,824],{"x":778,"y":795,"fill":780,"style":796},"edge cache",[776,826,827],{"x":778,"y":800,"fill":801,"style":802},"hit → return",[765,829],{"x":830,"y":831,"width":832,"height":788,"rx":504,"fill":833,"opacity":808,"stroke":833,"style":791},"470","60","130","#1982c4",[776,835,838],{"x":836,"y":837,"fill":780,"style":796},"535","84","GitHub API",[776,840,842],{"x":836,"y":841,"fill":801,"style":802},"102","secret key, 3 s timeout",[765,844],{"x":845,"y":831,"width":786,"height":788,"rx":504,"fill":819,"opacity":820,"stroke":821,"style":791},"630",[776,847,849],{"x":848,"y":837,"fill":780,"style":796},"685","trim + cache",[776,851,852],{"x":848,"y":841,"fill":801,"style":802},"3 fields, 5 min",[765,854],{"x":830,"y":806,"width":855,"height":788,"rx":504,"fill":856,"opacity":857,"stroke":858,"style":791},"270","#ffca3a","0.24","#a97b00",[776,860,863],{"x":861,"y":862,"fill":780,"style":796},"605","194","upstream fails → last good copy",[776,865,867],{"x":861,"y":866,"fill":801,"style":802},"212","kept 7 days, else empty list",[771,869,872,873,872,878,872,881,872,884,872,887,774],{"stroke":801,"fill":870,"style":871},"none","stroke-width:2px","\n      ",[874,875],"path",{"d":876,"style":877},"M142 138 L166 138","marker-end:url(#prx-arrow)",[874,879],{"d":880,"style":877},"M292 138 L316 138",[874,882],{"d":883,"style":877},"M442 128 L466 94",[874,885],{"d":886,"style":877},"M602 88 L626 88",[874,888],{"d":889,"style":877},"M535 118 L560 166",[776,891,895],{"x":892,"y":893,"fill":801,"style":894},"442","112","font-size:11px","miss",[776,897,899],{"x":778,"y":898,"fill":801,"style":802},"262","The key never leaves the function; the page never waits more than 3 s; the widget never breaks",[901,902,774,903,755],"defs",{},[904,905,872,912,774],"marker",{"id":906,"viewBox":907,"refX":908,"refY":909,"markerWidth":910,"markerHeight":910,"orient":911},"prx-arrow","0 0 10 10","8","5","7","auto-start-reverse",[874,913],{"d":914,"fill":801},"M0 0 L10 5 L0 10 z",[916,917,918],"figcaption",{},"Every branch ends in a fast, well-formed response; none ends in a hung request or an exposed key.",[27,920,922],{"id":921},"step-2-load-it-without-hurting-the-page","Step 2: Load It Without Hurting the Page",[14,924,925],{},"The page stays static. A small script fetches the proxy after load and renders into a reserved space, so the widget never delays LCP and never shifts layout:",[52,927,931],{"className":928,"code":929,"language":930,"meta":57,"style":57},"language-html shiki shiki-themes github-light github-dark","\u003Caside class=\"releases\" style=\"min-height:7.5rem\" data-repo=\"cli\" aria-live=\"polite\">\n  \u003Ch2>Latest releases\u003C\u002Fh2>\n  \u003Cul>\u003Cli>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Facme\u002Fcli\u002Freleases\">See all releases on GitHub\u003C\u002Fa>\u003C\u002Fli>\u003C\u002Ful>\n\u003C\u002Faside>\n\u003Cscript type=\"module\">\n  const el = document.querySelector('.releases');\n  const data = await fetch(`\u002Fapi\u002Freleases\u002F${el.dataset.repo}`).then((r) => r.json()).catch(() => []);\n  if (data.length) el.querySelector('ul').replaceChildren(...data.map((r) => {\n    const li = document.createElement('li');\n    li.innerHTML = `\u003Ca href=\"${r.url}\">${r.tag}\u003C\u002Fa> \u003Ctime>${r.date}\u003C\u002Ftime>`;\n    return li;\n  }));\n\u003C\u002Fscript>\n","html",[59,932,933,977,991,1030,1039,1056,1078,1141,1186,1207,1250,1257,1262],{"__ignoreMap":57},[62,934,935,938,942,945,947,950,953,955,958,961,963,966,969,971,974],{"class":64,"line":65},[62,936,937],{"class":93},"\u003C",[62,939,941],{"class":940},"s9eBZ","aside",[62,943,944],{"class":89}," class",[62,946,459],{"class":93},[62,948,949],{"class":97},"\"releases\"",[62,951,952],{"class":89}," style",[62,954,459],{"class":93},[62,956,957],{"class":97},"\"min-height:7.5rem\"",[62,959,960],{"class":89}," data-repo",[62,962,459],{"class":93},[62,964,965],{"class":97},"\"cli\"",[62,967,968],{"class":89}," aria-live",[62,970,459],{"class":93},[62,972,973],{"class":97},"\"polite\"",[62,975,976],{"class":93},">\n",[62,978,979,982,984,987,989],{"class":64,"line":72},[62,980,981],{"class":93},"  \u003C",[62,983,27],{"class":940},[62,985,986],{"class":93},">Latest releases\u003C\u002F",[62,988,27],{"class":940},[62,990,976],{"class":93},[62,992,993,995,997,1000,1002,1004,1006,1009,1011,1014,1017,1019,1022,1024,1026,1028],{"class":64,"line":118},[62,994,981],{"class":93},[62,996,32],{"class":940},[62,998,999],{"class":93},">\u003C",[62,1001,35],{"class":940},[62,1003,999],{"class":93},[62,1005,21],{"class":940},[62,1007,1008],{"class":89}," href",[62,1010,459],{"class":93},[62,1012,1013],{"class":97},"\"https:\u002F\u002Fgithub.com\u002Facme\u002Fcli\u002Freleases\"",[62,1015,1016],{"class":93},">See all releases on GitHub\u003C\u002F",[62,1018,21],{"class":940},[62,1020,1021],{"class":93},">\u003C\u002F",[62,1023,35],{"class":940},[62,1025,1021],{"class":93},[62,1027,32],{"class":940},[62,1029,976],{"class":93},[62,1031,1032,1035,1037],{"class":64,"line":125},[62,1033,1034],{"class":93},"\u003C\u002F",[62,1036,941],{"class":940},[62,1038,976],{"class":93},[62,1040,1041,1043,1046,1049,1051,1054],{"class":64,"line":160},[62,1042,937],{"class":93},[62,1044,1045],{"class":940},"script",[62,1047,1048],{"class":89}," type",[62,1050,459],{"class":93},[62,1052,1053],{"class":97},"\"module\"",[62,1055,976],{"class":93},[62,1057,1058,1060,1063,1065,1068,1071,1073,1076],{"class":64,"line":174},[62,1059,163],{"class":75},[62,1061,1062],{"class":79}," el",[62,1064,83],{"class":75},[62,1066,1067],{"class":93}," document.",[62,1069,1070],{"class":89},"querySelector",[62,1072,205],{"class":93},[62,1074,1075],{"class":97},"'.releases'",[62,1077,251],{"class":93},[62,1079,1080,1082,1085,1087,1089,1091,1093,1096,1099,1101,1104,1106,1108,1110,1113,1116,1118,1120,1122,1124,1127,1129,1131,1133,1136,1138],{"class":64,"line":220},[62,1081,163],{"class":75},[62,1083,1084],{"class":79}," data",[62,1086,83],{"class":75},[62,1088,277],{"class":75},[62,1090,338],{"class":89},[62,1092,205],{"class":93},[62,1094,1095],{"class":97},"`\u002Fapi\u002Freleases\u002F${",[62,1097,1098],{"class":93},"el",[62,1100,25],{"class":97},[62,1102,1103],{"class":93},"dataset",[62,1105,25],{"class":97},[62,1107,245],{"class":93},[62,1109,248],{"class":97},[62,1111,1112],{"class":93},").",[62,1114,1115],{"class":89},"then",[62,1117,479],{"class":93},[62,1119,482],{"class":143},[62,1121,485],{"class":93},[62,1123,488],{"class":75},[62,1125,1126],{"class":93}," r.",[62,1128,470],{"class":89},[62,1130,473],{"class":93},[62,1132,516],{"class":89},[62,1134,1135],{"class":93},"(() ",[62,1137,488],{"class":75},[62,1139,1140],{"class":93}," []);\n",[62,1142,1143,1145,1148,1151,1154,1156,1158,1161,1163,1166,1168,1171,1174,1176,1178,1180,1182,1184],{"class":64,"line":225},[62,1144,177],{"class":75},[62,1146,1147],{"class":93}," (data.",[62,1149,1150],{"class":79},"length",[62,1152,1153],{"class":93},") el.",[62,1155,1070],{"class":89},[62,1157,205],{"class":93},[62,1159,1160],{"class":97},"'ul'",[62,1162,1112],{"class":93},[62,1164,1165],{"class":89},"replaceChildren",[62,1167,205],{"class":93},[62,1169,1170],{"class":75},"...",[62,1172,1173],{"class":93},"data.",[62,1175,476],{"class":89},[62,1177,479],{"class":93},[62,1179,482],{"class":143},[62,1181,485],{"class":93},[62,1183,488],{"class":75},[62,1185,322],{"class":93},[62,1187,1188,1190,1193,1195,1197,1200,1202,1205],{"class":64,"line":254},[62,1189,328],{"class":75},[62,1191,1192],{"class":79}," li",[62,1194,83],{"class":75},[62,1196,1067],{"class":93},[62,1198,1199],{"class":89},"createElement",[62,1201,205],{"class":93},[62,1203,1204],{"class":97},"'li'",[62,1206,251],{"class":93},[62,1208,1209,1212,1214,1217,1219,1221,1224,1227,1229,1231,1234,1237,1239,1241,1244,1247],{"class":64,"line":267},[62,1210,1211],{"class":93},"    li.innerHTML ",[62,1213,459],{"class":75},[62,1215,1216],{"class":97}," `\u003Ca href=\"${",[62,1218,482],{"class":93},[62,1220,25],{"class":97},[62,1222,1223],{"class":93},"url",[62,1225,1226],{"class":97},"}\">${",[62,1228,482],{"class":93},[62,1230,25],{"class":97},[62,1232,1233],{"class":93},"tag",[62,1235,1236],{"class":97},"}\u003C\u002Fa> \u003Ctime>${",[62,1238,482],{"class":93},[62,1240,25],{"class":97},[62,1242,1243],{"class":93},"date",[62,1245,1246],{"class":97},"}\u003C\u002Ftime>`",[62,1248,1249],{"class":93},";\n",[62,1251,1252,1254],{"class":64,"line":289},[62,1253,559],{"class":75},[62,1255,1256],{"class":93}," li;\n",[62,1258,1259],{"class":64,"line":302},[62,1260,1261],{"class":93},"  }));\n",[62,1263,1264,1266,1268],{"class":64,"line":307},[62,1265,1034],{"class":93},[62,1267,1045],{"class":940},[62,1269,976],{"class":93},[14,1271,1272,1273,1276,1277,25],{},"The static fallback — a link to all releases — is in the HTML, so readers without JavaScript or during an outage still get something useful. The reserved ",[59,1274,1275],{},"min-height"," keeps the sidebar from shifting when the list arrives, the technique from ",[21,1278,1280],{"href":1279},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Freserving-space-for-images-and-embeds-to-stop-layout-shift\u002F","Reserving Space for Images and Embeds to Stop Layout Shift",[27,1282,1284],{"id":1283},"step-3-rate-limits-and-abuse","Step 3: Rate Limits and Abuse",[14,1286,1287],{},"The proxy is a public endpoint that spends your API quota. Three protections keep it from being abused: the allow-list already prevents arbitrary upstream calls; the edge cache means repeated requests do not reach the upstream at all; and a per-IP rate limit on the route (Cloudflare's rate-limiting rules, or a Workers rate-limit binding) stops anyone hammering cache misses by varying parameters. With these in place, upstream calls are bounded by cache lifetime × allowed parameter values, not by traffic: three repos with a five-minute cache can never exceed 36 upstream calls an hour, whatever the page views.",[744,1289,1290,1371],{},[747,1291,755,1296,755,1299,755,1302,755,1305,755,1364],{"viewBox":1292,"role":750,"ariaLabelledBy":1293,"xmlns":754},"0 0 760 240",[1294,1295],"prx-shape-title","prx-shape-desc",[757,1297,1298],{"id":1294},"Shaping the upstream response",[761,1300,1301],{"id":1295},"The GitHub releases response for three releases is about 48 kilobytes with dozens of fields per release including author objects, asset lists and markdown bodies. The proxy keeps three fields per release, tag, date and URL, producing about 300 bytes.",[765,1303],{"x":499,"y":499,"width":767,"height":1304,"fill":769},"240",[771,1306,774,1307,774,1310,774,1318,774,1322,774,1327,774,1331,774,1335,774,1339,774,1342,774,1346,774,1351,774,1355,774,1360,755],{"style":773},[776,1308,1309],{"x":778,"y":779,"fill":780,"style":781},"Return what the page renders, nothing more",[765,1311],{"x":1312,"y":788,"width":1313,"height":1314,"rx":504,"fill":1315,"opacity":1316,"stroke":1317,"style":791},"40","300","150","#ff595e","0.08","#d83b41",[776,1319,1321],{"x":1320,"y":794,"fill":780,"style":796},"190","upstream: ~48 KB",[776,1323,1326],{"x":831,"y":1324,"fill":801,"style":1325},"106","font-size:11px;font-family:ui-monospace, monospace","author{…}, assets[…], body,",[776,1328,1330],{"x":831,"y":1329,"fill":801,"style":1325},"126","reactions{…}, node_id, …",[776,1332,1334],{"x":831,"y":1333,"fill":780,"style":1325},"146","tag_name, published_at,",[776,1336,1338],{"x":831,"y":1337,"fill":780,"style":1325},"166","html_url",[776,1340,1341],{"x":831,"y":1320,"fill":801,"style":894},"~60 fields per release",[874,1343],{"d":1344,"stroke":801,"fill":870,"style":1345},"M345 131 L415 131","stroke-width:2px;marker-end:url(#prx-s-arrow)",[765,1347],{"x":1348,"y":1349,"width":1313,"height":1350,"rx":504,"fill":819,"opacity":808,"stroke":821,"style":791},"420","86","90",[776,1352,1354],{"x":1353,"y":786,"fill":780,"style":796},"570","proxy: ~300 bytes",[776,1356,1359],{"x":1357,"y":1358,"fill":780,"style":1325},"440","136","{ tag, date, url } × 3",[776,1361,1363],{"x":1357,"y":1362,"fill":801,"style":894},"158","no author data, no bodies",[901,1365,774,1366,755],{},[904,1367,872,1369,774],{"id":1368,"viewBox":907,"refX":908,"refY":909,"markerWidth":910,"markerHeight":910,"orient":911},"prx-s-arrow",[874,1370],{"d":914,"fill":801},[916,1372,1373],{},"Shaping is a performance win and a privacy win: fields the page never renders cannot leak from it either.",[27,1375,1377],{"id":1376},"measured-impact","Measured Impact",[14,1379,1380],{},"The docs site previously embedded a GitHub releases badge script and a status-page widget. After replacing both with proxied, trimmed JSON:",[1382,1383,1384,1400],"table",{},[1385,1386,1387],"thead",{},[1388,1389,1390,1394,1397],"tr",{},[1391,1392,1393],"th",{},"Measure",[1391,1395,1396],{},"Before (direct embeds)",[1391,1398,1399],{},"After (edge proxy)",[1401,1402,1403,1413,1424,1435,1446,1457],"tbody",{},[1388,1404,1405,1409,1411],{},[1406,1407,1408],"td",{},"Third-party requests per page",[1406,1410,910],{},[1406,1412,499],{},[1388,1414,1415,1418,1421],{},[1406,1416,1417],{},"Bytes for the two widgets",[1406,1419,1420],{},"184 KB",[1406,1422,1423],{},"1.1 KB",[1388,1425,1426,1429,1432],{},[1406,1427,1428],{},"Sidebar CLS contribution",[1406,1430,1431],{},"0.06",[1406,1433,1434],{},"0.00",[1388,1436,1437,1440,1443],{},[1406,1438,1439],{},"Upstream API calls per month",[1406,1441,1442],{},"~1.2 million (one per page view)",[1406,1444,1445],{},"~9,000",[1388,1447,1448,1451,1454],{},[1406,1449,1450],{},"Widget visible within 1 s of load (RUM)",[1406,1452,1453],{},"71%",[1406,1455,1456],{},"98%",[1388,1458,1459,1462,1465],{},[1406,1460,1461],{},"Widget failures shown to readers during a GitHub incident",[1406,1463,1464],{},"100% of views for 47 min",[1406,1466,1467],{},"0 (stale copy served)",[744,1469,1470,1525],{},[747,1471,755,1476,755,1479,755,1482,755,1485],{"viewBox":1472,"role":750,"ariaLabelledBy":1473,"xmlns":754},"0 0 760 260",[1474,1475],"prx-calls-title","prx-calls-desc",[757,1477,1478],{"id":1474},"Upstream calls per month",[761,1480,1481],{"id":1475},"A comparison on a log scale. Direct embeds made about 1.2 million upstream API calls a month, one per page view. The proxy with a five-minute edge cache made about 9,000, bounded by cache lifetime and the number of allowed repositories rather than by traffic.",[765,1483],{"x":499,"y":499,"width":767,"height":1484,"fill":769},"260",[771,1486,774,1487,774,1490,774,1495,774,1503,774,1509,774,1513,774,1517,774,1521,755],{"style":773},[776,1488,1489],{"x":778,"y":779,"fill":780,"style":781},"Upstream calls follow cache lifetime, not traffic",[776,1491,1494],{"x":1492,"y":1350,"fill":780,"style":1493},"30","font-weight:700","Direct embed",[765,1496],{"x":1320,"y":1497,"width":1498,"height":1499,"rx":1500,"fill":1315,"opacity":1501,"stroke":1317,"style":1502},"70","520","32","4","0.25","stroke-width:1px",[776,1504,1508],{"x":1505,"y":1506,"fill":780,"style":1507},"450","91","text-anchor:middle","~1,200,000 \u002F month (one per view)",[776,1510,1512],{"x":1492,"y":1511,"fill":780,"style":1493},"160","Edge proxy",[765,1514],{"x":1320,"y":1515,"width":831,"height":1499,"rx":1500,"fill":819,"opacity":1516,"stroke":821,"style":1502},"140","0.4",[776,1518,1445],{"x":1484,"y":1519,"fill":1520,"style":1493},"161","#3f6410",[776,1522,1524],{"x":778,"y":1523,"fill":801,"style":802},"222","log scale · ceiling = allowed repos × (60 \u002F cache minutes) × hours",[916,1526,1527],{},"Caching at the proxy turns the upstream rate limit from a traffic problem into a configuration constant.",[14,1529,1530],{},"The outage row is the most persuasive. During a 47-minute GitHub API incident, the old badge showed an error on every page view; the proxy kept serving the last good release list and nobody noticed.",[27,1532,1534],{"id":1533},"build-time-proxy-or-browser","Build Time, Proxy or Browser?",[14,1536,1537,1538,1542],{},"Before writing a proxy, ask whether the data needs to be live at all. Many \"live\" widgets change a few times a month: release lists, contributor counts, a status summary that is green 99.9% of the time. Those can be fetched ",[1539,1540,1541],"strong",{},"at build time"," and baked into the static HTML, then refreshed by a scheduled rebuild — zero requests at runtime, zero JavaScript, perfect caching. The release widget on this site could have worked that way with a daily rebuild; the team chose the proxy because releases sometimes shipped several times a day and the docs linked to them within minutes.",[14,1544,1545,1546,25],{},"A useful rule: if the data can be an hour old, build it in; if it must be minutes old, proxy it with a matching cache lifetime; if it must be seconds old or per-reader, proxy with a very short cache or none, and think hard about whether the page really needs it. Calling the upstream directly from the browser is right only for APIs designed for public, unauthenticated, cross-origin use — and even then a proxy often wins on payload size. The scheduled-rebuild side of this is covered in ",[21,1547,1549],{"href":1548},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fscheduling-content-publication-with-cron-triggered-builds\u002F","Scheduling Content Publication with Cron-Triggered Builds",[27,1551,1553],{"id":1552},"proxying-search-and-other-keyed-apis","Proxying Search and Other Keyed APIs",[14,1555,1556,1557,25],{},"The same pattern applies to APIs where the key is more sensitive. For hosted search with a key that must not be public, the proxy accepts only the query string and a page number, validates both (length, character set, page under 10), forwards to one fixed index, and strips any fields the result page does not render. Cache common queries for a minute; they repeat more than expected. For write APIs — adding an email to a mailing list — never cache, always rate-limit tightly, and verify a Turnstile token first, exactly as in ",[21,1558,1560],{"href":1559},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fadding-a-contact-form-with-cloudflare-workers\u002F","Adding a Contact Form with Cloudflare Workers",[27,1562,1564],{"id":1563},"pitfalls-rollback","Pitfalls & Rollback",[32,1566,1567,1573,1579,1585,1591],{},[35,1568,1569,1572],{},[1539,1570,1571],{},"Open proxies."," Forwarding any path or query with your key attached hands your key to everyone. Fix upstream URLs per route.",[35,1574,1575,1578],{},[1539,1576,1577],{},"No timeout."," A slow upstream holds the function and the reader. Set a short timeout and fall back.",[35,1580,1581,1584],{},[1539,1582,1583],{},"Returning the full upstream payload."," It wastes bytes and can leak fields you did not mean to publish. Shape the response.",[35,1586,1587,1590],{},[1539,1588,1589],{},"Caching errors."," Do not cache upstream 4xx and 5xx responses as if they were data; serve the stale copy instead.",[35,1592,1593,1596],{},[1539,1594,1595],{},"Rollback:"," the widget's static fallback link means removing the script or the function leaves a working page; restoring either is one file.",[27,1598,1600],{"id":1599},"conclusion","Conclusion",[14,1602,1603],{},"An edge proxy lets a static page show live third-party data without exposing keys, without cross-origin trouble and without sending every reader to the upstream API. One route per upstream request, an allow-list for parameters, a trimmed response, a short edge cache with a long-lived fallback copy, and a three-second timeout turned two heavy embeds into 1.1 KB of JSON, cut upstream calls by 99%, and kept the widgets working through an upstream outage.",[27,1605,1607],{"id":1606},"faq","FAQ",[1609,1610,1612],"h3",{"id":1611},"why-not-call-the-third-party-api-directly-from-the-browser","Why not call the third-party API directly from the browser?",[14,1614,1615],{},"Because any API key in front-end code is public, many APIs do not allow cross-origin requests, rate limits apply per key and would be shared by every reader, and responses usually contain far more data than the page needs. A proxy fixes all four.",[1609,1617,1619],{"id":1618},"is-a-generic-proxy-that-forwards-any-path-a-good-idea","Is a generic proxy that forwards any path a good idea?",[14,1621,1622],{},"No. A proxy that forwards arbitrary paths with your key attached lets anyone use your key for anything the API allows. Map each proxy route to one fixed upstream request, with only the parameters you intend to allow.",[1609,1624,1626],{"id":1625},"how-long-should-proxied-responses-be-cached","How long should proxied responses be cached?",[14,1628,1629],{},"As long as the data can reasonably be stale. Release lists and status summaries tolerate minutes; prices and stock levels may need seconds. Use stale-while-revalidate so readers get an immediate response while the cache refreshes.",[1609,1631,1633],{"id":1632},"what-should-happen-when-the-upstream-api-is-down","What should happen when the upstream API is down?",[14,1635,1636],{},"Serve the last good response from cache if you have one, or a small, well-formed fallback that the page can render gracefully. Never let an upstream failure turn into a broken page or a hung request.",[27,1638,1640],{"id":1639},"related","Related",[32,1642,1643,1652,1659,1666,1673],{},[35,1644,1645,1648,1649,1651],{},[1539,1646,1647],{},"Parent:"," ",[21,1650,24],{"href":23}," — functions on static sites in general.",[35,1653,1654,1658],{},[21,1655,1657],{"href":1656},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002F","Third-Party Script Performance on Static Sites"," — why replacing embeds pays off.",[35,1660,1661,1665],{},[21,1662,1664],{"href":1663},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fnetlify-functions-vs-cloudflare-workers\u002F","Netlify Functions vs Cloudflare Workers"," — where to run the proxy.",[35,1667,1668,1672],{},[21,1669,1671],{"href":1670},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fstale-while-revalidate-for-static-html\u002F","Stale-While-Revalidate for Static HTML"," — the same caching idea for pages.",[35,1674,1675,1679],{},[21,1676,1678],{"href":1677},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fpagefind-vs-algolia-docsearch\u002F","Pagefind vs Algolia DocSearch"," — when a keyed search API is worth proxying.",[1681,1682,1683],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":57,"searchDepth":72,"depth":72,"links":1685},[1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1701],{"id":29,"depth":72,"text":30},{"id":46,"depth":72,"text":47},{"id":921,"depth":72,"text":922},{"id":1283,"depth":72,"text":1284},{"id":1376,"depth":72,"text":1377},{"id":1533,"depth":72,"text":1534},{"id":1552,"depth":72,"text":1553},{"id":1563,"depth":72,"text":1564},{"id":1599,"depth":72,"text":1600},{"id":1606,"depth":72,"text":1607,"children":1696},[1697,1698,1699,1700],{"id":1611,"depth":118,"text":1612},{"id":1618,"depth":118,"text":1619},{"id":1625,"depth":118,"text":1626},{"id":1632,"depth":118,"text":1633},{"id":1639,"depth":72,"text":1640},[1703,1706,1709,1710],{"name":1704,"item":1705},"Home","\u002F",{"name":1707,"item":1708},"Production-Ready Deployment & CI\u002FCD Workflows","\u002Fproduction-ready-deployment-cicd-workflows\u002F",{"name":24,"item":23},{"name":5,"item":1711},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fproxying-third-party-apis-from-an-edge-function\u002F","2026-09-18","Serve live third-party data on a static site through an edge proxy: hidden API keys, strict allow-lists, response shaping, caching and graceful fallback.","md",[1716,1717,1718,1719],{"q":1612,"a":1615},{"q":1619,"a":1622},{"q":1626,"a":1629},{"q":1633,"a":1636},{},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fproxying-third-party-apis-from-an-edge-function",{"title":5,"description":1713},"production-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fproxying-third-party-apis-from-an-edge-function\u002Findex","article","0tK5sZ7_iYwrfMwBMHFM6omg8ijlpeMqefrah9apLxw",[1727,1730,1733,1736,1739,1742,1745,1748,1751,1754,1757,1760,1763,1766,1769,1772,1775,1778,1781,1784,1787,1790,1793,1796,1799,1802,1805,1808,1811,1814,1817,1820,1823,1826,1829,1832,1835,1838,1841,1844,1847,1850,1853,1856,1859,1862,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1905,1908,1911,1914,1917,1920,1923,1926,1929,1932,1935,1938,1941,1944,1947,1950,1953,1956,1959,1962,1965,1968,1971,1974,1977,1980,1983,1986,1989,1992,1995,1998,2001,2004,2007,2010,2013,2016,2019,2022,2025,2028,2031,2034,2037,2040,2043,2046,2048,2051,2054,2057,2060,2063,2066,2069,2072,2075,2078,2081,2084,2087,2090,2093,2096,2099,2102,2105,2108,2111,2114,2117,2120,2123,2126,2129,2132,2135,2138,2141,2144,2147,2150,2153,2156,2159,2162,2165,2168,2171,2174,2177,2180,2183,2186,2189,2192,2195,2198,2201,2204,2207,2210,2213,2216,2219,2222,2225,2228,2231,2234,2237,2240,2243,2246,2249,2252,2255,2257,2260,2262,2264,2267],{"path":1728,"title":1729},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fastro-vs-eleventy-build-times-at-10000-pages","Astro vs Eleventy Build Times at 10,000 Pages",{"path":1731,"title":1732},"\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":1734,"title":1735},"\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":1737,"title":1738},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1740,"title":1741},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fshortcodes-vs-components-for-docs-authors","Shortcodes vs Components for Docs Authors",{"path":1743,"title":1744},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fsidebar-navigation-in-astro-and-eleventy","Sidebar Navigation in Astro and Eleventy",{"path":1746,"title":1747},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fcustomizing-starlight-without-forking-the-theme","Customizing Starlight Without Forking the Theme",{"path":1749,"title":1750},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fdocusaurus-vs-starlight-for-product-documentation","Docusaurus vs Starlight for Product Documentation",{"path":1752,"title":1753},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress","Docs Frameworks: Docusaurus, Starlight and VitePress",{"path":1755,"title":1756},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fmdx-vs-markdoc-for-docs-content","MDX vs Markdoc for Docs Content",{"path":1758,"title":1759},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fmigrating-from-mkdocs-to-starlight","Migrating from MkDocs to Starlight",{"path":1761,"title":1762},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fversioned-documentation-with-docusaurus","Versioned Documentation with Docusaurus",{"path":1764,"title":1765},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fvitepress-for-library-documentation","VitePress for Library Documentation",{"path":1767,"title":1768},"\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":1770,"title":1771},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Fhugo-partialcached-for-faster-builds","Hugo partialCached for Faster Builds",{"path":1773,"title":1774},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1776,"title":1777},"\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":1779,"title":1780},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Freducing-hugo-memory-usage-on-ci-runners","Reducing Hugo Memory Usage on CI Runners",{"path":1782,"title":1783},"\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":1785,"title":1786},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":1788,"title":1789},"\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":1791,"title":1792},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":1794,"title":1795},"\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":1797,"title":1798},"\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":1800,"title":1801},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fspeeding-up-slow-jekyll-builds","Speeding Up Slow Jekyll Builds",{"path":1803,"title":1804},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fupgrading-jekyll-and-ruby-versions-safely","Upgrading Jekyll and Ruby Versions Safely",{"path":1806,"title":1807},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fconverting-front-matter-at-scale-during-migration","Converting Front Matter at Scale During Migration",{"path":1809,"title":1810},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":1812,"title":1813},"\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":1815,"title":1816},"\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":1818,"title":1819},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-gatsby-to-astro","Migrating from Gatsby to Astro",{"path":1821,"title":1822},"\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":1824,"title":1825},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-wordpress-to-a-static-site-generator","Migrating WordPress to a Static Site Generator",{"path":1827,"title":1828},"\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":1830,"title":1831},"\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":1833,"title":1834},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1836,"title":1837},"\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":1839,"title":1840},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fnextjs-app-router-static-export-limitations","Next.js App Router Static Export Limitations",{"path":1842,"title":1843},"\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":1845,"title":1846},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Foptimizing-images-in-nextjs-static-export","Optimizing Images in Next.js Static Export",{"path":1848,"title":1849},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fadding-pagefind-to-an-astro-site","Adding Pagefind to an Astro Site",{"path":1851,"title":1852},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fbuilding-a-lunr-index-at-build-time-in-eleventy","Building a Lunr Index at Build Time in Eleventy",{"path":1854,"title":1855},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites","Search for Static Sites",{"path":1857,"title":1858},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Findexing-hugo-sites-with-pagefind","Indexing Hugo Sites with Pagefind",{"path":1860,"title":1861},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fmultilingual-search-on-static-sites","Multilingual Search on Static Sites",{"path":1863,"title":1678},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fpagefind-vs-algolia-docsearch",{"path":1865,"title":1866},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fsearch-index-size-budgets-for-large-docs","Search Index Size Budgets for Large Docs",{"path":1868,"title":1869},"\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":1871,"title":1872},"\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":1874,"title":1875},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fevaluating-ssg-accessibility-defaults","Evaluating SSG Accessibility Defaults",{"path":1877,"title":1878},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":1880,"title":1881},"\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":1883,"title":1884},"\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":1886,"title":1887},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Ftotal-cost-of-ownership-for-static-site-generators","Total Cost of Ownership for Static Site Generators",{"path":1889,"title":1890},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fcache-busting-with-content-hashed-filenames","Cache Busting with Content-Hashed Filenames",{"path":1892,"title":1893},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":1895,"title":1896},"\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":1898,"title":1899},"\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":1901,"title":1902},"\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":1904,"title":1671},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fstale-while-revalidate-for-static-html",{"path":1906,"title":1907},"\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":1909,"title":1910},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-cookie-banners","Fixing CLS from Cookie Banners",{"path":1912,"title":1913},"\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":1915,"title":1916},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-sticky-headers-and-anchor-links","Fixing CLS from Sticky Headers and Anchor Links",{"path":1918,"title":1919},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":1921,"title":1922},"\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":1924,"title":1925},"\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":1927,"title":1928},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Ffont-display-optional-vs-swap","font-display: optional vs swap",{"path":1930,"title":1931},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":1933,"title":1934},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fmetric-matched-fallback-fonts-with-size-adjust","Metric-Matched Fallback Fonts with size-adjust",{"path":1936,"title":1937},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fpreloading-fonts-without-double-downloads","Preloading Fonts Without Double Downloads",{"path":1939,"title":1940},"\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":1942,"title":1943},"\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":1945,"title":1946},"\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":1948,"title":1949},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Fgenerating-open-graph-images-at-build-time","Generating Open Graph Images at Build Time",{"path":1951,"title":1952},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":1954,"title":1955},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Flazy-loading-images-without-hurting-lcp","Lazy-Loading Images Without Hurting LCP",{"path":1957,"title":1958},"\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":1960,"title":1961},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Fresponsive-images-with-srcset-in-eleventy","Responsive Images with srcset in Eleventy",{"path":1963,"title":1964},"\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":1966,"title":1967},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":1969,"title":1970},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":1972,"title":1973},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fdeferring-hydration-with-client-visible-in-astro","Deferring Hydration with client:visible in Astro",{"path":1975,"title":1976},"\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":1978,"title":1979},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":1981,"title":1982},"\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":1984,"title":1985},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Freplacing-react-islands-with-web-components","Replacing React Islands with Web Components",{"path":1987,"title":1988},"\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":1990,"title":1991},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Ffixing-lcp-on-text-heavy-documentation-pages","Fixing LCP on Text-Heavy Documentation Pages",{"path":1993,"title":1994},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":1996,"title":1997},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Fmeasuring-lcp-subparts-with-devtools","Measuring LCP Subparts with DevTools",{"path":1999,"title":2000},"\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":2002,"title":2003},"\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":2005,"title":2006},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Fcomparing-lab-and-field-data-with-crux","Comparing Lab and Field Data with CrUX",{"path":2008,"title":2009},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci","Performance Budgets and Lighthouse CI",{"path":2011,"title":2012},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Freducing-lighthouse-score-variance-in-ci","Reducing Lighthouse Score Variance in CI",{"path":2014,"title":2015},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Frunning-webpagetest-scripts-against-preview-deploys","Running WebPageTest Scripts Against Preview Deploys",{"path":2017,"title":2018},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Fsetting-up-lighthouse-ci-for-a-static-site","Setting Up Lighthouse CI for a Static Site",{"path":2020,"title":2021},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Ftracking-bundle-size-per-pull-request","Tracking Bundle Size per Pull Request",{"path":2023,"title":2024},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Fwriting-a-performance-budget-that-fails-builds","Writing a Performance Budget That Fails Builds",{"path":2026,"title":2027},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fauditing-unused-preloads","Auditing Unused Preloads",{"path":2029,"title":2030},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed","Resource Hints and Navigation Speed",{"path":2032,"title":2033},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Finstant-navigation-with-speculation-rules","Instant Navigation with Speculation Rules",{"path":2035,"title":2036},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fpreconnect-vs-dns-prefetch-on-static-sites","Preconnect vs DNS-Prefetch on Static Sites",{"path":2038,"title":2039},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fprefetching-links-in-astro","Prefetching Links in Astro",{"path":2041,"title":2042},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fview-transitions-on-multi-page-static-sites","View Transitions on Multi-Page Static Sites",{"path":2044,"title":2045},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fauditing-third-party-scripts-with-lighthouse","Auditing Third-Party Scripts With Lighthouse",{"path":2047,"title":1657},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites",{"path":2049,"title":2050},"\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":2052,"title":2053},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Floading-google-tag-manager-without-hurting-inp","Loading Google Tag Manager Without Hurting INP",{"path":2055,"title":2056},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Frunning-third-party-scripts-in-a-web-worker-with-partytown","Running Third-Party Scripts in a Web Worker with Partytown",{"path":2058,"title":2059},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Fself-hosting-analytics-to-cut-third-party-requests","Self-Hosting Analytics to Cut Third-Party Requests",{"path":2061,"title":2062},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":2064,"title":2065},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fconfiguring-redirects-on-cloudflare-pages","Configuring Redirects on Cloudflare Pages",{"path":2067,"title":2068},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fcustom-domains-and-tls-on-cloudflare-pages","Custom Domains and TLS on Cloudflare Pages",{"path":2070,"title":2071},"\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":2073,"title":2074},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":2076,"title":2077},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fmigrating-from-cloudflare-pages-to-workers-static-assets","Migrating from Cloudflare Pages to Workers Static Assets",{"path":2079,"title":2080},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fchecking-links-in-pull-requests","Checking Links in Pull Requests",{"path":2082,"title":2083},"\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":2085,"title":2086},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Feditorial-checks-with-vale-in-ci","Editorial Checks with Vale in CI",{"path":2088,"title":2089},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":2091,"title":2092},"\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":2094,"title":2095},"\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":2097,"title":2098},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fbuilding-astro-sites-with-github-actions","Building Astro Sites with GitHub Actions",{"path":2100,"title":2101},"\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":2103,"title":2104},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fdeploying-to-github-pages-with-actions","Deploying to GitHub Pages with Actions",{"path":2106,"title":2107},"\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":2109,"title":2110},"\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":2112,"title":2113},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":2115,"title":2116},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fmatrix-builds-for-multi-site-monorepos","Matrix Builds for Multi-Site Monorepos",{"path":2118,"title":2119},"\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":2121,"title":2122},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":2124,"title":2125},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fincremental-builds-in-astro-with-the-content-layer","Incremental Builds in Astro with the Content Layer",{"path":2127,"title":2128},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":2130,"title":2131},"\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":2133,"title":2134},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fremote-caching-with-turborepo-for-ssg-monorepos","Remote Caching with Turborepo for SSG Monorepos",{"path":2136,"title":2137},"\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":2139,"title":2140},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":2142,"title":2143},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Falerting-on-cache-hit-ratio-drops","Alerting on Cache Hit Ratio Drops",{"path":2145,"title":2146},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Fbuilding-a-core-web-vitals-dashboard-from-rum-data","Building a Core Web Vitals Dashboard from RUM Data",{"path":2148,"title":2149},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Fcrawling-for-broken-links-on-a-schedule","Crawling for Broken Links on a Schedule",{"path":2151,"title":2152},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production","Monitoring Static Sites in Production",{"path":2154,"title":2155},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Flogging-404s-at-the-edge","Logging 404s at the Edge",{"path":2157,"title":2158},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Fuptime-and-synthetic-checks-for-static-sites","Uptime and Synthetic Checks for Static Sites",{"path":2160,"title":2161},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fconfiguring-vercel-for-hugo-and-eleventy","Configuring Vercel for Hugo and Eleventy",{"path":2163,"title":2164},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":2166,"title":2167},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":2169,"title":2170},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-redirects-and-rewrites-for-static-sites","Netlify Redirects and Rewrites for Static Sites",{"path":2172,"title":2173},"\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":2175,"title":2176},"\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":2178,"title":2179},"\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":2181,"title":2182},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":2184,"title":2185},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":2187,"title":2188},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpassword-protecting-preview-deployments","Password-Protecting Preview Deployments",{"path":2190,"title":2191},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpreviewing-headless-cms-drafts","Previewing Headless CMS Drafts",{"path":2193,"title":2194},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fvisual-regression-testing-on-preview-deploys","Visual Regression Testing on Preview Deploys",{"path":2196,"title":2197},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":2199,"title":2200},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fcanary-releases-for-static-sites","Canary Releases for Static Sites",{"path":2202,"title":2203},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Ffeature-flags-on-static-sites","Feature Flags on Static Sites",{"path":2205,"title":2206},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":2208,"title":2209},"\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":2211,"title":2212},"\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",{"path":2214,"title":2215},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fauditing-npm-dependencies-in-ssg-pipelines","Auditing npm Dependencies in SSG Pipelines",{"path":2217,"title":2218},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fenabling-hsts-and-preload-safely","Enabling HSTS and Preload Safely",{"path":2220,"title":2221},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fhash-based-csp-for-inline-scripts-in-astro","Hash-Based CSP for Inline Scripts in Astro",{"path":2223,"title":2224},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites","Security Headers and Hardening for Static Sites",{"path":2226,"title":2227},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsecuring-deploy-credentials-with-github-oidc","Securing Deploy Credentials with GitHub OIDC",{"path":2229,"title":2230},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsubresource-integrity-for-third-party-assets","Subresource Integrity for Third-Party Assets",{"path":2232,"title":2233},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fwriting-a-content-security-policy-for-a-static-site","Writing a Content Security Policy for a Static Site",{"path":2235,"title":2236},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fclean-urls-and-trailing-slashes-on-s3","Clean URLs and Trailing Slashes on S3",{"path":2238,"title":2239},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fcloudfront-functions-for-redirects","CloudFront Functions for Redirects",{"path":2241,"title":2242},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fdeploying-a-static-site-to-s3-and-cloudfront","Deploying a Static Site to S3 and CloudFront",{"path":2244,"title":2245},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites","Self-Hosting Static Sites on S3, Nginx and Caddy",{"path":2247,"title":2248},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-caddy","Serving a Static Site with Caddy",{"path":2250,"title":2251},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx","Serving a Static Site with Nginx",{"path":2253,"title":2254},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fzero-downtime-deploys-with-symlink-swaps","Zero-Downtime Deploys with Symlink Swaps",{"path":2256,"title":1560},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fadding-a-contact-form-with-cloudflare-workers",{"path":2258,"title":2259},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fhandling-form-submissions-on-a-static-site","Handling Form Submissions on a Static Site",{"path":2261,"title":24},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites",{"path":2263,"title":1664},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fnetlify-functions-vs-cloudflare-workers",{"path":2265,"title":2266},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fprotecting-a-static-site-behind-authentication","Protecting a Static Site Behind Authentication",{"path":1721,"title":5},1789722847984]