[{"data":1,"prerenderedAt":2493},["ShallowReactive",2],{"page:\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites":3,"all-docs-nav":2252},{"id":4,"title":5,"body":6,"breadcrumb":2228,"dateModified":2234,"datePublished":2234,"description":2235,"extension":2236,"faq":2237,"meta":2246,"navigation":617,"path":2247,"seo":2248,"slug":12,"stem":2249,"type":2250,"__hash__":2251},"content\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Findex.md","Rollbacks and Deploy Safety for Static Sites",{"type":7,"value":8,"toc":2207},"minimark",[9,13,17,31,138,143,146,149,254,257,356,362,366,369,379,422,425,431,443,447,450,1038,1046,1050,1053,1149,1272,1275,1279,1282,1288,1294,1300,1381,1384,1392,1396,1399,1402,1418,1547,1554,1558,1561,1567,1577,1583,1586,1590,1593,1701,2034,2040,2043,2047,2089,2093,2113,2117,2122,2125,2129,2132,2136,2139,2143,2146,2150,2153,2157,2160,2164,2203],[10,11,5],"h1",{"id":12},"rollbacks-and-deploy-safety-for-static-sites",[14,15,16],"p",{},"Static hosting removes a lot of failure modes — no runtime, no database, no server to fall over — and leaves one: you can publish a broken build. A missing partial, a content file that fails a schema, an asset pipeline that silently produced nothing. The site is up, fast, cached at the edge, and wrong.",[14,18,19,20,25,26,30],{},"The defence is not more careful deploying; it is making a bad deploy cheap to undo. This guide covers atomic deploys, immutable artifacts, smoke tests that run against the deployed URL, and the one-command rollback that turns an incident into an inconvenience. It sits under ",[21,22,24],"a",{"href":23},"\u002Fproduction-ready-deployment-cicd-workflows\u002F","Production-Ready Deployment & CI\u002FCD Workflows",", alongside ",[21,27,29],{"href":28},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002F","Preview Environments for Pull Requests"," — previews catch problems before merge, this catches the ones that get through.",[32,33,34,134],"figure",{},[35,36,43,44,43,48,43,52,43,59],"svg",{"viewBox":37,"role":38,"ariaLabelledBy":39,"xmlns":42},"0 0 760 320","img",[40,41],"rb-layers-title","rb-layers-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[45,46,47],"title",{"id":40},"Four layers of deploy safety",[49,50,51],"desc",{"id":41},"Four stacked layers, each catching what the previous one missed. Build gates catch broken content before deploy. Atomic upload prevents partial states. Smoke tests against the deployed URL catch broken output. A pointer-based rollback restores the previous build in seconds when everything else fails.",[53,54],"rect",{"x":55,"y":55,"width":56,"height":57,"fill":58},"0","760","320","#ffffff",[60,61,63,64,63,72,63,83,63,88,63,94,63,99,63,103,63,107,63,113,63,117,63,121,63,126,63,130,43],"g",{"style":62},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[65,66,71],"text",{"x":67,"y":68,"fill":69,"style":70},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","Each layer catches what the one before it missed",[53,73],{"x":74,"y":75,"width":76,"height":77,"rx":78,"fill":79,"opacity":80,"stroke":81,"style":82},"60","52","640","54","10","#8ac926","0.18","#5a8a16","stroke-width:1.5px",[65,84,87],{"x":67,"y":85,"fill":69,"style":86},"74","font-size:13px;font-weight:700;text-anchor:middle","1 · Build gates — link check, schema, word count, Lighthouse budget",[65,89,93],{"x":67,"y":90,"fill":91,"style":92},"94","#556071","font-size:11px;text-anchor:middle","catches broken content before anything is published",[53,95],{"x":74,"y":96,"width":76,"height":77,"rx":78,"fill":97,"opacity":98,"stroke":97,"style":82},"118","#1982c4","0.14",[65,100,102],{"x":67,"y":101,"fill":69,"style":86},"140","2 · Atomic upload — complete build to a new immutable location, then flip",[65,104,106],{"x":67,"y":105,"fill":91,"style":92},"160","removes the half-deployed window entirely",[53,108],{"x":74,"y":109,"width":76,"height":77,"rx":78,"fill":110,"opacity":111,"stroke":112,"style":82},"184","#ffca3a","0.3","#a97b00",[65,114,116],{"x":67,"y":115,"fill":69,"style":86},"206","3 · Smoke tests — against the deployed URL, not the build directory",[65,118,120],{"x":67,"y":119,"fill":91,"style":92},"226","catches output that built fine and serves wrong",[53,122],{"x":74,"y":123,"width":76,"height":77,"rx":78,"fill":124,"opacity":98,"stroke":125,"style":82},"250","#ff595e","#d83b41",[65,127,129],{"x":67,"y":128,"fill":69,"style":86},"272","4 · Rollback — repoint at the previous deployment, under 60 seconds",[65,131,133],{"x":67,"y":132,"fill":91,"style":92},"292","the layer that makes every other decision reversible",[135,136,137],"figcaption",{},"The layers are ordered by cost: gates are free and run on every commit, rollback is the expensive one you hope never to use — and the only one that works when the failure is something nobody predicted.",[139,140,142],"h2",{"id":141},"atomic-deploys-are-not-optional","Atomic Deploys Are Not Optional",[14,144,145],{},"A deploy that copies files into a live directory produces a window — sometimes seconds, sometimes minutes — where the site is a mixture of two builds. New HTML references hashed assets that have not uploaded yet; readers get unstyled pages and 404s on scripts. It is the single most common cause of \"the site broke for five minutes after a deploy\".",[14,147,148],{},"Every modern static host solves this the same way: upload the complete build to a new immutable location, verify it, then flip a pointer.",[150,151,152,171],"table",{},[153,154,155],"thead",{},[156,157,158,162,165,168],"tr",{},[159,160,161],"th",{},"Host",[159,163,164],{},"Atomic by default",[159,166,167],{},"Previous builds addressable",[159,169,170],{},"Rollback mechanism",[172,173,174,189,202,215,236],"tbody",{},[156,175,176,180,183,186],{},[177,178,179],"td",{},"Netlify",[177,181,182],{},"Yes",[177,184,185],{},"Yes, indefinitely",[177,187,188],{},"Publish an earlier deploy",[156,190,191,194,196,199],{},[177,192,193],{},"Cloudflare Pages \u002F Workers",[177,195,182],{},[177,197,198],{},"Yes, by deployment ID",[177,200,201],{},"Promote a previous deployment",[156,203,204,207,209,212],{},[177,205,206],{},"Vercel",[177,208,182],{},[177,210,211],{},"Yes, by deployment URL",[177,213,214],{},"Promote to production",[156,216,217,224,230,233],{},[177,218,219,220],{},"S3 + CloudFront ",[221,222,223],"code",{},"sync",[177,225,226],{},[227,228,229],"strong",{},"No",[177,231,232],{},"Only via versioning",[177,234,235],{},"Re-upload, then invalidate",[156,237,238,244,248,251],{},[177,239,240,243],{},[221,241,242],{},"rsync"," to a VM",[177,245,246],{},[227,247,229],{},[177,249,250],{},"Only what you kept",[177,252,253],{},"Restore from a copy",[14,255,256],{},"The last two rows are where teams get hurt. If you must deploy to object storage or a VM, build atomicity yourself: upload to a versioned prefix, then move a symlink or update the origin path.",[258,259,264],"pre",{"className":260,"code":261,"language":262,"meta":263,"style":263},"language-bash shiki shiki-themes github-light github-dark","# Atomic deploy to a VM origin: upload beside, then swap a symlink\nSTAMP=$(git rev-parse --short HEAD)\nrsync -a --delete dist\u002F deploy@origin:\u002Fvar\u002Fwww\u002Freleases\u002F\"$STAMP\"\u002F\nssh deploy@origin \"ln -sfn \u002Fvar\u002Fwww\u002Freleases\u002F$STAMP \u002Fvar\u002Fwww\u002Fcurrent.new && \\\n                   mv -Tf \u002Fvar\u002Fwww\u002Fcurrent.new \u002Fvar\u002Fwww\u002Fcurrent\"\n","bash","",[221,265,266,275,307,330,350],{"__ignoreMap":263},[267,268,271],"span",{"class":269,"line":270},"line",1,[267,272,274],{"class":273},"sJ8bj","# Atomic deploy to a VM origin: upload beside, then swap a symlink\n",[267,276,278,282,286,289,293,297,301,304],{"class":269,"line":277},2,[267,279,281],{"class":280},"sVt8B","STAMP",[267,283,285],{"class":284},"szBVR","=",[267,287,288],{"class":280},"$(",[267,290,292],{"class":291},"sScJk","git",[267,294,296],{"class":295},"sZZnC"," rev-parse",[267,298,300],{"class":299},"sj4cs"," --short",[267,302,303],{"class":295}," HEAD",[267,305,306],{"class":280},")\n",[267,308,310,312,315,318,321,324,327],{"class":269,"line":309},3,[267,311,242],{"class":291},[267,313,314],{"class":299}," -a",[267,316,317],{"class":299}," --delete",[267,319,320],{"class":295}," dist\u002F",[267,322,323],{"class":295}," deploy@origin:\u002Fvar\u002Fwww\u002Freleases\u002F\"",[267,325,326],{"class":280},"$STAMP",[267,328,329],{"class":295},"\"\u002F\n",[267,331,333,336,339,342,344,347],{"class":269,"line":332},4,[267,334,335],{"class":291},"ssh",[267,337,338],{"class":295}," deploy@origin",[267,340,341],{"class":295}," \"ln -sfn \u002Fvar\u002Fwww\u002Freleases\u002F",[267,343,326],{"class":280},[267,345,346],{"class":295}," \u002Fvar\u002Fwww\u002Fcurrent.new && ",[267,348,349],{"class":299},"\\\n",[267,351,353],{"class":269,"line":352},5,[267,354,355],{"class":295},"                   mv -Tf \u002Fvar\u002Fwww\u002Fcurrent.new \u002Fvar\u002Fwww\u002Fcurrent\"\n",[14,357,358,361],{},[221,359,360],{},"mv -Tf"," on a symlink is atomic on Linux, so no request ever sees a partial state, and every previous release remains on disk ready to be swapped back.",[139,363,365],{"id":364},"keep-build-artifacts-immutable-and-addressable","Keep Build Artifacts Immutable and Addressable",[14,367,368],{},"A rollback is only fast if the previous output still exists somewhere you can point at. That means two disciplines.",[14,370,371,374,375,378],{},[227,372,373],{},"Stamp every build."," Put the commit SHA in the output — a meta tag, a ",[221,376,377],{},"\u002Fversion.json",", a comment in the HTML — so you can tell from a browser which build is live:",[258,380,384],{"className":381,"code":382,"language":383,"meta":263,"style":263},"language-html shiki shiki-themes github-light github-dark","\u003Cmeta name=\"build\" content=\"a91f3c2\" data-built=\"2026-08-01T09:14:22Z\">\n","html",[221,385,386],{"__ignoreMap":263},[267,387,388,391,395,398,400,403,406,408,411,414,416,419],{"class":269,"line":270},[267,389,390],{"class":280},"\u003C",[267,392,394],{"class":393},"s9eBZ","meta",[267,396,397],{"class":291}," name",[267,399,285],{"class":280},[267,401,402],{"class":295},"\"build\"",[267,404,405],{"class":291}," content",[267,407,285],{"class":280},[267,409,410],{"class":295},"\"a91f3c2\"",[267,412,413],{"class":291}," data-built",[267,415,285],{"class":280},[267,417,418],{"class":295},"\"2026-08-01T09:14:22Z\"",[267,420,421],{"class":280},">\n",[14,423,424],{},"This is the first thing you check during an incident, and without it you spend the first five minutes arguing about whether the deploy actually went out.",[14,426,427,430],{},[227,428,429],{},"Retain deployments."," Thirty days is a reasonable default for a content site. Static output is small, and the cost of retention is far below the cost of discovering that the last known-good build was garbage-collected an hour ago.",[14,432,433,434,437,438,442],{},"Immutability matters for assets too: hashed filenames mean an old HTML page and a new one can coexist during a cache transition without fighting over ",[221,435,436],{},"\u002Fstyle.css",". That is the same policy described in ",[21,439,441],{"href":440},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002F","CDN Caching Rules for SSGs",", and it is what makes a rollback safe rather than merely fast — rolling back HTML that references hashed assets works because those assets were never overwritten.",[139,444,446],{"id":445},"smoke-test-the-deployment-not-the-build","Smoke Test the Deployment, Not the Build",[14,448,449],{},"A build directory that passes every check can still serve wrong: a host redirect rule swallowing a path, a base URL configured for the wrong environment, an asset excluded by a deploy ignore file. Smoke tests run against the deployed URL and answer one question — is this site usable?",[258,451,455],{"className":452,"code":453,"language":454,"meta":263,"style":263},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F scripts\u002Fsmoke.mjs \u003Cbase-url> — exits non-zero on the first failure\nconst base = process.argv[2].replace(\u002F\\\u002F$\u002F, '');\nconst checks = [\n  { path: '\u002F', must: ['\u003Ctitle', 'site-header', 'href=\"\u002Fguides\u002F'] },\n  { path: '\u002Fguides\u002F', must: ['\u003Ch1', 'article'] },\n  { path: '\u002Fguides\u002Fdeploying\u002F', must: ['\u003Ch1', 'site-footer'] },\n  { path: '\u002Fsitemap.xml', must: ['\u003Curlset', '\u003Cloc>'] },\n];\n\nlet failed = 0;\nfor (const { path, must } of checks) {\n  const res = await fetch(base + path);\n  const body = await res.text();\n  if (res.status !== 200) { console.error(`FAIL ${path} → ${res.status}`); failed++; continue; }\n  for (const needle of must) {\n    if (!body.includes(needle)) { console.error(`FAIL ${path} missing ${needle}`); failed++; }\n  }\n  \u002F\u002F Every hashed asset the page references must actually exist\n  for (const [, href] of body.matchAll(\u002F(?:href|src)=\"(\\\u002F_assets\\\u002F[^\"]+)\"\u002Fg)) {\n    const asset = await fetch(base + href, { method: 'HEAD' });\n    if (!asset.ok) { console.error(`FAIL ${path} → missing asset ${href}`); failed++; }\n  }\n}\nconsole.log(failed ? `smoke: ${failed} failure(s)` : 'smoke: OK');\nprocess.exit(failed ? 1 : 0);\n","javascript",[221,456,457,462,509,521,548,567,586,606,612,619,636,667,693,713,771,790,832,838,844,912,940,973,978,984,1016],{"__ignoreMap":263},[267,458,459],{"class":269,"line":270},[267,460,461],{"class":273},"\u002F\u002F scripts\u002Fsmoke.mjs \u003Cbase-url> — exits non-zero on the first failure\n",[267,463,464,467,470,473,476,479,482,485,488,491,495,498,500,503,506],{"class":269,"line":277},[267,465,466],{"class":284},"const",[267,468,469],{"class":299}," base",[267,471,472],{"class":284}," =",[267,474,475],{"class":280}," process.argv[",[267,477,478],{"class":299},"2",[267,480,481],{"class":280},"].",[267,483,484],{"class":291},"replace",[267,486,487],{"class":280},"(",[267,489,490],{"class":295},"\u002F",[267,492,494],{"class":493},"snhLl","\\\u002F",[267,496,497],{"class":284},"$",[267,499,490],{"class":295},[267,501,502],{"class":280},", ",[267,504,505],{"class":295},"''",[267,507,508],{"class":280},");\n",[267,510,511,513,516,518],{"class":269,"line":309},[267,512,466],{"class":284},[267,514,515],{"class":299}," checks",[267,517,472],{"class":284},[267,519,520],{"class":280}," [\n",[267,522,523,526,529,532,535,537,540,542,545],{"class":269,"line":332},[267,524,525],{"class":280},"  { path: ",[267,527,528],{"class":295},"'\u002F'",[267,530,531],{"class":280},", must: [",[267,533,534],{"class":295},"'\u003Ctitle'",[267,536,502],{"class":280},[267,538,539],{"class":295},"'site-header'",[267,541,502],{"class":280},[267,543,544],{"class":295},"'href=\"\u002Fguides\u002F'",[267,546,547],{"class":280},"] },\n",[267,549,550,552,555,557,560,562,565],{"class":269,"line":352},[267,551,525],{"class":280},[267,553,554],{"class":295},"'\u002Fguides\u002F'",[267,556,531],{"class":280},[267,558,559],{"class":295},"'\u003Ch1'",[267,561,502],{"class":280},[267,563,564],{"class":295},"'article'",[267,566,547],{"class":280},[267,568,570,572,575,577,579,581,584],{"class":269,"line":569},6,[267,571,525],{"class":280},[267,573,574],{"class":295},"'\u002Fguides\u002Fdeploying\u002F'",[267,576,531],{"class":280},[267,578,559],{"class":295},[267,580,502],{"class":280},[267,582,583],{"class":295},"'site-footer'",[267,585,547],{"class":280},[267,587,589,591,594,596,599,601,604],{"class":269,"line":588},7,[267,590,525],{"class":280},[267,592,593],{"class":295},"'\u002Fsitemap.xml'",[267,595,531],{"class":280},[267,597,598],{"class":295},"'\u003Curlset'",[267,600,502],{"class":280},[267,602,603],{"class":295},"'\u003Cloc>'",[267,605,547],{"class":280},[267,607,609],{"class":269,"line":608},8,[267,610,611],{"class":280},"];\n",[267,613,615],{"class":269,"line":614},9,[267,616,618],{"emptyLinePlaceholder":617},true,"\n",[267,620,622,625,628,630,633],{"class":269,"line":621},10,[267,623,624],{"class":284},"let",[267,626,627],{"class":280}," failed ",[267,629,285],{"class":284},[267,631,632],{"class":299}," 0",[267,634,635],{"class":280},";\n",[267,637,639,642,645,647,650,653,655,658,661,664],{"class":269,"line":638},11,[267,640,641],{"class":284},"for",[267,643,644],{"class":280}," (",[267,646,466],{"class":284},[267,648,649],{"class":280}," { ",[267,651,652],{"class":299},"path",[267,654,502],{"class":280},[267,656,657],{"class":299},"must",[267,659,660],{"class":280}," } ",[267,662,663],{"class":284},"of",[267,665,666],{"class":280}," checks) {\n",[267,668,670,673,676,678,681,684,687,690],{"class":269,"line":669},12,[267,671,672],{"class":284},"  const",[267,674,675],{"class":299}," res",[267,677,472],{"class":284},[267,679,680],{"class":284}," await",[267,682,683],{"class":291}," fetch",[267,685,686],{"class":280},"(base ",[267,688,689],{"class":284},"+",[267,691,692],{"class":280}," path);\n",[267,694,696,698,701,703,705,708,710],{"class":269,"line":695},13,[267,697,672],{"class":284},[267,699,700],{"class":299}," body",[267,702,472],{"class":284},[267,704,680],{"class":284},[267,706,707],{"class":280}," res.",[267,709,65],{"class":291},[267,711,712],{"class":280},"();\n",[267,714,716,719,722,725,728,731,734,736,739,741,744,747,750,753,756,759,762,765,768],{"class":269,"line":715},14,[267,717,718],{"class":284},"  if",[267,720,721],{"class":280}," (res.status ",[267,723,724],{"class":284},"!==",[267,726,727],{"class":299}," 200",[267,729,730],{"class":280},") { console.",[267,732,733],{"class":291},"error",[267,735,487],{"class":280},[267,737,738],{"class":295},"`FAIL ${",[267,740,652],{"class":280},[267,742,743],{"class":295},"} → ${",[267,745,746],{"class":280},"res",[267,748,749],{"class":295},".",[267,751,752],{"class":280},"status",[267,754,755],{"class":295},"}`",[267,757,758],{"class":280},"); failed",[267,760,761],{"class":284},"++",[267,763,764],{"class":280},"; ",[267,766,767],{"class":284},"continue",[267,769,770],{"class":280},"; }\n",[267,772,774,777,779,781,784,787],{"class":269,"line":773},15,[267,775,776],{"class":284},"  for",[267,778,644],{"class":280},[267,780,466],{"class":284},[267,782,783],{"class":299}," needle",[267,785,786],{"class":284}," of",[267,788,789],{"class":280}," must) {\n",[267,791,793,796,798,801,804,807,810,812,814,816,818,821,824,826,828,830],{"class":269,"line":792},16,[267,794,795],{"class":284},"    if",[267,797,644],{"class":280},[267,799,800],{"class":284},"!",[267,802,803],{"class":280},"body.",[267,805,806],{"class":291},"includes",[267,808,809],{"class":280},"(needle)) { console.",[267,811,733],{"class":291},[267,813,487],{"class":280},[267,815,738],{"class":295},[267,817,652],{"class":280},[267,819,820],{"class":295},"} missing ${",[267,822,823],{"class":280},"needle",[267,825,755],{"class":295},[267,827,758],{"class":280},[267,829,761],{"class":284},[267,831,770],{"class":280},[267,833,835],{"class":269,"line":834},17,[267,836,837],{"class":280},"  }\n",[267,839,841],{"class":269,"line":840},18,[267,842,843],{"class":273},"  \u002F\u002F Every hashed asset the page references must actually exist\n",[267,845,847,849,851,853,856,859,862,864,867,870,872,874,878,881,884,886,889,891,894,897,900,902,905,907,909],{"class":269,"line":846},19,[267,848,776],{"class":284},[267,850,644],{"class":280},[267,852,466],{"class":284},[267,854,855],{"class":280}," [, ",[267,857,858],{"class":299},"href",[267,860,861],{"class":280},"] ",[267,863,663],{"class":284},[267,865,866],{"class":280}," body.",[267,868,869],{"class":291},"matchAll",[267,871,487],{"class":280},[267,873,490],{"class":295},[267,875,877],{"class":876},"sA_wV","(?:href",[267,879,880],{"class":284},"|",[267,882,883],{"class":876},"src)=\"(",[267,885,494],{"class":493},[267,887,888],{"class":876},"_assets",[267,890,494],{"class":493},[267,892,893],{"class":299},"[",[267,895,896],{"class":284},"^",[267,898,899],{"class":299},"\"]",[267,901,689],{"class":284},[267,903,904],{"class":876},")\"",[267,906,490],{"class":295},[267,908,60],{"class":284},[267,910,911],{"class":280},")) {\n",[267,913,915,918,921,923,925,927,929,931,934,937],{"class":269,"line":914},20,[267,916,917],{"class":284},"    const",[267,919,920],{"class":299}," asset",[267,922,472],{"class":284},[267,924,680],{"class":284},[267,926,683],{"class":291},[267,928,686],{"class":280},[267,930,689],{"class":284},[267,932,933],{"class":280}," href, { method: ",[267,935,936],{"class":295},"'HEAD'",[267,938,939],{"class":280}," });\n",[267,941,943,945,947,949,952,954,956,958,960,963,965,967,969,971],{"class":269,"line":942},21,[267,944,795],{"class":284},[267,946,644],{"class":280},[267,948,800],{"class":284},[267,950,951],{"class":280},"asset.ok) { console.",[267,953,733],{"class":291},[267,955,487],{"class":280},[267,957,738],{"class":295},[267,959,652],{"class":280},[267,961,962],{"class":295},"} → missing asset ${",[267,964,858],{"class":280},[267,966,755],{"class":295},[267,968,758],{"class":280},[267,970,761],{"class":284},[267,972,770],{"class":280},[267,974,976],{"class":269,"line":975},22,[267,977,837],{"class":280},[267,979,981],{"class":269,"line":980},23,[267,982,983],{"class":280},"}\n",[267,985,987,990,993,996,999,1002,1005,1008,1011,1014],{"class":269,"line":986},24,[267,988,989],{"class":280},"console.",[267,991,992],{"class":291},"log",[267,994,995],{"class":280},"(failed ",[267,997,998],{"class":284},"?",[267,1000,1001],{"class":295}," `smoke: ${",[267,1003,1004],{"class":280},"failed",[267,1006,1007],{"class":295},"} failure(s)`",[267,1009,1010],{"class":284}," :",[267,1012,1013],{"class":295}," 'smoke: OK'",[267,1015,508],{"class":280},[267,1017,1019,1022,1025,1027,1029,1032,1034,1036],{"class":269,"line":1018},25,[267,1020,1021],{"class":280},"process.",[267,1023,1024],{"class":291},"exit",[267,1026,995],{"class":280},[267,1028,998],{"class":284},[267,1030,1031],{"class":299}," 1",[267,1033,1010],{"class":284},[267,1035,632],{"class":299},[267,1037,508],{"class":280},[14,1039,1040,1041,1045],{},"Run it twice: once against the preview URL before promotion, and once against production immediately after. The second run is what turns a silent bad deploy into an alert, and it is the trigger for an automatic rollback if you choose to wire one — see ",[21,1042,1044],{"href":1043},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url\u002F","Running Smoke Tests Against a Preview URL"," for the full check set.",[139,1047,1049],{"id":1048},"make-rollback-a-single-command","Make Rollback a Single Command",[14,1051,1052],{},"The rollback procedure must be short enough to execute correctly at 2 a.m. by someone who did not write it. That means one command, documented where people will look, with no build step.",[258,1054,1056],{"className":260,"code":1055,"language":262,"meta":263,"style":263},"# Cloudflare Workers\u002FPages — list and promote a previous deployment\nnpx wrangler deployments list\nnpx wrangler rollback \u003Cdeployment-id>\n\n# Netlify\nnetlify api listSiteDeploys --data '{\"site_id\":\"SITE\"}' | jq -r '.[0:5][] | \"\\(.id) \\(.created_at) \\(.title)\"'\nnetlify api restoreSiteDeploy --data '{\"site_id\":\"SITE\",\"deploy_id\":\"DEPLOY\"}'\n",[221,1057,1058,1063,1077,1097,1101,1106,1135],{"__ignoreMap":263},[267,1059,1060],{"class":269,"line":270},[267,1061,1062],{"class":273},"# Cloudflare Workers\u002FPages — list and promote a previous deployment\n",[267,1064,1065,1068,1071,1074],{"class":269,"line":277},[267,1066,1067],{"class":291},"npx",[267,1069,1070],{"class":295}," wrangler",[267,1072,1073],{"class":295}," deployments",[267,1075,1076],{"class":295}," list\n",[267,1078,1079,1081,1083,1086,1089,1092,1095],{"class":269,"line":309},[267,1080,1067],{"class":291},[267,1082,1070],{"class":295},[267,1084,1085],{"class":295}," rollback",[267,1087,1088],{"class":284}," \u003C",[267,1090,1091],{"class":295},"deployment-i",[267,1093,1094],{"class":280},"d",[267,1096,421],{"class":284},[267,1098,1099],{"class":269,"line":332},[267,1100,618],{"emptyLinePlaceholder":617},[267,1102,1103],{"class":269,"line":352},[267,1104,1105],{"class":273},"# Netlify\n",[267,1107,1108,1111,1114,1117,1120,1123,1126,1129,1132],{"class":269,"line":569},[267,1109,1110],{"class":291},"netlify",[267,1112,1113],{"class":295}," api",[267,1115,1116],{"class":295}," listSiteDeploys",[267,1118,1119],{"class":299}," --data",[267,1121,1122],{"class":295}," '{\"site_id\":\"SITE\"}'",[267,1124,1125],{"class":284}," |",[267,1127,1128],{"class":291}," jq",[267,1130,1131],{"class":299}," -r",[267,1133,1134],{"class":295}," '.[0:5][] | \"\\(.id) \\(.created_at) \\(.title)\"'\n",[267,1136,1137,1139,1141,1144,1146],{"class":269,"line":588},[267,1138,1110],{"class":291},[267,1140,1113],{"class":295},[267,1142,1143],{"class":295}," restoreSiteDeploy",[267,1145,1119],{"class":299},[267,1147,1148],{"class":295}," '{\"site_id\":\"SITE\",\"deploy_id\":\"DEPLOY\"}'\n",[32,1150,1151,1269],{},[35,1152,43,1157,43,1160,43,1163,43,1166],{"viewBox":1153,"role":38,"ariaLabelledBy":1154,"xmlns":42},"0 0 760 300",[1155,1156],"rb-timeline-title","rb-timeline-desc",[45,1158,1159],{"id":1155},"Incident timeline with and without a rehearsed rollback",[49,1161,1162],{"id":1156},"Two timelines. Without a rehearsed rollback: detection at 4 minutes, diagnosis at 14, a fix commit at 26, a rebuild at 31 and recovery at 34 minutes. With a rehearsed rollback: detection at 4 minutes, decision at 5, pointer flip at 6 and recovery at 6 minutes, with diagnosis happening afterwards at leisure.",[53,1164],{"x":55,"y":55,"width":56,"height":1165,"fill":58},"300",[60,1167,63,1168,63,1171,63,1177,63,1184,63,1188,63,1193,63,1195,63,1199,63,1204,63,1208,63,1212,63,1216,63,1220,63,1223,63,1226,63,1229,63,1232,63,1236,63,1240,63,1243,63,1247,63,1251,63,1256,63,1260,63,1265,43],{"style":62},[65,1169,1170],{"x":67,"y":68,"fill":69,"style":70},"Fix forward vs roll back, same incident",[65,1172,1176],{"x":1173,"y":1174,"fill":125,"style":1175},"40","70","font-size:12px;font-weight:700","Fix forward",[269,1178],{"x1":1179,"y1":1180,"x2":1181,"y2":1180,"stroke":1182,"style":1183},"150","86","710","#d9e2ef","stroke-width:2px",[53,1185],{"x":1179,"y":1186,"width":1174,"height":68,"rx":1187,"fill":110,"opacity":111,"stroke":112,"style":82},"72","5",[65,1189,1192],{"x":1190,"y":1191,"fill":69,"style":92},"185","91","detect 4m",[53,1194],{"x":119,"y":1186,"width":1179,"height":68,"rx":1187,"fill":124,"opacity":98,"stroke":125,"style":82},[65,1196,1198],{"x":1197,"y":1191,"fill":69,"style":92},"301","diagnose 10m",[53,1200],{"x":1201,"y":1186,"width":1202,"height":68,"rx":1187,"fill":124,"opacity":1203,"stroke":125,"style":82},"382","170","0.12",[65,1205,1207],{"x":1206,"y":1191,"fill":69,"style":92},"467","write + review fix 12m",[53,1209],{"x":1210,"y":1186,"width":1211,"height":68,"rx":1187,"fill":124,"opacity":1203,"stroke":125,"style":82},"558","100",[65,1213,1215],{"x":1214,"y":1191,"fill":69,"style":92},"608","rebuild 5m",[65,1217,1219],{"x":1218,"y":1191,"fill":125,"style":1175},"664","34m",[65,1221,1222],{"x":1173,"y":1202,"fill":81,"style":1175},"Roll back",[269,1224],{"x1":1179,"y1":1225,"x2":1181,"y2":1225,"stroke":1182,"style":1183},"186",[53,1227],{"x":1179,"y":1228,"width":1174,"height":68,"rx":1187,"fill":110,"opacity":111,"stroke":112,"style":82},"172",[65,1230,1192],{"x":1190,"y":1231,"fill":69,"style":92},"191",[53,1233],{"x":119,"y":1228,"width":1234,"height":68,"rx":1187,"fill":79,"opacity":1235,"stroke":81,"style":82},"46","0.22",[65,1237,1239],{"x":1238,"y":1231,"fill":69,"style":92},"249","decide",[53,1241],{"x":1242,"y":1228,"width":74,"height":68,"rx":1187,"fill":79,"opacity":1235,"stroke":81,"style":82},"278",[65,1244,1246],{"x":1245,"y":1231,"fill":69,"style":92},"308","flip",[65,1248,1250],{"x":1249,"y":1231,"fill":81,"style":1175},"348","6m — site restored",[53,1252],{"x":1253,"y":1228,"width":1254,"height":68,"rx":1187,"fill":1182,"opacity":1255,"stroke":91,"style":82},"500","210","0.5",[65,1257,1259],{"x":1258,"y":1231,"fill":69,"style":92},"605","diagnose calmly, off the clock",[65,1261,1264],{"x":1173,"y":1262,"fill":91,"style":1263},"252","font-size:11px","Detection time is identical in both — the difference is entirely what happens after the decision",[65,1266,1268],{"x":1173,"y":1267,"fill":91,"style":1263},"274","Rolling back does not skip the diagnosis; it moves it out of the incident",[135,1270,1271],{},"The argument for rolling back first is not that diagnosis is unimportant — it is that diagnosis is slower than a pointer flip, and readers are looking at the broken site the whole time.",[14,1273,1274],{},"Rehearse it. A rollback nobody has performed is a hypothesis. Once a quarter, roll production back one deployment on purpose, confirm the site is correct, and roll forward again — the whole exercise takes five minutes and is the difference between a documented procedure and a working one.",[139,1276,1278],{"id":1277},"decide-what-safe-to-deploy-means","Decide What \"Safe to Deploy\" Means",[14,1280,1281],{},"A deploy policy that treats every change identically is either too slow for a typo fix or too loose for a template change. Classify changes and let the classification decide the ceremony.",[14,1283,1284,1287],{},[227,1285,1286],{},"Content-only changes"," — prose edits, a new article, a corrected number. These touch one page's output. A build gate plus a smoke test is enough, and they can deploy on merge, continuously, at any hour.",[14,1289,1290,1293],{},[227,1291,1292],{},"Template and layout changes"," — a partial, a stylesheet, a component. These touch every page that uses them, so a single broken partial is a site-wide incident. These deserve a preview review, a visual check on at least one page per template, and deployment during hours when someone is watching.",[14,1295,1296,1299],{},[227,1297,1298],{},"Structural changes"," — routing, URL scheme, generator upgrade, dependency bumps that affect the build. These can break things no page-level check will notice. Stage them behind a canary, deploy them early in the week, and never batch them with content.",[258,1301,1305],{"className":1302,"code":1303,"language":1304,"meta":263,"style":263},"language-yaml shiki shiki-themes github-light github-dark","# Classify from the diff and gate accordingly\n- name: Classify change\n  id: classify\n  run: |\n    if git diff --name-only origin\u002Fmain | grep -qE '^(layouts|src\u002Fcomponents|src\u002Fstyles)\u002F'; then\n      echo \"tier=template\" >> \"$GITHUB_OUTPUT\"\n    elif git diff --name-only origin\u002Fmain | grep -qE '^(config|package.json|astro.config)'; then\n      echo \"tier=structural\" >> \"$GITHUB_OUTPUT\"\n    else\n      echo \"tier=content\" >> \"$GITHUB_OUTPUT\"\n    fi\n","yaml",[221,1306,1307,1312,1326,1336,1346,1351,1356,1361,1366,1371,1376],{"__ignoreMap":263},[267,1308,1309],{"class":269,"line":270},[267,1310,1311],{"class":273},"# Classify from the diff and gate accordingly\n",[267,1313,1314,1317,1320,1323],{"class":269,"line":277},[267,1315,1316],{"class":280},"- ",[267,1318,1319],{"class":393},"name",[267,1321,1322],{"class":280},": ",[267,1324,1325],{"class":295},"Classify change\n",[267,1327,1328,1331,1333],{"class":269,"line":309},[267,1329,1330],{"class":393},"  id",[267,1332,1322],{"class":280},[267,1334,1335],{"class":295},"classify\n",[267,1337,1338,1341,1343],{"class":269,"line":332},[267,1339,1340],{"class":393},"  run",[267,1342,1322],{"class":280},[267,1344,1345],{"class":284},"|\n",[267,1347,1348],{"class":269,"line":352},[267,1349,1350],{"class":295},"    if git diff --name-only origin\u002Fmain | grep -qE '^(layouts|src\u002Fcomponents|src\u002Fstyles)\u002F'; then\n",[267,1352,1353],{"class":269,"line":569},[267,1354,1355],{"class":295},"      echo \"tier=template\" >> \"$GITHUB_OUTPUT\"\n",[267,1357,1358],{"class":269,"line":588},[267,1359,1360],{"class":295},"    elif git diff --name-only origin\u002Fmain | grep -qE '^(config|package.json|astro.config)'; then\n",[267,1362,1363],{"class":269,"line":608},[267,1364,1365],{"class":295},"      echo \"tier=structural\" >> \"$GITHUB_OUTPUT\"\n",[267,1367,1368],{"class":269,"line":614},[267,1369,1370],{"class":295},"    else\n",[267,1372,1373],{"class":269,"line":621},[267,1374,1375],{"class":295},"      echo \"tier=content\" >> \"$GITHUB_OUTPUT\"\n",[267,1377,1378],{"class":269,"line":638},[267,1379,1380],{"class":295},"    fi\n",[14,1382,1383],{},"Writing the tiers down is most of the value. Teams without them tend to apply template-level caution to typo fixes, which slows publishing until people start batching changes — and a batched deploy is exactly the thing that makes an incident hard to diagnose, because five changes went out at once.",[14,1385,1386,1387,1391],{},"The corollary is that small, frequent content deploys are a safety feature rather than a risk. When one commit is one deploy, the rollback target is obvious and the blast radius is a single page. This is the same argument for keeping build times low enough that continuous deployment is comfortable, which is why the work in ",[21,1388,1390],{"href":1389},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002F","Incremental Builds and Build Caching for SSGs"," pays off in reliability and not only in speed.",[139,1393,1395],{"id":1394},"detect-automatically-decide-deliberately","Detect Automatically, Decide Deliberately",[14,1397,1398],{},"Automatic rollback on a failed smoke test is attractive and worth a moment's thought. It works well when the check is unambiguous — the homepage returns 500, an asset is missing — and badly when it is a threshold, because a flaky Lighthouse run should not revert a correct deploy.",[14,1400,1401],{},"A good split:",[1403,1404,1405,1412],"ul",{},[1406,1407,1408,1411],"li",{},[227,1409,1410],{},"Automatic rollback",": hard failures only. Non-200 on a critical path, missing referenced asset, sitemap that does not parse.",[1406,1413,1414,1417],{},[227,1415,1416],{},"Alert, do not act",": performance budgets, visual diffs, link-check warnings. A human decides.",[258,1419,1421],{"className":1302,"code":1420,"language":1304,"meta":263,"style":263},"# .github\u002Fworkflows\u002Fdeploy.yml (excerpt)\n- name: Deploy\n  id: deploy\n  run: npx wrangler deploy --json | tee deploy.json\n\n- name: Smoke test production\n  id: smoke\n  run: node scripts\u002Fsmoke.mjs https:\u002F\u002Fexample.com\n  continue-on-error: true\n\n- name: Roll back on hard failure\n  if: steps.smoke.outcome == 'failure'\n  run: |\n    PREV=$(npx wrangler deployments list --json | jq -r '.[1].id')\n    npx wrangler rollback \"$PREV\" --message \"auto-rollback: smoke test failed\"\n    exit 1\n",[221,1422,1423,1428,1439,1448,1457,1461,1472,1481,1490,1500,1504,1515,1524,1532,1537,1542],{"__ignoreMap":263},[267,1424,1425],{"class":269,"line":270},[267,1426,1427],{"class":273},"# .github\u002Fworkflows\u002Fdeploy.yml (excerpt)\n",[267,1429,1430,1432,1434,1436],{"class":269,"line":277},[267,1431,1316],{"class":280},[267,1433,1319],{"class":393},[267,1435,1322],{"class":280},[267,1437,1438],{"class":295},"Deploy\n",[267,1440,1441,1443,1445],{"class":269,"line":309},[267,1442,1330],{"class":393},[267,1444,1322],{"class":280},[267,1446,1447],{"class":295},"deploy\n",[267,1449,1450,1452,1454],{"class":269,"line":332},[267,1451,1340],{"class":393},[267,1453,1322],{"class":280},[267,1455,1456],{"class":295},"npx wrangler deploy --json | tee deploy.json\n",[267,1458,1459],{"class":269,"line":352},[267,1460,618],{"emptyLinePlaceholder":617},[267,1462,1463,1465,1467,1469],{"class":269,"line":569},[267,1464,1316],{"class":280},[267,1466,1319],{"class":393},[267,1468,1322],{"class":280},[267,1470,1471],{"class":295},"Smoke test production\n",[267,1473,1474,1476,1478],{"class":269,"line":588},[267,1475,1330],{"class":393},[267,1477,1322],{"class":280},[267,1479,1480],{"class":295},"smoke\n",[267,1482,1483,1485,1487],{"class":269,"line":608},[267,1484,1340],{"class":393},[267,1486,1322],{"class":280},[267,1488,1489],{"class":295},"node scripts\u002Fsmoke.mjs https:\u002F\u002Fexample.com\n",[267,1491,1492,1495,1497],{"class":269,"line":614},[267,1493,1494],{"class":393},"  continue-on-error",[267,1496,1322],{"class":280},[267,1498,1499],{"class":299},"true\n",[267,1501,1502],{"class":269,"line":621},[267,1503,618],{"emptyLinePlaceholder":617},[267,1505,1506,1508,1510,1512],{"class":269,"line":638},[267,1507,1316],{"class":280},[267,1509,1319],{"class":393},[267,1511,1322],{"class":280},[267,1513,1514],{"class":295},"Roll back on hard failure\n",[267,1516,1517,1519,1521],{"class":269,"line":669},[267,1518,718],{"class":393},[267,1520,1322],{"class":280},[267,1522,1523],{"class":295},"steps.smoke.outcome == 'failure'\n",[267,1525,1526,1528,1530],{"class":269,"line":695},[267,1527,1340],{"class":393},[267,1529,1322],{"class":280},[267,1531,1345],{"class":284},[267,1533,1534],{"class":269,"line":715},[267,1535,1536],{"class":295},"    PREV=$(npx wrangler deployments list --json | jq -r '.[1].id')\n",[267,1538,1539],{"class":269,"line":773},[267,1540,1541],{"class":295},"    npx wrangler rollback \"$PREV\" --message \"auto-rollback: smoke test failed\"\n",[267,1543,1544],{"class":269,"line":792},[267,1545,1546],{"class":295},"    exit 1\n",[14,1548,1549,1550,1553],{},"The ",[221,1551,1552],{},"exit 1"," at the end matters: the pipeline must end red even though the site is healthy, or the next person to look will believe the deploy succeeded.",[139,1555,1557],{"id":1556},"watch-the-first-ten-minutes","Watch the First Ten Minutes",[14,1559,1560],{},"Most bad deploys announce themselves quickly if you are looking at the right things. Three signals cover almost everything on a static site:",[14,1562,1563,1566],{},[227,1564,1565],{},"404 rate at the edge."," A structural break — a changed URL scheme, a missing redirect file — shows up within seconds as a spike. This is the single most valuable alert a static site can have.",[14,1568,1569,1572,1573,749],{},[227,1570,1571],{},"Client-side error rate."," A missing or mismatched script fires errors on every page view. Even a minimal error beacon catches this; the collection mechanics are the same as in ",[21,1574,1576],{"href":1575},"\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",[14,1578,1579,1582],{},[227,1580,1581],{},"Cache hit ratio."," A sudden drop usually means asset filenames changed in a way that invalidated everything, which is a performance incident even when the site is correct.",[14,1584,1585],{},"Watch them for ten minutes after every production deploy, and give the person who deployed a link to the dashboard rather than expecting them to find it. If nothing has moved in ten minutes, a static deploy is almost certainly fine — there is no slow-burn resource leak to worry about.",[139,1587,1589],{"id":1588},"staged-rollouts-for-structural-changes","Staged Rollouts for Structural Changes",[14,1591,1592],{},"Content edits do not need a canary; a redesign, a routing change or a generator upgrade does. On a static host the mechanism is an edge routing rule rather than a deployment platform, which makes it cheap enough to use whenever the change touches every page.",[32,1594,1595,1698],{},[35,1596,43,1600,43,1603,43,1606,43,1608,43,1684],{"viewBox":1153,"role":38,"ariaLabelledBy":1597,"xmlns":42},[1598,1599],"rb-canary-title","rb-canary-desc",[45,1601,1602],{"id":1598},"Edge-routed canary for a static deployment",[49,1604,1605],{"id":1599},"Incoming requests reach an edge worker that sends five percent to the new deployment and ninety-five percent to the current one, with a sticky cookie so a reader stays on one version. Error rate and vitals from each group are compared before the split is widened to fifty and then a hundred percent.",[53,1607],{"x":55,"y":55,"width":56,"height":1165,"fill":58},[60,1609,63,1610,63,1613,63,1615,63,1621,63,1625,63,1629,63,1633,63,1636,63,1642,63,1647,63,1651,63,1654,63,1658,63,1662,63,1676,63,1680,43],{"style":62},[65,1611,1612],{"x":67,"y":68,"fill":69,"style":70},"Five percent first, then decide with data",[53,1614],{"x":1173,"y":96,"width":101,"height":74,"rx":78,"fill":1182,"opacity":1255,"stroke":91,"style":82},[65,1616,1620],{"x":1617,"y":1618,"fill":69,"style":1619},"110","144","font-size:12px;font-weight:700;text-anchor:middle","Readers",[65,1622,1624],{"x":1617,"y":1623,"fill":91,"style":92},"164","all traffic",[53,1626],{"x":1627,"y":96,"width":1179,"height":74,"rx":78,"fill":1628,"opacity":98,"stroke":1628,"style":82},"230","#6a4c93",[65,1630,1632],{"x":1631,"y":1618,"fill":69,"style":1619},"305","Edge worker",[65,1634,1635],{"x":1631,"y":1623,"fill":91,"style":92},"sticky by cookie",[53,1637],{"x":1638,"y":1639,"width":1640,"height":74,"rx":78,"fill":79,"opacity":1641,"stroke":81,"style":82},"440","62","280","0.16",[65,1643,1646],{"x":1644,"y":1645,"fill":69,"style":1619},"580","88","Current deployment · 95%",[65,1648,1650],{"x":1644,"y":1649,"fill":91,"style":92},"108","known good, unchanged",[53,1652],{"x":1638,"y":1653,"width":1640,"height":74,"rx":78,"fill":110,"opacity":111,"stroke":112,"style":82},"174",[65,1655,1657],{"x":1644,"y":1656,"fill":69,"style":1619},"200","New deployment · 5%",[65,1659,1661],{"x":1644,"y":1660,"fill":91,"style":92},"220","watched for 15 minutes",[60,1663,1665,1666,1665,1670,1665,1673,63],{"stroke":91,"fill":1664,"style":1183},"none","\n      ",[652,1667],{"d":1668,"style":1669},"M182 148 L228 148","marker-end:url(#rb-canary-arrow)",[652,1671],{"d":1672,"style":1669},"M382 138 L438 100",[652,1674],{"d":1675,"style":1669},"M382 158 L438 196",[65,1677,1679],{"x":1173,"y":1678,"fill":91,"style":1263},"268","Widen to 50% then 100% only when error rate and vitals match the control group",[65,1681,1683],{"x":1173,"y":1682,"fill":91,"style":1263},"288","Aborting is the same one-line change that started it — set the split back to zero",[1685,1686,63,1687,43],"defs",{},[1688,1689,1665,1695,63],"marker",{"id":1690,"viewBox":1691,"refX":1692,"refY":1187,"markerWidth":1693,"markerHeight":1693,"orient":1694},"rb-canary-arrow","0 0 10 10","8","7","auto-start-reverse",[652,1696],{"d":1697,"fill":91},"M0 0 L10 5 L0 10 z",[135,1699,1700],{},"The sticky cookie matters: without it a reader flips between two versions as they navigate, which produces confusing bug reports and cache noise.",[258,1702,1704],{"className":452,"code":1703,"language":454,"meta":263,"style":263},"\u002F\u002F Edge canary: 5% to the new deployment, sticky per reader\nexport default {\n  async fetch(request, env) {\n    const cookie = request.headers.get('cookie') || '';\n    let bucket = cookie.match(\u002F(?:^|;\\s*)rel=(new|old)\u002F)?.[1];\n    if (!bucket) bucket = Math.random() \u003C env.CANARY_SHARE ? 'new' : 'old';\n\n    const origin = bucket === 'new' ? env.NEW_ORIGIN : env.CURRENT_ORIGIN;\n    const url = new URL(request.url);\n    const res = await fetch(new Request(new URL(url.pathname + url.search, origin), request));\n    const out = new Response(res.body, res);\n    out.headers.append('set-cookie', `rel=${bucket}; Path=\u002F; Max-Age=3600; SameSite=Lax`);\n    out.headers.set('x-release', bucket);\n    return out;\n  },\n};\n",[221,1705,1706,1711,1722,1743,1774,1827,1870,1874,1906,1924,1958,1975,2001,2016,2024,2029],{"__ignoreMap":263},[267,1707,1708],{"class":269,"line":270},[267,1709,1710],{"class":273},"\u002F\u002F Edge canary: 5% to the new deployment, sticky per reader\n",[267,1712,1713,1716,1719],{"class":269,"line":277},[267,1714,1715],{"class":284},"export",[267,1717,1718],{"class":284}," default",[267,1720,1721],{"class":280}," {\n",[267,1723,1724,1727,1729,1731,1735,1737,1740],{"class":269,"line":309},[267,1725,1726],{"class":284},"  async",[267,1728,683],{"class":291},[267,1730,487],{"class":280},[267,1732,1734],{"class":1733},"s4XuR","request",[267,1736,502],{"class":280},[267,1738,1739],{"class":1733},"env",[267,1741,1742],{"class":280},") {\n",[267,1744,1745,1747,1750,1752,1755,1758,1760,1763,1766,1769,1772],{"class":269,"line":332},[267,1746,917],{"class":284},[267,1748,1749],{"class":299}," cookie",[267,1751,472],{"class":284},[267,1753,1754],{"class":280}," request.headers.",[267,1756,1757],{"class":291},"get",[267,1759,487],{"class":280},[267,1761,1762],{"class":295},"'cookie'",[267,1764,1765],{"class":280},") ",[267,1767,1768],{"class":284},"||",[267,1770,1771],{"class":295}," ''",[267,1773,635],{"class":280},[267,1775,1776,1779,1782,1784,1787,1790,1792,1794,1797,1800,1803,1806,1809,1812,1814,1817,1819,1822,1825],{"class":269,"line":352},[267,1777,1778],{"class":284},"    let",[267,1780,1781],{"class":280}," bucket ",[267,1783,285],{"class":284},[267,1785,1786],{"class":280}," cookie.",[267,1788,1789],{"class":291},"match",[267,1791,487],{"class":280},[267,1793,490],{"class":295},[267,1795,1796],{"class":876},"(?:",[267,1798,1799],{"class":284},"^|",[267,1801,1802],{"class":876},";",[267,1804,1805],{"class":299},"\\s",[267,1807,1808],{"class":284},"*",[267,1810,1811],{"class":876},")rel=(new",[267,1813,880],{"class":284},[267,1815,1816],{"class":876},"old)",[267,1818,490],{"class":295},[267,1820,1821],{"class":280},")?.[",[267,1823,1824],{"class":299},"1",[267,1826,611],{"class":280},[267,1828,1829,1831,1833,1835,1838,1840,1843,1846,1849,1851,1854,1857,1860,1863,1865,1868],{"class":269,"line":569},[267,1830,795],{"class":284},[267,1832,644],{"class":280},[267,1834,800],{"class":284},[267,1836,1837],{"class":280},"bucket) bucket ",[267,1839,285],{"class":284},[267,1841,1842],{"class":280}," Math.",[267,1844,1845],{"class":291},"random",[267,1847,1848],{"class":280},"() ",[267,1850,390],{"class":284},[267,1852,1853],{"class":280}," env.",[267,1855,1856],{"class":299},"CANARY_SHARE",[267,1858,1859],{"class":284}," ?",[267,1861,1862],{"class":295}," 'new'",[267,1864,1010],{"class":284},[267,1866,1867],{"class":295}," 'old'",[267,1869,635],{"class":280},[267,1871,1872],{"class":269,"line":588},[267,1873,618],{"emptyLinePlaceholder":617},[267,1875,1876,1878,1881,1883,1885,1888,1890,1892,1894,1897,1899,1901,1904],{"class":269,"line":608},[267,1877,917],{"class":284},[267,1879,1880],{"class":299}," origin",[267,1882,472],{"class":284},[267,1884,1781],{"class":280},[267,1886,1887],{"class":284},"===",[267,1889,1862],{"class":295},[267,1891,1859],{"class":284},[267,1893,1853],{"class":280},[267,1895,1896],{"class":299},"NEW_ORIGIN",[267,1898,1010],{"class":284},[267,1900,1853],{"class":280},[267,1902,1903],{"class":299},"CURRENT_ORIGIN",[267,1905,635],{"class":280},[267,1907,1908,1910,1913,1915,1918,1921],{"class":269,"line":614},[267,1909,917],{"class":284},[267,1911,1912],{"class":299}," url",[267,1914,472],{"class":284},[267,1916,1917],{"class":284}," new",[267,1919,1920],{"class":291}," URL",[267,1922,1923],{"class":280},"(request.url);\n",[267,1925,1926,1928,1930,1932,1934,1936,1938,1941,1944,1946,1948,1950,1953,1955],{"class":269,"line":621},[267,1927,917],{"class":284},[267,1929,675],{"class":299},[267,1931,472],{"class":284},[267,1933,680],{"class":284},[267,1935,683],{"class":291},[267,1937,487],{"class":280},[267,1939,1940],{"class":284},"new",[267,1942,1943],{"class":291}," Request",[267,1945,487],{"class":280},[267,1947,1940],{"class":284},[267,1949,1920],{"class":291},[267,1951,1952],{"class":280},"(url.pathname ",[267,1954,689],{"class":284},[267,1956,1957],{"class":280}," url.search, origin), request));\n",[267,1959,1960,1962,1965,1967,1969,1972],{"class":269,"line":638},[267,1961,917],{"class":284},[267,1963,1964],{"class":299}," out",[267,1966,472],{"class":284},[267,1968,1917],{"class":284},[267,1970,1971],{"class":291}," Response",[267,1973,1974],{"class":280},"(res.body, res);\n",[267,1976,1977,1980,1983,1985,1988,1990,1993,1996,1999],{"class":269,"line":669},[267,1978,1979],{"class":280},"    out.headers.",[267,1981,1982],{"class":291},"append",[267,1984,487],{"class":280},[267,1986,1987],{"class":295},"'set-cookie'",[267,1989,502],{"class":280},[267,1991,1992],{"class":295},"`rel=${",[267,1994,1995],{"class":280},"bucket",[267,1997,1998],{"class":295},"}; Path=\u002F; Max-Age=3600; SameSite=Lax`",[267,2000,508],{"class":280},[267,2002,2003,2005,2008,2010,2013],{"class":269,"line":695},[267,2004,1979],{"class":280},[267,2006,2007],{"class":291},"set",[267,2009,487],{"class":280},[267,2011,2012],{"class":295},"'x-release'",[267,2014,2015],{"class":280},", bucket);\n",[267,2017,2018,2021],{"class":269,"line":715},[267,2019,2020],{"class":284},"    return",[267,2022,2023],{"class":280}," out;\n",[267,2025,2026],{"class":269,"line":773},[267,2027,2028],{"class":280},"  },\n",[267,2030,2031],{"class":269,"line":792},[267,2032,2033],{"class":280},"};\n",[14,2035,1549,2036,2039],{},[221,2037,2038],{},"x-release"," header is what makes the canary measurable: field data and edge logs can be grouped by it, so \"is the new version worse?\" becomes a query rather than an opinion. Fifteen minutes at five per cent is usually enough to catch a structural break; a week is enough to catch a subtle one, and there is no cost to leaving a canary running longer on a site with no server bill.",[14,2041,2042],{},"Abort by setting the share to zero. That is a configuration change with no deploy, which makes it the fastest reversal available — faster even than a rollback, because nothing has to be promoted.",[139,2044,2046],{"id":2045},"common-pitfalls","Common Pitfalls",[1403,2048,2049,2059,2065,2071,2077,2083],{},[1406,2050,2051,2054,2055,2058],{},[227,2052,2053],{},"Sync-style deploys."," ",[221,2056,2057],{},"aws s3 sync"," into a live bucket is not atomic and will serve mixed builds. Upload to a versioned prefix and flip.",[1406,2060,2061,2064],{},[227,2062,2063],{},"Rollback that rebuilds."," If restoring the previous version requires a build, you are minutes away from recovery instead of seconds. Keep artifacts.",[1406,2066,2067,2070],{},[227,2068,2069],{},"No build stamp."," Without a visible SHA in the output, the first minutes of an incident go to establishing what is even live.",[1406,2072,2073,2076],{},[227,2074,2075],{},"Smoke tests against the build directory."," They pass while production serves something else entirely. Test the URL.",[1406,2078,2079,2082],{},[227,2080,2081],{},"Purging the whole cache on rollback."," Immutable hashed assets never need purging; over-purging turns a rollback into a traffic spike against the origin.",[1406,2084,2085,2088],{},[227,2086,2087],{},"Never rehearsing."," The first real use of a rollback should not be the first use of a rollback.",[139,2090,2092],{"id":2091},"key-takeaways","Key Takeaways",[1403,2094,2095,2098,2101,2104,2107,2110],{},[1406,2096,2097],{},"Insist on atomic deploys; build them yourself with a versioned upload and a symlink swap if your host will not.",[1406,2099,2100],{},"Stamp every build with its commit SHA and retain deployments for at least thirty days.",[1406,2102,2103],{},"Smoke test the deployed URL — including that referenced assets exist — before and after promotion.",[1406,2105,2106],{},"Make rollback one command with no build step, document it where people look, and rehearse it quarterly.",[1406,2108,2109],{},"Automate rollback only for unambiguous failures; alert on everything else and let a human decide.",[1406,2111,2112],{},"Watch 404 rate, client errors and cache hit ratio for ten minutes after each deploy.",[139,2114,2116],{"id":2115},"faq","FAQ",[2118,2119,2121],"h3",{"id":2120},"why-does-a-static-site-need-a-rollback-plan-at-all","Why does a static site need a rollback plan at all?",[14,2123,2124],{},"Because a bad build is indistinguishable from a bad server. A template change that drops a navigation partial, a content edit that breaks a schema, or a deploy that half-uploads leaves readers with a broken site, and the only cure is putting the previous output back quickly. Static hosting makes that fast, but only if the previous output still exists.",[2118,2126,2128],{"id":2127},"what-makes-a-deploy-atomic","What makes a deploy atomic?",[14,2130,2131],{},"Readers see either the whole old version or the whole new one, never a mixture. Hosts achieve this by uploading a complete build to a new immutable location and then flipping a pointer. Syncing files into a live directory is the opposite, and it produces windows where new HTML references assets that have not uploaded yet.",[2118,2133,2135],{"id":2134},"how-fast-should-a-rollback-be","How fast should a rollback be?",[14,2137,2138],{},"Under a minute from decision to restored site. That is achievable on any host that keeps previous deployments addressable, because a rollback is a pointer change rather than a rebuild. If your rollback requires a rebuild, you do not have a rollback, you have a re-deploy.",[2118,2140,2142],{"id":2141},"what-should-smoke-tests-check-after-a-deploy","What should smoke tests check after a deploy?",[14,2144,2145],{},"The handful of things whose failure means the site is unusable: the homepage and one page of each template return 200, the navigation and footer render, an internal link resolves, the sitemap parses, and the built asset referenced by the HTML actually exists. Five checks that take ten seconds catch most catastrophic deploys.",[2118,2147,2149],{"id":2148},"do-i-need-a-canary-or-staged-rollout-for-a-static-site","Do I need a canary or staged rollout for a static site?",[14,2151,2152],{},"Rarely for content changes, and it is worth it for structural ones. Routing a small percentage of traffic to the new deployment for a few minutes catches a broken template before it reaches everyone, and on a static host the mechanism is an edge routing rule rather than infrastructure.",[2118,2154,2156],{"id":2155},"how-many-previous-builds-should-i-retain","How many previous builds should I retain?",[14,2158,2159],{},"Enough to cover the time it takes to notice a problem, which is usually longer than you think. Thirty days of deployments is a reasonable default; the storage cost of static output is negligible compared with the cost of not being able to go back.",[139,2161,2163],{"id":2162},"related","Related",[1403,2165,2166,2174,2181,2186,2193,2198],{},[1406,2167,2168,2054,2171,2173],{},[227,2169,2170],{},"Parent:",[21,2172,24],{"href":23}," — where deploy safety fits the pipeline.",[1406,2175,2176,2180],{},[21,2177,2179],{"href":2178},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frolling-back-a-bad-static-deploy-in-under-a-minute\u002F","Rolling Back a Bad Static Deploy in Under a Minute"," — the runbook, per host.",[1406,2182,2183,2185],{},[21,2184,1044],{"href":1043}," — the check set in full.",[1406,2187,2188,2192],{},[21,2189,2191],{"href":2190},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads\u002F","Atomic Deploys vs Incremental Uploads"," — why the upload strategy decides your worst case.",[1406,2194,2195,2197],{},[21,2196,29],{"href":28}," — catching problems before they are deployed at all.",[1406,2199,2200,2202],{},[21,2201,441],{"href":440}," — immutable assets are what make a rollback safe.",[2204,2205,2206],"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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .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}html pre.shiki code .snhLl, html code.shiki .snhLl{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":263,"searchDepth":277,"depth":277,"links":2208},[2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2227],{"id":141,"depth":277,"text":142},{"id":364,"depth":277,"text":365},{"id":445,"depth":277,"text":446},{"id":1048,"depth":277,"text":1049},{"id":1277,"depth":277,"text":1278},{"id":1394,"depth":277,"text":1395},{"id":1556,"depth":277,"text":1557},{"id":1588,"depth":277,"text":1589},{"id":2045,"depth":277,"text":2046},{"id":2091,"depth":277,"text":2092},{"id":2115,"depth":277,"text":2116,"children":2220},[2221,2222,2223,2224,2225,2226],{"id":2120,"depth":309,"text":2121},{"id":2127,"depth":309,"text":2128},{"id":2134,"depth":309,"text":2135},{"id":2141,"depth":309,"text":2142},{"id":2148,"depth":309,"text":2149},{"id":2155,"depth":309,"text":2156},{"id":2162,"depth":277,"text":2163},[2229,2231,2232],{"name":2230,"item":490},"Home",{"name":24,"item":23},{"name":5,"item":2233},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002F","2026-08-01","Atomic deploys, immutable build artifacts, smoke tests against the preview URL and a one-command rollback — the safety net that makes shipping a static site boring.","md",[2238,2239,2240,2241,2244,2245],{"q":2121,"a":2124},{"q":2128,"a":2131},{"q":2135,"a":2138},{"q":2142,"a":2242},{"The handful of things whose failure means the site is unusable":2243},"the homepage and one page of each template return 200, the navigation and footer render, an internal link resolves, the sitemap parses, and the built asset referenced by the HTML actually exists. Five checks that take ten seconds catch most catastrophic deploys.",{"q":2149,"a":2152},{"q":2156,"a":2159},{},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites",{"title":5,"description":2235},"production-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Findex","guide","UVMb1S0x_iSyaXvxeD7u6NORCX5mHeg7p3zOaoNHJcQ",[2253,2256,2259,2262,2265,2268,2271,2274,2277,2280,2283,2286,2289,2292,2295,2298,2301,2304,2307,2310,2313,2316,2319,2322,2325,2328,2331,2333,2336,2339,2342,2345,2348,2351,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2386,2389,2392,2395,2398,2401,2404,2407,2410,2413,2416,2419,2422,2425,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2457,2460,2463,2466,2469,2472,2475,2478,2481,2484,2486,2488,2489,2491],{"path":2254,"title":2255},"\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":2257,"title":2258},"\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":2260,"title":2261},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":2263,"title":2264},"\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":2266,"title":2267},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":2269,"title":2270},"\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":2272,"title":2273},"\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":2275,"title":2276},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":2278,"title":2279},"\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":2281,"title":2282},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":2284,"title":2285},"\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":2287,"title":2288},"\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":2290,"title":2291},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":2293,"title":2294},"\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":2296,"title":2297},"\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":2299,"title":2300},"\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":2302,"title":2303},"\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":2305,"title":2306},"\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":2308,"title":2309},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":2311,"title":2312},"\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":2314,"title":2315},"\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":2317,"title":2318},"\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":2320,"title":2321},"\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":2323,"title":2324},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":2326,"title":2327},"\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":2329,"title":2330},"\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":2332,"title":441},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs",{"path":2334,"title":2335},"\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":2337,"title":2338},"\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":2340,"title":2341},"\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":2343,"title":2344},"\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":2346,"title":2347},"\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":2349,"title":2350},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":2352,"title":1576},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Fmeasuring-cls-in-the-field-with-web-vitals-js",{"path":2354,"title":2355},"\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":2357,"title":2358},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":2360,"title":2361},"\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":2363,"title":2364},"\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":2366,"title":2367},"\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":2369,"title":2370},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":2372,"title":2373},"\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":2375,"title":2376},"\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":2378,"title":2379},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":2381,"title":2382},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":2384,"title":2385},"\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":2387,"title":2388},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":2390,"title":2391},"\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":2393,"title":2394},"\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":2396,"title":2397},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":2399,"title":2400},"\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":2402,"title":2403},"\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":2405,"title":2406},"\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":2408,"title":2409},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":2411,"title":2412},"\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":2414,"title":2415},"\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":2417,"title":2418},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":2420,"title":2421},"\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":2423,"title":2424},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":2426,"title":2427},"\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":2429,"title":2430},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":2432,"title":2433},"\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":2435,"title":2436},"\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":2438,"title":2439},"\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":2441,"title":2442},"\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":2444,"title":2445},"\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":2447,"title":2448},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":2450,"title":2451},"\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":2453,"title":2454},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":2456,"title":1390},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs",{"path":2458,"title":2459},"\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":2461,"title":2462},"\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":2464,"title":2465},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":2467,"title":2468},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":2470,"title":2471},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":2473,"title":2474},"\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":2476,"title":2477},"\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":2479,"title":2480},"\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":2482,"title":2483},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":2485,"title":29},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests",{"path":2487,"title":2191},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads",{"path":2247,"title":5},{"path":2490,"title":2179},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frolling-back-a-bad-static-deploy-in-under-a-minute",{"path":2492,"title":1044},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url",1785611670808]