[{"data":1,"prerenderedAt":2434},["ShallowReactive",2],{"page:\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url":3,"all-docs-nav":2190},{"id":4,"title":5,"body":6,"breadcrumb":2164,"dateModified":2173,"datePublished":2173,"description":2174,"extension":2175,"faq":2176,"meta":2184,"navigation":387,"path":2185,"seo":2186,"slug":12,"stem":2187,"type":2188,"__hash__":2189},"content\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url\u002Findex.md","Running Smoke Tests Against a Preview URL",{"type":7,"value":8,"toc":2145},"minimark",[9,13,31,39,44,66,70,222,226,229,236,246,264,270,276,1488,1492,1640,1643,1775,1779,1782,1789,1804,1807,1811,1814,1879,1964,1967,1971,1974,1981,1987,1993,1996,2000,2047,2051,2062,2066,2071,2074,2078,2081,2085,2088,2092,2095,2099,2102,2106,2141],[10,11,5],"h1",{"id":12},"running-smoke-tests-against-a-preview-url",[14,15,16,17,21,22,26,27,30],"p",{},"Build gates check the artifact. Smoke tests check the ",[18,19,20],"em",{},"deployment"," — and there is a whole category of failure that only exists between the two: a redirect rule that swallows a path, a base URL pointing at the wrong environment, a deploy-ignore pattern that excluded the asset directory, a host that serves ",[23,24,25],"code",{},"\u002Fguides"," but not ",[23,28,29],{},"\u002Fguides\u002F",".",[14,32,33,34,30],{},"These failures produce a build that passes every check and a site that is broken for readers. Ten seconds of HTTP requests against the deployed preview catches nearly all of them. This guide is the check set, the wiring, and what to do when one fails. It is part of ",[35,36,38],"a",{"href":37},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002F","Rollbacks and Deploy Safety for Static Sites",[40,41,43],"h2",{"id":42},"prerequisites","Prerequisites",[45,46,47,56,63],"ul",{},[48,49,50,51,55],"li",{},"A per-pull-request preview URL — see ",[35,52,54],{"href":53},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002F","Preview Environments for Pull Requests"," for the deployment side.",[48,57,58,59,62],{},"A CI step that can run Node or ",[23,60,61],{},"curl"," after the preview is live and knows its URL.",[48,64,65],{},"A short list of representative URLs: the homepage plus one page per template.",[40,67,69],{"id":68},"what-belongs-in-a-smoke-test","What Belongs in a Smoke Test",[71,72,73,218],"figure",{},[74,75,82,83,82,87,82,91,82,98],"svg",{"viewBox":76,"role":77,"ariaLabelledBy":78,"xmlns":81},"0 0 760 300","img",[79,80],"smk-scope-title","smk-scope-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[84,85,86],"title",{"id":79},"What each layer of testing owns",[88,89,90],"desc",{"id":80},"Three layers. Build gates own per-page correctness such as links, schema and word counts across every page. Smoke tests own deployment reality on a handful of URLs: status codes, template markers, referenced assets, sitemap and redirects. Monitoring owns behaviour over time, including error rate and vitals.",[92,93],"rect",{"x":94,"y":94,"width":95,"height":96,"fill":97},"0","760","300","#ffffff",[99,100,102,103,102,111,102,122,102,128,102,134,102,140,102,144,102,148,102,152,102,156,102,162,102,166,102,169,102,173,102,176,102,179,102,182,102,185,102,190,102,194,102,197,102,201,102,204,102,207,102,210,102,213,82],"g",{"style":101},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[104,105,110],"text",{"x":106,"y":107,"fill":108,"style":109},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","Three layers, three questions, no overlap",[92,112],{"x":113,"y":114,"width":115,"height":116,"rx":117,"fill":118,"opacity":119,"stroke":120,"style":121},"40","56","220","200","12","#8ac926","0.14","#5a8a16","stroke-width:1.5px",[104,123,127],{"x":124,"y":125,"fill":108,"style":126},"150","82","font-size:13px;font-weight:700;text-anchor:middle","Build gates",[104,129,133],{"x":124,"y":130,"fill":131,"style":132},"104","#556071","font-size:11px;text-anchor:middle","every page, pre-deploy",[104,135,139],{"x":136,"y":137,"fill":108,"style":138},"62","134","font-size:12px","internal links resolve",[104,141,143],{"x":136,"y":142,"fill":108,"style":138},"158","front matter schema",[104,145,147],{"x":136,"y":146,"fill":108,"style":138},"182","no unrendered markup",[104,149,151],{"x":136,"y":150,"fill":108,"style":138},"206","structured data valid",[104,153,155],{"x":136,"y":154,"fill":108,"style":138},"230","performance budget",[92,157],{"x":158,"y":114,"width":115,"height":116,"rx":117,"fill":159,"opacity":160,"stroke":161,"style":121},"280","#ffca3a","0.26","#a97b00",[104,163,165],{"x":164,"y":125,"fill":108,"style":126},"390","Smoke tests",[104,167,168],{"x":164,"y":130,"fill":131,"style":132},"few URLs, post-deploy",[104,170,172],{"x":171,"y":137,"fill":108,"style":138},"302","200 on each template",[104,174,175],{"x":171,"y":142,"fill":108,"style":138},"nav + footer present",[104,177,178],{"x":171,"y":146,"fill":108,"style":138},"referenced assets exist",[104,180,181],{"x":171,"y":150,"fill":108,"style":138},"sitemap parses",[104,183,184],{"x":171,"y":154,"fill":108,"style":138},"one redirect works",[92,186],{"x":187,"y":114,"width":116,"height":116,"rx":117,"fill":188,"opacity":189,"stroke":188,"style":121},"520","#1982c4","0.12",[104,191,193],{"x":192,"y":125,"fill":108,"style":126},"620","Monitoring",[104,195,196],{"x":192,"y":130,"fill":131,"style":132},"production, over time",[104,198,200],{"x":199,"y":137,"fill":108,"style":138},"542","404 rate at the edge",[104,202,203],{"x":199,"y":142,"fill":108,"style":138},"client error beacons",[104,205,206],{"x":199,"y":146,"fill":108,"style":138},"field Core Web Vitals",[104,208,209],{"x":199,"y":150,"fill":108,"style":138},"cache hit ratio",[104,211,212],{"x":199,"y":154,"fill":108,"style":138},"traffic anomalies",[104,214,217],{"x":113,"y":215,"fill":131,"style":216},"286","font-size:11px","A check in the wrong column is either too slow to run often or too late to prevent harm",[219,220,221],"figcaption",{},"Smoke tests are deliberately narrow: a handful of URLs, no browser, no per-page correctness. Their job is to prove the deployment serves a usable site.",[40,223,225],{"id":224},"the-check-set","The Check Set",[14,227,228],{},"Five checks cover the failure modes that reach production:",[14,230,231,235],{},[232,233,234],"strong",{},"1. Status codes."," Every representative URL returns 200 — including the trailing-slash form your site actually links to.",[14,237,238,241,242,245],{},[232,239,240],{},"2. Template markers."," Each page contains the markup that proves its layout rendered: a header class, a footer class, an ",[23,243,244],{},"\u003Ch1>",". A missing partial produces a page that returns 200 and is unusable.",[14,247,248,251,252,255,256,259,260,263],{},[232,249,250],{},"3. Referenced assets exist."," Parse each page's ",[23,253,254],{},"href","\u002F",[23,257,258],{},"src"," attributes and ",[23,261,262],{},"HEAD"," every same-origin asset. This is the check that catches deploy-ignore mistakes and asset-pipeline failures.",[14,265,266,269],{},[232,267,268],{},"4. Sitemap parses and matches."," The sitemap is XML, it contains entries, and a sample of those entries return 200.",[14,271,272,275],{},[232,273,274],{},"5. One redirect resolves."," Pick a known redirect and assert it returns 301 to a URL that returns 200 in one hop.",[277,278,283],"pre",{"className":279,"code":280,"language":281,"meta":282,"style":282},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F scripts\u002Fsmoke.mjs \u003Cbase-url>\nconst base = process.argv[2].replace(\u002F\\\u002F$\u002F, '');\nif (!base) { console.error('usage: smoke.mjs \u003Cbase-url>'); process.exit(2); }\n\nconst PAGES = [\n  { path: '\u002F', must: ['site-header', 'site-footer', '\u003Ch1'] },\n  { path: '\u002Fguides\u002F', must: ['site-header', '\u003Ch1', 'href=\"\u002Fguides\u002F'] },\n  { path: '\u002Fguides\u002Fdeploying\u002F', must: ['site-header', 'site-footer', '\u003Ch1', '\u003C\u002Farticle>'] },\n  { path: '\u002Fabout\u002F', must: ['site-header', '\u003Ch1'] },\n];\nconst REDIRECT = { from: '\u002Fold-docs\u002Fdeploying\u002F', to: '\u002Fguides\u002Fdeploying\u002F' };\n\nlet failures = 0;\nconst fail = (msg) => { console.error(`FAIL ${msg}`); failures++; };\n\nfor (const { path, must } of PAGES) {\n  const res = await fetch(base + path, { redirect: 'manual' });\n  if (res.status !== 200) { fail(`${path} → HTTP ${res.status}`); continue; }\n  const html = await res.text();\n  for (const marker of must) if (!html.includes(marker)) fail(`${path} missing marker ${marker}`);\n\n  const refs = [...html.matchAll(\u002F(?:href|src)=\"(\\\u002F[^\"#?]+\\.(?:css|js|svg|png|avif|webp|woff2))\"\u002Fg)]\n    .map((m) => m[1]);\n  for (const ref of [...new Set(refs)]) {\n    const head = await fetch(base + ref, { method: 'HEAD' });\n    if (!head.ok) fail(`${path} references missing asset ${ref} (${head.status})`);\n  }\n}\n\nconst sm = await fetch(base + '\u002Fsitemap.xml');\nconst smText = sm.ok ? await sm.text() : '';\nif (!sm.ok || !smText.includes('\u003Curlset')) fail(`sitemap.xml → ${sm.status}`);\nconst locs = [...smText.matchAll(\u002F\u003Cloc>([^\u003C]+)\u003C\\\u002Floc>\u002Fg)].map((m) => m[1]);\nif (locs.length \u003C 5) fail(`sitemap has only ${locs.length} entries`);\n\nconst r = await fetch(base + REDIRECT.from, { redirect: 'manual' });\nif (r.status !== 301) fail(`${REDIRECT.from} → ${r.status}, expected 301`);\nelse {\n  const dest = new URL(r.headers.get('location'), base);\n  const destRes = await fetch(dest, { redirect: 'manual' });\n  if (destRes.status !== 200) fail(`${REDIRECT.from} → ${dest.pathname} → ${destRes.status}`);\n}\n\nconsole.log(failures ? `smoke: ${failures} failure(s)` : `smoke: OK (${PAGES.length} pages checked)`);\nprocess.exit(failures ? 1 : 0);\n","javascript","",[23,284,285,294,346,382,389,402,430,453,480,498,504,528,533,551,597,602,634,666,716,736,788,793,883,911,936,963,1005,1011,1017,1022,1045,1079,1128,1193,1231,1236,1263,1307,1316,1346,1367,1416,1421,1426,1467],{"__ignoreMap":282},[286,287,290],"span",{"class":288,"line":289},"line",1,[286,291,293],{"class":292},"sJ8bj","\u002F\u002F scripts\u002Fsmoke.mjs \u003Cbase-url>\n",[286,295,297,301,305,308,312,315,318,322,325,328,332,335,337,340,343],{"class":288,"line":296},2,[286,298,300],{"class":299},"szBVR","const",[286,302,304],{"class":303},"sj4cs"," base",[286,306,307],{"class":299}," =",[286,309,311],{"class":310},"sVt8B"," process.argv[",[286,313,314],{"class":303},"2",[286,316,317],{"class":310},"].",[286,319,321],{"class":320},"sScJk","replace",[286,323,324],{"class":310},"(",[286,326,255],{"class":327},"sZZnC",[286,329,331],{"class":330},"snhLl","\\\u002F",[286,333,334],{"class":299},"$",[286,336,255],{"class":327},[286,338,339],{"class":310},", ",[286,341,342],{"class":327},"''",[286,344,345],{"class":310},");\n",[286,347,349,352,355,358,361,364,366,369,372,375,377,379],{"class":288,"line":348},3,[286,350,351],{"class":299},"if",[286,353,354],{"class":310}," (",[286,356,357],{"class":299},"!",[286,359,360],{"class":310},"base) { console.",[286,362,363],{"class":320},"error",[286,365,324],{"class":310},[286,367,368],{"class":327},"'usage: smoke.mjs \u003Cbase-url>'",[286,370,371],{"class":310},"); process.",[286,373,374],{"class":320},"exit",[286,376,324],{"class":310},[286,378,314],{"class":303},[286,380,381],{"class":310},"); }\n",[286,383,385],{"class":288,"line":384},4,[286,386,388],{"emptyLinePlaceholder":387},true,"\n",[286,390,392,394,397,399],{"class":288,"line":391},5,[286,393,300],{"class":299},[286,395,396],{"class":303}," PAGES",[286,398,307],{"class":299},[286,400,401],{"class":310}," [\n",[286,403,405,408,411,414,417,419,422,424,427],{"class":288,"line":404},6,[286,406,407],{"class":310},"  { path: ",[286,409,410],{"class":327},"'\u002F'",[286,412,413],{"class":310},", must: [",[286,415,416],{"class":327},"'site-header'",[286,418,339],{"class":310},[286,420,421],{"class":327},"'site-footer'",[286,423,339],{"class":310},[286,425,426],{"class":327},"'\u003Ch1'",[286,428,429],{"class":310},"] },\n",[286,431,433,435,438,440,442,444,446,448,451],{"class":288,"line":432},7,[286,434,407],{"class":310},[286,436,437],{"class":327},"'\u002Fguides\u002F'",[286,439,413],{"class":310},[286,441,416],{"class":327},[286,443,339],{"class":310},[286,445,426],{"class":327},[286,447,339],{"class":310},[286,449,450],{"class":327},"'href=\"\u002Fguides\u002F'",[286,452,429],{"class":310},[286,454,456,458,461,463,465,467,469,471,473,475,478],{"class":288,"line":455},8,[286,457,407],{"class":310},[286,459,460],{"class":327},"'\u002Fguides\u002Fdeploying\u002F'",[286,462,413],{"class":310},[286,464,416],{"class":327},[286,466,339],{"class":310},[286,468,421],{"class":327},[286,470,339],{"class":310},[286,472,426],{"class":327},[286,474,339],{"class":310},[286,476,477],{"class":327},"'\u003C\u002Farticle>'",[286,479,429],{"class":310},[286,481,483,485,488,490,492,494,496],{"class":288,"line":482},9,[286,484,407],{"class":310},[286,486,487],{"class":327},"'\u002Fabout\u002F'",[286,489,413],{"class":310},[286,491,416],{"class":327},[286,493,339],{"class":310},[286,495,426],{"class":327},[286,497,429],{"class":310},[286,499,501],{"class":288,"line":500},10,[286,502,503],{"class":310},"];\n",[286,505,507,509,512,514,517,520,523,525],{"class":288,"line":506},11,[286,508,300],{"class":299},[286,510,511],{"class":303}," REDIRECT",[286,513,307],{"class":299},[286,515,516],{"class":310}," { from: ",[286,518,519],{"class":327},"'\u002Fold-docs\u002Fdeploying\u002F'",[286,521,522],{"class":310},", to: ",[286,524,460],{"class":327},[286,526,527],{"class":310}," };\n",[286,529,531],{"class":288,"line":530},12,[286,532,388],{"emptyLinePlaceholder":387},[286,534,536,539,542,545,548],{"class":288,"line":535},13,[286,537,538],{"class":299},"let",[286,540,541],{"class":310}," failures ",[286,543,544],{"class":299},"=",[286,546,547],{"class":303}," 0",[286,549,550],{"class":310},";\n",[286,552,554,556,559,561,563,567,570,573,576,578,580,583,585,588,591,594],{"class":288,"line":553},14,[286,555,300],{"class":299},[286,557,558],{"class":320}," fail",[286,560,307],{"class":299},[286,562,354],{"class":310},[286,564,566],{"class":565},"s4XuR","msg",[286,568,569],{"class":310},") ",[286,571,572],{"class":299},"=>",[286,574,575],{"class":310}," { console.",[286,577,363],{"class":320},[286,579,324],{"class":310},[286,581,582],{"class":327},"`FAIL ${",[286,584,566],{"class":310},[286,586,587],{"class":327},"}`",[286,589,590],{"class":310},"); failures",[286,592,593],{"class":299},"++",[286,595,596],{"class":310},"; };\n",[286,598,600],{"class":288,"line":599},15,[286,601,388],{"emptyLinePlaceholder":387},[286,603,605,608,610,612,615,618,620,623,626,629,631],{"class":288,"line":604},16,[286,606,607],{"class":299},"for",[286,609,354],{"class":310},[286,611,300],{"class":299},[286,613,614],{"class":310}," { ",[286,616,617],{"class":303},"path",[286,619,339],{"class":310},[286,621,622],{"class":303},"must",[286,624,625],{"class":310}," } ",[286,627,628],{"class":299},"of",[286,630,396],{"class":303},[286,632,633],{"class":310},") {\n",[286,635,637,640,643,645,648,651,654,657,660,663],{"class":288,"line":636},17,[286,638,639],{"class":299},"  const",[286,641,642],{"class":303}," res",[286,644,307],{"class":299},[286,646,647],{"class":299}," await",[286,649,650],{"class":320}," fetch",[286,652,653],{"class":310},"(base ",[286,655,656],{"class":299},"+",[286,658,659],{"class":310}," path, { redirect: ",[286,661,662],{"class":327},"'manual'",[286,664,665],{"class":310}," });\n",[286,667,669,672,675,678,681,684,687,689,692,694,697,700,702,705,707,710,713],{"class":288,"line":668},18,[286,670,671],{"class":299},"  if",[286,673,674],{"class":310}," (res.status ",[286,676,677],{"class":299},"!==",[286,679,680],{"class":303}," 200",[286,682,683],{"class":310},") { ",[286,685,686],{"class":320},"fail",[286,688,324],{"class":310},[286,690,691],{"class":327},"`${",[286,693,617],{"class":310},[286,695,696],{"class":327},"} → HTTP ${",[286,698,699],{"class":310},"res",[286,701,30],{"class":327},[286,703,704],{"class":310},"status",[286,706,587],{"class":327},[286,708,709],{"class":310},"); ",[286,711,712],{"class":299},"continue",[286,714,715],{"class":310},"; }\n",[286,717,719,721,724,726,728,731,733],{"class":288,"line":718},19,[286,720,639],{"class":299},[286,722,723],{"class":303}," html",[286,725,307],{"class":299},[286,727,647],{"class":299},[286,729,730],{"class":310}," res.",[286,732,104],{"class":320},[286,734,735],{"class":310},"();\n",[286,737,739,742,744,746,749,752,755,757,759,761,764,767,770,772,774,776,778,781,784,786],{"class":288,"line":738},20,[286,740,741],{"class":299},"  for",[286,743,354],{"class":310},[286,745,300],{"class":299},[286,747,748],{"class":303}," marker",[286,750,751],{"class":299}," of",[286,753,754],{"class":310}," must) ",[286,756,351],{"class":299},[286,758,354],{"class":310},[286,760,357],{"class":299},[286,762,763],{"class":310},"html.",[286,765,766],{"class":320},"includes",[286,768,769],{"class":310},"(marker)) ",[286,771,686],{"class":320},[286,773,324],{"class":310},[286,775,691],{"class":327},[286,777,617],{"class":310},[286,779,780],{"class":327},"} missing marker ${",[286,782,783],{"class":310},"marker",[286,785,587],{"class":327},[286,787,345],{"class":310},[286,789,791],{"class":288,"line":790},21,[286,792,388],{"emptyLinePlaceholder":387},[286,794,796,798,801,803,806,809,811,814,816,818,822,825,828,830,833,836,839,841,844,847,849,852,854,856,858,861,863,866,868,871,873,876,878,880],{"class":288,"line":795},22,[286,797,639],{"class":299},[286,799,800],{"class":303}," refs",[286,802,307],{"class":299},[286,804,805],{"class":310}," [",[286,807,808],{"class":299},"...",[286,810,763],{"class":310},[286,812,813],{"class":320},"matchAll",[286,815,324],{"class":310},[286,817,255],{"class":327},[286,819,821],{"class":820},"sA_wV","(?:href",[286,823,824],{"class":299},"|",[286,826,827],{"class":820},"src)=\"(",[286,829,331],{"class":330},[286,831,832],{"class":303},"[",[286,834,835],{"class":299},"^",[286,837,838],{"class":303},"\"#?]",[286,840,656],{"class":299},[286,842,843],{"class":330},"\\.",[286,845,846],{"class":820},"(?:css",[286,848,824],{"class":299},[286,850,851],{"class":820},"js",[286,853,824],{"class":299},[286,855,74],{"class":820},[286,857,824],{"class":299},[286,859,860],{"class":820},"png",[286,862,824],{"class":299},[286,864,865],{"class":820},"avif",[286,867,824],{"class":299},[286,869,870],{"class":820},"webp",[286,872,824],{"class":299},[286,874,875],{"class":820},"woff2))\"",[286,877,255],{"class":327},[286,879,99],{"class":299},[286,881,882],{"class":310},")]\n",[286,884,886,889,892,895,898,900,902,905,908],{"class":288,"line":885},23,[286,887,888],{"class":310},"    .",[286,890,891],{"class":320},"map",[286,893,894],{"class":310},"((",[286,896,897],{"class":565},"m",[286,899,569],{"class":310},[286,901,572],{"class":299},[286,903,904],{"class":310}," m[",[286,906,907],{"class":303},"1",[286,909,910],{"class":310},"]);\n",[286,912,914,916,918,920,923,925,927,930,933],{"class":288,"line":913},24,[286,915,741],{"class":299},[286,917,354],{"class":310},[286,919,300],{"class":299},[286,921,922],{"class":303}," ref",[286,924,751],{"class":299},[286,926,805],{"class":310},[286,928,929],{"class":299},"...new",[286,931,932],{"class":320}," Set",[286,934,935],{"class":310},"(refs)]) {\n",[286,937,939,942,945,947,949,951,953,955,958,961],{"class":288,"line":938},25,[286,940,941],{"class":299},"    const",[286,943,944],{"class":303}," head",[286,946,307],{"class":299},[286,948,647],{"class":299},[286,950,650],{"class":320},[286,952,653],{"class":310},[286,954,656],{"class":299},[286,956,957],{"class":310}," ref, { method: ",[286,959,960],{"class":327},"'HEAD'",[286,962,665],{"class":310},[286,964,966,969,971,973,976,978,980,982,984,987,990,993,996,998,1000,1003],{"class":288,"line":965},26,[286,967,968],{"class":299},"    if",[286,970,354],{"class":310},[286,972,357],{"class":299},[286,974,975],{"class":310},"head.ok) ",[286,977,686],{"class":320},[286,979,324],{"class":310},[286,981,691],{"class":327},[286,983,617],{"class":310},[286,985,986],{"class":327},"} references missing asset ${",[286,988,989],{"class":310},"ref",[286,991,992],{"class":327},"} (${",[286,994,995],{"class":310},"head",[286,997,30],{"class":327},[286,999,704],{"class":310},[286,1001,1002],{"class":327},"})`",[286,1004,345],{"class":310},[286,1006,1008],{"class":288,"line":1007},27,[286,1009,1010],{"class":310},"  }\n",[286,1012,1014],{"class":288,"line":1013},28,[286,1015,1016],{"class":310},"}\n",[286,1018,1020],{"class":288,"line":1019},29,[286,1021,388],{"emptyLinePlaceholder":387},[286,1023,1025,1027,1030,1032,1034,1036,1038,1040,1043],{"class":288,"line":1024},30,[286,1026,300],{"class":299},[286,1028,1029],{"class":303}," sm",[286,1031,307],{"class":299},[286,1033,647],{"class":299},[286,1035,650],{"class":320},[286,1037,653],{"class":310},[286,1039,656],{"class":299},[286,1041,1042],{"class":327}," '\u002Fsitemap.xml'",[286,1044,345],{"class":310},[286,1046,1048,1050,1053,1055,1058,1061,1063,1066,1068,1071,1074,1077],{"class":288,"line":1047},31,[286,1049,300],{"class":299},[286,1051,1052],{"class":303}," smText",[286,1054,307],{"class":299},[286,1056,1057],{"class":310}," sm.ok ",[286,1059,1060],{"class":299},"?",[286,1062,647],{"class":299},[286,1064,1065],{"class":310}," sm.",[286,1067,104],{"class":320},[286,1069,1070],{"class":310},"() ",[286,1072,1073],{"class":299},":",[286,1075,1076],{"class":327}," ''",[286,1078,550],{"class":310},[286,1080,1082,1084,1086,1088,1091,1094,1097,1100,1102,1104,1107,1110,1112,1114,1117,1120,1122,1124,1126],{"class":288,"line":1081},32,[286,1083,351],{"class":299},[286,1085,354],{"class":310},[286,1087,357],{"class":299},[286,1089,1090],{"class":310},"sm.ok ",[286,1092,1093],{"class":299},"||",[286,1095,1096],{"class":299}," !",[286,1098,1099],{"class":310},"smText.",[286,1101,766],{"class":320},[286,1103,324],{"class":310},[286,1105,1106],{"class":327},"'\u003Curlset'",[286,1108,1109],{"class":310},")) ",[286,1111,686],{"class":320},[286,1113,324],{"class":310},[286,1115,1116],{"class":327},"`sitemap.xml → ${",[286,1118,1119],{"class":310},"sm",[286,1121,30],{"class":327},[286,1123,704],{"class":310},[286,1125,587],{"class":327},[286,1127,345],{"class":310},[286,1129,1131,1133,1136,1138,1140,1142,1144,1146,1148,1150,1153,1155,1157,1160,1162,1165,1167,1170,1172,1174,1177,1179,1181,1183,1185,1187,1189,1191],{"class":288,"line":1130},33,[286,1132,300],{"class":299},[286,1134,1135],{"class":303}," locs",[286,1137,307],{"class":299},[286,1139,805],{"class":310},[286,1141,808],{"class":299},[286,1143,1099],{"class":310},[286,1145,813],{"class":320},[286,1147,324],{"class":310},[286,1149,255],{"class":327},[286,1151,1152],{"class":820},"\u003Cloc>(",[286,1154,832],{"class":303},[286,1156,835],{"class":299},[286,1158,1159],{"class":303},"\u003C]",[286,1161,656],{"class":299},[286,1163,1164],{"class":820},")\u003C",[286,1166,331],{"class":330},[286,1168,1169],{"class":820},"loc>",[286,1171,255],{"class":327},[286,1173,99],{"class":299},[286,1175,1176],{"class":310},")].",[286,1178,891],{"class":320},[286,1180,894],{"class":310},[286,1182,897],{"class":565},[286,1184,569],{"class":310},[286,1186,572],{"class":299},[286,1188,904],{"class":310},[286,1190,907],{"class":303},[286,1192,910],{"class":310},[286,1194,1196,1198,1201,1204,1207,1210,1212,1214,1216,1219,1222,1224,1226,1229],{"class":288,"line":1195},34,[286,1197,351],{"class":299},[286,1199,1200],{"class":310}," (locs.",[286,1202,1203],{"class":303},"length",[286,1205,1206],{"class":299}," \u003C",[286,1208,1209],{"class":303}," 5",[286,1211,569],{"class":310},[286,1213,686],{"class":320},[286,1215,324],{"class":310},[286,1217,1218],{"class":327},"`sitemap has only ${",[286,1220,1221],{"class":310},"locs",[286,1223,30],{"class":327},[286,1225,1203],{"class":303},[286,1227,1228],{"class":327},"} entries`",[286,1230,345],{"class":310},[286,1232,1234],{"class":288,"line":1233},35,[286,1235,388],{"emptyLinePlaceholder":387},[286,1237,1239,1241,1244,1246,1248,1250,1252,1254,1256,1259,1261],{"class":288,"line":1238},36,[286,1240,300],{"class":299},[286,1242,1243],{"class":303}," r",[286,1245,307],{"class":299},[286,1247,647],{"class":299},[286,1249,650],{"class":320},[286,1251,653],{"class":310},[286,1253,656],{"class":299},[286,1255,511],{"class":303},[286,1257,1258],{"class":310},".from, { redirect: ",[286,1260,662],{"class":327},[286,1262,665],{"class":310},[286,1264,1266,1268,1271,1273,1276,1278,1280,1282,1284,1287,1289,1292,1295,1298,1300,1302,1305],{"class":288,"line":1265},37,[286,1267,351],{"class":299},[286,1269,1270],{"class":310}," (r.status ",[286,1272,677],{"class":299},[286,1274,1275],{"class":303}," 301",[286,1277,569],{"class":310},[286,1279,686],{"class":320},[286,1281,324],{"class":310},[286,1283,691],{"class":327},[286,1285,1286],{"class":303},"REDIRECT",[286,1288,30],{"class":327},[286,1290,1291],{"class":310},"from",[286,1293,1294],{"class":327},"} → ${",[286,1296,1297],{"class":310},"r",[286,1299,30],{"class":327},[286,1301,704],{"class":310},[286,1303,1304],{"class":327},"}, expected 301`",[286,1306,345],{"class":310},[286,1308,1310,1313],{"class":288,"line":1309},38,[286,1311,1312],{"class":299},"else",[286,1314,1315],{"class":310}," {\n",[286,1317,1319,1321,1324,1326,1329,1332,1335,1338,1340,1343],{"class":288,"line":1318},39,[286,1320,639],{"class":299},[286,1322,1323],{"class":303}," dest",[286,1325,307],{"class":299},[286,1327,1328],{"class":299}," new",[286,1330,1331],{"class":320}," URL",[286,1333,1334],{"class":310},"(r.headers.",[286,1336,1337],{"class":320},"get",[286,1339,324],{"class":310},[286,1341,1342],{"class":327},"'location'",[286,1344,1345],{"class":310},"), base);\n",[286,1347,1349,1351,1354,1356,1358,1360,1363,1365],{"class":288,"line":1348},40,[286,1350,639],{"class":299},[286,1352,1353],{"class":303}," destRes",[286,1355,307],{"class":299},[286,1357,647],{"class":299},[286,1359,650],{"class":320},[286,1361,1362],{"class":310},"(dest, { redirect: ",[286,1364,662],{"class":327},[286,1366,665],{"class":310},[286,1368,1370,1372,1375,1377,1379,1381,1383,1385,1387,1389,1391,1393,1395,1398,1400,1403,1405,1408,1410,1412,1414],{"class":288,"line":1369},41,[286,1371,671],{"class":299},[286,1373,1374],{"class":310}," (destRes.status ",[286,1376,677],{"class":299},[286,1378,680],{"class":303},[286,1380,569],{"class":310},[286,1382,686],{"class":320},[286,1384,324],{"class":310},[286,1386,691],{"class":327},[286,1388,1286],{"class":303},[286,1390,30],{"class":327},[286,1392,1291],{"class":310},[286,1394,1294],{"class":327},[286,1396,1397],{"class":310},"dest",[286,1399,30],{"class":327},[286,1401,1402],{"class":310},"pathname",[286,1404,1294],{"class":327},[286,1406,1407],{"class":310},"destRes",[286,1409,30],{"class":327},[286,1411,704],{"class":310},[286,1413,587],{"class":327},[286,1415,345],{"class":310},[286,1417,1419],{"class":288,"line":1418},42,[286,1420,1016],{"class":310},[286,1422,1424],{"class":288,"line":1423},43,[286,1425,388],{"emptyLinePlaceholder":387},[286,1427,1429,1432,1435,1438,1440,1443,1446,1449,1452,1455,1458,1460,1462,1465],{"class":288,"line":1428},44,[286,1430,1431],{"class":310},"console.",[286,1433,1434],{"class":320},"log",[286,1436,1437],{"class":310},"(failures ",[286,1439,1060],{"class":299},[286,1441,1442],{"class":327}," `smoke: ${",[286,1444,1445],{"class":310},"failures",[286,1447,1448],{"class":327},"} failure(s)`",[286,1450,1451],{"class":299}," :",[286,1453,1454],{"class":327}," `smoke: OK (${",[286,1456,1457],{"class":303},"PAGES",[286,1459,30],{"class":327},[286,1461,1203],{"class":303},[286,1463,1464],{"class":327},"} pages checked)`",[286,1466,345],{"class":310},[286,1468,1470,1473,1475,1477,1479,1482,1484,1486],{"class":288,"line":1469},45,[286,1471,1472],{"class":310},"process.",[286,1474,374],{"class":320},[286,1476,1437],{"class":310},[286,1478,1060],{"class":299},[286,1480,1481],{"class":303}," 1",[286,1483,1451],{"class":299},[286,1485,547],{"class":303},[286,1487,345],{"class":310},[40,1489,1491],{"id":1490},"wire-it-into-the-pipeline","Wire It Into the Pipeline",[277,1493,1497],{"className":1494,"code":1495,"language":1496,"meta":282,"style":282},"language-yaml shiki shiki-themes github-light github-dark","# .github\u002Fworkflows\u002Fpreview.yml (excerpt)\n- name: Deploy preview\n  id: preview\n  run: |\n    URL=$(npx wrangler versions upload --json | jq -r '.preview_url')\n    echo \"url=$URL\" >> \"$GITHUB_OUTPUT\"\n\n- name: Smoke test the preview\n  run: node scripts\u002Fsmoke.mjs \"${{ steps.preview.outputs.url }}\"\n\n- name: Comment the preview URL\n  uses: actions\u002Fgithub-script@v7\n  with:\n    script: |\n      github.rest.issues.createComment({\n        issue_number: context.issue.number,\n        owner: context.repo.owner, repo: context.repo.repo,\n        body: `Preview: ${{ steps.preview.outputs.url }} — smoke tests passed.`\n      })\n","yaml",[23,1498,1499,1504,1519,1529,1539,1544,1549,1553,1564,1573,1577,1588,1598,1606,1615,1620,1625,1630,1635],{"__ignoreMap":282},[286,1500,1501],{"class":288,"line":289},[286,1502,1503],{"class":292},"# .github\u002Fworkflows\u002Fpreview.yml (excerpt)\n",[286,1505,1506,1509,1513,1516],{"class":288,"line":296},[286,1507,1508],{"class":310},"- ",[286,1510,1512],{"class":1511},"s9eBZ","name",[286,1514,1515],{"class":310},": ",[286,1517,1518],{"class":327},"Deploy preview\n",[286,1520,1521,1524,1526],{"class":288,"line":348},[286,1522,1523],{"class":1511},"  id",[286,1525,1515],{"class":310},[286,1527,1528],{"class":327},"preview\n",[286,1530,1531,1534,1536],{"class":288,"line":384},[286,1532,1533],{"class":1511},"  run",[286,1535,1515],{"class":310},[286,1537,1538],{"class":299},"|\n",[286,1540,1541],{"class":288,"line":391},[286,1542,1543],{"class":327},"    URL=$(npx wrangler versions upload --json | jq -r '.preview_url')\n",[286,1545,1546],{"class":288,"line":404},[286,1547,1548],{"class":327},"    echo \"url=$URL\" >> \"$GITHUB_OUTPUT\"\n",[286,1550,1551],{"class":288,"line":432},[286,1552,388],{"emptyLinePlaceholder":387},[286,1554,1555,1557,1559,1561],{"class":288,"line":455},[286,1556,1508],{"class":310},[286,1558,1512],{"class":1511},[286,1560,1515],{"class":310},[286,1562,1563],{"class":327},"Smoke test the preview\n",[286,1565,1566,1568,1570],{"class":288,"line":482},[286,1567,1533],{"class":1511},[286,1569,1515],{"class":310},[286,1571,1572],{"class":327},"node scripts\u002Fsmoke.mjs \"${{ steps.preview.outputs.url }}\"\n",[286,1574,1575],{"class":288,"line":500},[286,1576,388],{"emptyLinePlaceholder":387},[286,1578,1579,1581,1583,1585],{"class":288,"line":506},[286,1580,1508],{"class":310},[286,1582,1512],{"class":1511},[286,1584,1515],{"class":310},[286,1586,1587],{"class":327},"Comment the preview URL\n",[286,1589,1590,1593,1595],{"class":288,"line":530},[286,1591,1592],{"class":1511},"  uses",[286,1594,1515],{"class":310},[286,1596,1597],{"class":327},"actions\u002Fgithub-script@v7\n",[286,1599,1600,1603],{"class":288,"line":535},[286,1601,1602],{"class":1511},"  with",[286,1604,1605],{"class":310},":\n",[286,1607,1608,1611,1613],{"class":288,"line":553},[286,1609,1610],{"class":1511},"    script",[286,1612,1515],{"class":310},[286,1614,1538],{"class":299},[286,1616,1617],{"class":288,"line":599},[286,1618,1619],{"class":327},"      github.rest.issues.createComment({\n",[286,1621,1622],{"class":288,"line":604},[286,1623,1624],{"class":327},"        issue_number: context.issue.number,\n",[286,1626,1627],{"class":288,"line":636},[286,1628,1629],{"class":327},"        owner: context.repo.owner, repo: context.repo.repo,\n",[286,1631,1632],{"class":288,"line":668},[286,1633,1634],{"class":327},"        body: `Preview: ${{ steps.preview.outputs.url }} — smoke tests passed.`\n",[286,1636,1637],{"class":288,"line":718},[286,1638,1639],{"class":327},"      })\n",[14,1641,1642],{},"Run the same script against production immediately after promotion. The two runs use identical code, which means a production failure is unambiguous: the artifact was fine on the preview host, so the difference is environmental.",[71,1644,1645,1772],{},[74,1646,82,1651,82,1654,82,1657,82,1660,82,1758],{"viewBox":1647,"role":77,"ariaLabelledBy":1648,"xmlns":81},"0 0 760 290",[1649,1650],"smk-flow-title","smk-flow-desc",[84,1652,1653],{"id":1649},"Where smoke tests run in the pipeline",[88,1655,1656],{"id":1650},"A pipeline: build gates run on the artifact, the preview deploy publishes it, smoke tests run against the preview URL and block the pull request on failure, the merge promotes to production, and the same smoke tests run against production, triggering a rollback on hard failure.",[92,1658],{"x":94,"y":94,"width":95,"height":1659,"fill":97},"290",[99,1661,102,1662,102,1665,102,1673,102,1678,102,1682,102,1685,102,1689,102,1692,102,1696,102,1699,102,1702,102,1707,102,1711,102,1714,102,1717,102,1719,102,1723,102,1727,102,1730,102,1733,102,1754,82],{"style":101},[104,1663,1664],{"x":106,"y":107,"fill":108,"style":109},"The same script runs twice, against two hosts",[92,1666],{"x":1667,"y":1668,"width":1669,"height":1670,"rx":1671,"fill":118,"opacity":1672,"stroke":120,"style":121},"30","70","130","60","10","0.16",[104,1674,127],{"x":1675,"y":1676,"fill":108,"style":1677},"95","96","font-size:12px;font-weight:700;text-anchor:middle",[104,1679,1681],{"x":1675,"y":1680,"fill":131,"style":132},"116","on the artifact",[92,1683],{"x":1684,"y":1668,"width":1669,"height":1670,"rx":1671,"fill":188,"opacity":119,"stroke":188,"style":121},"190",[104,1686,1688],{"x":1687,"y":1676,"fill":108,"style":1677},"255","Preview deploy",[104,1690,1691],{"x":1687,"y":1680,"fill":131,"style":132},"per pull request",[92,1693],{"x":1694,"y":1668,"width":124,"height":1670,"rx":1671,"fill":159,"opacity":1695,"stroke":161,"style":121},"350","0.3",[104,1697,165],{"x":1698,"y":1676,"fill":108,"style":1677},"425",[104,1700,1701],{"x":1698,"y":1680,"fill":131,"style":132},"blocks the PR",[92,1703],{"x":1704,"y":1668,"width":1705,"height":1670,"rx":1671,"fill":1706,"opacity":119,"stroke":1706,"style":121},"530","120","#6a4c93",[104,1708,1710],{"x":1709,"y":1676,"fill":108,"style":1677},"590","Promote",[104,1712,1713],{"x":1709,"y":1680,"fill":131,"style":132},"on merge",[92,1715],{"x":1694,"y":1716,"width":124,"height":1670,"rx":1671,"fill":159,"opacity":160,"stroke":161,"style":121},"180",[104,1718,165],{"x":1698,"y":150,"fill":108,"style":1677},[104,1720,1722],{"x":1698,"y":1721,"fill":131,"style":132},"226","against production",[92,1724],{"x":1704,"y":1716,"width":1716,"height":1670,"rx":1671,"fill":1725,"opacity":119,"stroke":1726,"style":121},"#ff595e","#d83b41",[104,1728,1729],{"x":192,"y":150,"fill":108,"style":1677},"Rollback",[104,1731,1732],{"x":192,"y":1721,"fill":131,"style":132},"on hard failure only",[99,1734,1737,1738,1737,1742,1737,1745,1737,1748,1737,1751,102],{"stroke":131,"fill":1735,"style":1736},"none","stroke-width:2px","\n      ",[617,1739],{"d":1740,"style":1741},"M162 100 L188 100","marker-end:url(#smk-arrow)",[617,1743],{"d":1744,"style":1741},"M322 100 L348 100",[617,1746],{"d":1747,"style":1741},"M502 100 L528 100",[617,1749],{"d":1750,"style":1741},"M590 132 L470 178",[617,1752],{"d":1753,"style":1741},"M502 210 L528 210",[104,1755,1757],{"x":1667,"y":1756,"fill":131,"style":216},"272","A production failure after a green preview means the environment differs — usually headers, redirects or base URL",[1759,1760,102,1761,82],"defs",{},[783,1762,1737,1769,102],{"id":1763,"viewBox":1764,"refX":1765,"refY":1766,"markerWidth":1767,"markerHeight":1767,"orient":1768},"smk-arrow","0 0 10 10","8","5","7","auto-start-reverse",[617,1770],{"d":1771,"fill":131},"M0 0 L10 5 L0 10 z",[219,1773,1774],{},"Running the identical script in both places is what makes a production failure diagnosable — the variable under test is the environment, not the code.",[40,1776,1778],{"id":1777},"keeping-the-suite-honest","Keeping the Suite Honest",[14,1780,1781],{},"A smoke suite decays in two directions, and both are worth guarding against.",[14,1783,1784,1785,1788],{},"It ",[232,1786,1787],{},"grows",", because every incident produces a suggestion to add a check. Most of those belong in build gates, where they run on every page rather than on four. Before adding a check, ask whether it could have been caught pre-deploy; if it could, add it there instead. Cap the suite at a fixed wall-clock budget — fifteen seconds — and treat exceeding it as a signal to move something out.",[14,1790,1791,1792,1795,1796,1799,1800,1803],{},"It also ",[232,1793,1794],{},"rots",", because markers drift. A CSS refactor renames ",[23,1797,1798],{},"site-header"," and the check silently starts asserting on a class that never appears, which is a test that can only pass by accident. Guard against this by asserting the markers exist on a known-good build in CI: if a marker matches zero pages of the current build, fail the ",[18,1801,1802],{},"test suite"," rather than the deploy, so the failure is understood as maintenance rather than an incident.",[14,1805,1806],{},"Finally, keep the URL list representative rather than historical. When a new template ships, add a page of it and remove one of the two article pages that now test the same code path. The suite should always be one page per template, not one page per template you have ever had.",[40,1808,1810],{"id":1809},"measured-impact","Measured Impact",[14,1812,1813],{},"Six months of pipeline data from a documentation site deploying roughly forty times a month:",[1815,1816,1817,1833],"table",{},[1818,1819,1820],"thead",{},[1821,1822,1823,1827,1830],"tr",{},[1824,1825,1826],"th",{},"Failure caught by",[1824,1828,1829],{},"Count",[1824,1831,1832],{},"Would have reached readers",[1834,1835,1836,1848,1859,1869],"tbody",{},[1821,1837,1838,1842,1845],{},[1839,1840,1841],"td",{},"Build gates (links, schema, markup)",[1839,1843,1844],{},"34",[1839,1846,1847],{},"No",[1821,1849,1850,1853,1856],{},[1839,1851,1852],{},"Smoke tests on preview",[1839,1854,1855],{},"9",[1839,1857,1858],{},"Yes — all nine built cleanly",[1821,1860,1861,1864,1866],{},[1839,1862,1863],{},"Smoke tests on production",[1839,1865,314],{},[1839,1867,1868],{},"Briefly; auto-rolled back in ~40 s",[1821,1870,1871,1874,1876],{},[1839,1872,1873],{},"Monitoring (after both)",[1839,1875,907],{},[1839,1877,1878],{},"Yes, for 11 minutes",[71,1880,1881,1961],{},[74,1882,82,1887,82,1890,82,1893,82,1895],{"viewBox":1883,"role":77,"ariaLabelledBy":1884,"xmlns":81},"0 0 760 280",[1885,1886],"smk-catch-title","smk-catch-desc",[84,1888,1889],{"id":1885},"Where failures were caught over six months",[88,1891,1892],{"id":1886},"A bar chart of 46 caught failures: build gates caught 34, smoke tests on the preview caught 9, smoke tests on production caught 2, and monitoring caught 1. The nine preview catches are highlighted because they all passed the build gates.",[92,1894],{"x":94,"y":94,"width":95,"height":158,"fill":97},[99,1896,102,1897,102,1901,102,1906,102,1910,102,1914,102,1918,102,1922,102,1927,102,1931,102,1935,102,1938,102,1942,102,1946,102,1949,102,1953,102,1957,82],{"style":101},[104,1898,1900],{"x":106,"y":1899,"fill":108,"style":109},"26","46 failures, caught at four different depths",[288,1902],{"x1":154,"y1":1903,"x2":154,"y2":1904,"stroke":1905,"style":121},"44","212","#d9e2ef",[104,1907,127],{"x":1670,"y":1908,"fill":108,"style":1909},"76","font-size:12px;font-weight:700",[92,1911],{"x":154,"y":1670,"width":1912,"height":1667,"rx":1766,"fill":118,"opacity":1913,"stroke":120,"style":121},"440","0.22",[104,1915,1844],{"x":1916,"y":1917,"fill":120,"style":1677},"450","80",[104,1919,1921],{"x":1670,"y":1920,"fill":108,"style":1909},"128","Smoke · preview",[92,1923],{"x":154,"y":1924,"width":1925,"height":1667,"rx":1766,"fill":159,"opacity":1926,"stroke":161,"style":121},"112","117","0.36",[104,1928,1855],{"x":1929,"y":1930,"fill":161,"style":1677},"288","132",[104,1932,1934],{"x":1933,"y":1930,"fill":131,"style":216},"360","all built cleanly — invisible to build gates",[104,1936,1937],{"x":1670,"y":1716,"fill":108,"style":1909},"Smoke · production",[92,1939],{"x":154,"y":1940,"width":1899,"height":1667,"rx":1766,"fill":1725,"opacity":1941,"stroke":1726,"style":121},"164","0.18",[104,1943,314],{"x":1944,"y":1945,"fill":1726,"style":1909},"276","184",[104,1947,193],{"x":1670,"y":1948,"fill":108,"style":1909},"228",[92,1950],{"x":154,"y":1904,"width":1951,"height":1667,"rx":1952,"fill":1725,"opacity":119,"stroke":1726,"style":121},"13","4",[104,1954,907],{"x":1955,"y":1956,"fill":1726,"style":1909},"262","232",[104,1958,1960],{"x":154,"y":1959,"fill":131,"style":216},"266","Shared linear scale · 40 deploys per month over six months",[219,1962,1963],{},"The nine preview catches are the argument for this whole layer: every one of them produced a clean build and a broken site.",[14,1965,1966],{},"Of the nine preview catches, five were missing assets after a build-tool upgrade changed the output directory, three were redirect rules that shadowed real pages, and one was a base URL left pointing at staging. None is detectable from the build directory alone.",[40,1968,1970],{"id":1969},"reading-a-failure","Reading a Failure",[14,1972,1973],{},"A smoke failure names a URL and a reason, which is usually enough to classify it in seconds.",[14,1975,1976,1977,1980],{},"A ",[232,1978,1979],{},"non-200 status"," on a page that exists in the build points at routing: a redirect rule that matched too broadly, a trailing-slash mismatch, or a host that has not finished propagating the deployment. Check the redirect file first; it is the most common cause by a wide margin.",[14,1982,1976,1983,1986],{},[232,1984,1985],{},"missing marker"," on a page that returns 200 points at a template: a partial that failed to render, a layout that resolved to the wrong file, or a component that threw and was swallowed. The build log usually has a warning that nobody read.",[14,1988,1976,1989,1992],{},[232,1990,1991],{},"missing asset"," points at the pipeline or the deploy configuration: an output directory that changed, an ignore pattern that now matches, or a hashed filename that the HTML references from a different build. This is the failure that most often reaches production, because nothing about the artifact looks wrong.",[14,1994,1995],{},"Whatever the category, re-run the suite once before acting. Preview hosts occasionally serve a 404 for a few seconds while a deployment propagates, and a single retry distinguishes propagation from breakage without adding a sleep to every run.",[40,1997,1999],{"id":1998},"pitfalls-rollback","Pitfalls & Rollback",[45,2001,2002,2008,2014,2026,2032,2041],{},[48,2003,2004,2007],{},[232,2005,2006],{},"Testing the build directory."," A local file server has no redirect rules, no headers and no deploy-ignore behaviour. Test the deployed URL.",[48,2009,2010,2013],{},[232,2011,2012],{},"Too many URLs."," A smoke suite that takes two minutes gets skipped. Keep it under fifteen seconds and let build gates own coverage.",[48,2015,2016,2019,2020,339,2022,2025],{},[232,2017,2018],{},"Asserting on prose."," Markers should be structural (",[23,2021,1798],{},[23,2023,2024],{},"\u003C\u002Farticle>","); asserting on wording makes the suite fail on every copy edit.",[48,2027,2028,2031],{},[232,2029,2030],{},"Ignoring the trailing-slash variant."," Test the exact form your internal links use, since hosts differ in how they normalise.",[48,2033,2034,2040],{},[232,2035,2036,2037,30],{},"Silent ",[23,2038,2039],{},"continue-on-error"," A smoke step that never fails the job is decoration. Fail loudly, then decide what the failure triggers.",[48,2042,2043,2046],{},[232,2044,2045],{},"Rollback:"," the suite is one script and one pipeline step. Removing the step restores the previous pipeline; a failing check that turns out to be wrong should be fixed or deleted the same day, never muted indefinitely.",[40,2048,2050],{"id":2049},"conclusion","Conclusion",[14,2052,2053,2054,2057,2058,30],{},"Smoke tests occupy a narrow and unglamorous slot: a handful of URLs, no browser, ten seconds, run against the thing readers will actually hit. That narrowness is why they stay fast enough to run on every deploy, and running them against the preview ",[18,2055,2056],{},"and"," production is what turns \"the site is broken\" into a specific, environmental difference. Pair them with a rehearsed rollback from ",[35,2059,2061],{"href":2060},"\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",[40,2063,2065],{"id":2064},"faq","FAQ",[2067,2068,2070],"h3",{"id":2069},"why-test-the-preview-url-instead-of-the-build-directory","Why test the preview URL instead of the build directory?",[14,2072,2073],{},"Because a build directory does not exercise the host. Redirect rules, header files, base URL configuration, deploy-ignore patterns and asset routing only exist once the artifact is deployed, and each of them can produce a site that serves incorrectly from output that built perfectly.",[2067,2075,2077],{"id":2076},"how-many-pages-should-a-smoke-test-cover","How many pages should a smoke test cover?",[14,2079,2080],{},"One per template plus the homepage, which is usually four to six URLs. Smoke tests answer \"is the site fundamentally usable\", not \"is every page correct\" — coverage belongs to the build gates, which are far cheaper per page.",[2067,2082,2084],{"id":2083},"should-a-smoke-test-failure-block-the-deploy","Should a smoke test failure block the deploy?",[14,2086,2087],{},"On a preview, yes: fail the pull request. On production, fail the pipeline and trigger a rollback only for unambiguous failures such as a non-200 status or a missing referenced asset. Threshold-based checks should alert rather than act.",[2067,2089,2091],{"id":2090},"how-long-should-the-whole-suite-take","How long should the whole suite take?",[14,2093,2094],{},"Under fifteen seconds. Anything slower gets skipped under deadline pressure, and speed here comes free because the checks are a handful of HTTP requests with no browser involved.",[2067,2096,2098],{"id":2097},"do-i-need-a-headless-browser","Do I need a headless browser?",[14,2100,2101],{},"Not for smoke tests. Fetching HTML and asserting on markers and asset availability catches the catastrophic failures. Add a browser only for checks that genuinely require rendering, and keep those in a separate, slower job.",[40,2103,2105],{"id":2104},"related","Related",[45,2107,2108,2117,2122,2127,2134],{},[48,2109,2110,2113,2114,2116],{},[232,2111,2112],{},"Parent:"," ",[35,2115,38],{"href":37}," — the layer this sits in.",[48,2118,2119,2121],{},[35,2120,2061],{"href":2060}," — what a production failure triggers.",[48,2123,2124,2126],{},[35,2125,54],{"href":53}," — the URL these tests run against.",[48,2128,2129,2133],{},[35,2130,2132],{"href":2131},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fsetting-up-deploy-previews-on-netlify-for-every-pull-request\u002F","Setting Up Deploy Previews on Netlify for Every Pull Request"," — the same wiring on another host.",[48,2135,2136,2140],{},[35,2137,2139],{"href":2138},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002F","GitHub Actions for Automated SSG Builds"," — where the step lives.",[2142,2143,2144],"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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}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":282,"searchDepth":296,"depth":296,"links":2146},[2147,2148,2149,2150,2151,2152,2153,2154,2155,2156,2163],{"id":42,"depth":296,"text":43},{"id":68,"depth":296,"text":69},{"id":224,"depth":296,"text":225},{"id":1490,"depth":296,"text":1491},{"id":1777,"depth":296,"text":1778},{"id":1809,"depth":296,"text":1810},{"id":1969,"depth":296,"text":1970},{"id":1998,"depth":296,"text":1999},{"id":2049,"depth":296,"text":2050},{"id":2064,"depth":296,"text":2065,"children":2157},[2158,2159,2160,2161,2162],{"id":2069,"depth":348,"text":2070},{"id":2076,"depth":348,"text":2077},{"id":2083,"depth":348,"text":2084},{"id":2090,"depth":348,"text":2091},{"id":2097,"depth":348,"text":2098},{"id":2104,"depth":296,"text":2105},[2165,2167,2170,2171],{"name":2166,"item":255},"Home",{"name":2168,"item":2169},"Production-Ready Deployment & CI\u002FCD Workflows","\u002Fproduction-ready-deployment-cicd-workflows\u002F",{"name":38,"item":37},{"name":5,"item":2172},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url\u002F","2026-08-01","Ten seconds of checks against the deployed preview — status codes, template markers, referenced assets, sitemap and redirects — that catch the deploys a build gate cannot.","md",[2177,2178,2179,2182,2183],{"q":2070,"a":2073},{"q":2077,"a":2080},{"q":2084,"a":2180},{"On a preview, yes":2181},"fail the pull request. On production, fail the pipeline and trigger a rollback only for unambiguous failures such as a non-200 status or a missing referenced asset. Threshold-based checks should alert rather than act.",{"q":2091,"a":2094},{"q":2098,"a":2101},{},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url",{"title":5,"description":2174},"production-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url\u002Findex","article","GmOpRkmQqUSvyeiJQSCqnVo6gByvoBe_eiIoeh5E354",[2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2308,2311,2314,2317,2320,2323,2326,2329,2332,2335,2338,2341,2344,2347,2350,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2386,2388,2391,2394,2397,2400,2403,2406,2409,2412,2415,2418,2421,2424,2426,2429,2431,2433],{"path":2192,"title":2193},"\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":2195,"title":2196},"\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":2198,"title":2199},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":2201,"title":2202},"\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":2204,"title":2205},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":2207,"title":2208},"\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":2210,"title":2211},"\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":2213,"title":2214},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":2216,"title":2217},"\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":2219,"title":2220},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":2222,"title":2223},"\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":2225,"title":2226},"\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":2228,"title":2229},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":2231,"title":2232},"\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":2234,"title":2235},"\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":2237,"title":2238},"\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":2240,"title":2241},"\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":2243,"title":2244},"\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":2246,"title":2247},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":2249,"title":2250},"\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":2252,"title":2253},"\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":2255,"title":2256},"\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":2258,"title":2259},"\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":2261,"title":2262},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":2264,"title":2265},"\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":2267,"title":2268},"\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":2270,"title":2271},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":2273,"title":2274},"\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":2276,"title":2277},"\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":2279,"title":2280},"\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":2282,"title":2283},"\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":2285,"title":2286},"\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":2288,"title":2289},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":2291,"title":2292},"\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":2294,"title":2295},"\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":2297,"title":2298},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":2300,"title":2301},"\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":2303,"title":2304},"\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":2306,"title":2307},"\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":2309,"title":2310},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":2312,"title":2313},"\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":2315,"title":2316},"\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":2318,"title":2319},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":2321,"title":2322},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":2324,"title":2325},"\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":2327,"title":2328},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":2330,"title":2331},"\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":2333,"title":2334},"\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":2336,"title":2337},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":2339,"title":2340},"\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":2342,"title":2343},"\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":2345,"title":2346},"\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":2348,"title":2349},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":2351,"title":2352},"\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":2354,"title":2355},"\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":2357,"title":2358},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":2360,"title":2361},"\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":2363,"title":2364},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":2366,"title":2367},"\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":2369,"title":2370},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":2372,"title":2373},"\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":2375,"title":2376},"\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":2378,"title":2379},"\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":2381,"title":2382},"\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":2384,"title":2385},"\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":2387,"title":2139},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds",{"path":2389,"title":2390},"\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":2392,"title":2393},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":2395,"title":2396},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":2398,"title":2399},"\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":2401,"title":2402},"\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":2404,"title":2405},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":2407,"title":2408},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":2410,"title":2411},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":2413,"title":2414},"\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":2416,"title":2417},"\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":2419,"title":2420},"\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":2422,"title":2423},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":2425,"title":54},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests",{"path":2427,"title":2428},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":2430,"title":38},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites",{"path":2432,"title":2061},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frolling-back-a-bad-static-deploy-in-under-a-minute",{"path":2185,"title":5},1785611671104]