[{"data":1,"prerenderedAt":2034},["ShallowReactive",2],{"page:\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fhash-based-csp-for-inline-scripts-in-astro":3,"all-docs-nav":1491},{"id":4,"title":5,"body":6,"breadcrumb":1468,"dateModified":1477,"datePublished":1477,"description":1478,"extension":1479,"faq":1480,"meta":1485,"navigation":406,"path":1486,"seo":1487,"slug":12,"stem":1488,"type":1489,"__hash__":1490},"content\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fhash-based-csp-for-inline-scripts-in-astro\u002Findex.md","Hash-Based CSP for Inline Scripts in Astro",{"type":7,"value":8,"toc":1450},"minimark",[9,13,29,38,43,60,64,75,97,100,181,189,343,347,357,896,910,916,922,926,929,937,944,959,963,970,1035,1109,1112,1116,1207,1295,1299,1315,1319,1360,1364,1370,1374,1379,1382,1386,1389,1393,1396,1400,1403,1407,1446],[10,11,5],"h1",{"id":12},"hash-based-csp-for-inline-scripts-in-astro",[14,15,16,17,21,22,25,26,28],"p",{},"Most real-world Content Security Policies contain ",[18,19,20],"code",{},"'unsafe-inline'"," in ",[18,23,24],{},"script-src",", and most of them contain it for the same reason: the site has a few inline scripts — a theme toggle that must run before first paint, an analytics loader, a small bootstrap — and listing them properly seemed like too much work. But ",[18,27,20],{}," also allows any script an attacker manages to inject, which is the main thing CSP exists to prevent.",[14,30,31,32,37],{},"Static sites have an unusually clean way out. Their HTML is fixed at build time, so every inline script can be hashed during the build and listed in the policy by its SHA-256 digest. The browser then runs inline scripts whose content matches a listed hash and blocks everything else. This guide automates that for an Astro site with a post-build script that hashes the final HTML and writes the header. The policy process around it is in ",[33,34,36],"a",{"href":35},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fwriting-a-content-security-policy-for-a-static-site\u002F","Writing a Content Security Policy for a Static Site",".",[39,40,42],"h2",{"id":41},"prerequisites","Prerequisites",[44,45,46,54,57],"ul",{},[47,48,49,50,53],"li",{},"An Astro site with static output, deployed to a host that reads a ",[18,51,52],{},"_headers"," file (Cloudflare Pages, Netlify) or equivalent.",[47,55,56],{},"Node.js 20 or later for the post-build script.",[47,58,59],{},"A draft CSP for everything except inline scripts.",[39,61,63],{"id":62},"how-astro-produces-inline-scripts","How Astro Produces Inline Scripts",[14,65,66,67,70,71,74],{},"Astro processes ",[18,68,69],{},"\u003Cscript>"," tags in components: by default it bundles them into external module files, which ",[18,72,73],{},"script-src 'self'"," already allows. Inline scripts in the output come from three places:",[44,76,77,83,90],{},[47,78,79,82],{},[18,80,81],{},"\u003Cscript is:inline>"," in your components — usually deliberate, such as a theme bootstrap that must run before paint.",[47,84,85,86,89],{},"Integrations and framework islands — the island hydration bootstrap (",[18,87,88],{},"astro-island"," definitions) and some integrations' loaders are inlined.",[47,91,92,93,96],{},"Small hoisted scripts Astro chooses to inline when they are below ",[18,94,95],{},"build.inlineStylesheets","-style thresholds or when a directive requires it.",[14,98,99],{},"On the 700-page docs site, the built output contained four distinct inline scripts across all pages, each appearing on hundreds of pages with identical content.",[101,102,107],"pre",{"className":103,"code":104,"language":105,"meta":106,"style":106},"language-bash shiki shiki-themes github-light github-dark","# count distinct inline scripts across the build\nnode -e \"\nconst fs=require('fs'),glob=require('fs').globSync;\nconst set=new Map();\nfor (const f of glob('dist\u002F**\u002F*.html')) {\n  for (const m of fs.readFileSync(f,'utf8').matchAll(\u002F\u003Cscript(?![^>]*\\bsrc=)[^>]*>([\\s\\S]*?)\u003C\\\u002Fscript>\u002Fg))\n    set.set(m[1], (set.get(m[1])||0)+1);\n}\nfor (const [s,n] of set) console.log(n, s.slice(0,60).replace(\u002F\\s+\u002Fg,' '));\n\"\n","bash","",[18,108,109,118,133,139,145,151,157,163,169,175],{"__ignoreMap":106},[110,111,114],"span",{"class":112,"line":113},"line",1,[110,115,117],{"class":116},"sJ8bj","# count distinct inline scripts across the build\n",[110,119,121,125,129],{"class":112,"line":120},2,[110,122,124],{"class":123},"sScJk","node",[110,126,128],{"class":127},"sj4cs"," -e",[110,130,132],{"class":131},"sZZnC"," \"\n",[110,134,136],{"class":112,"line":135},3,[110,137,138],{"class":131},"const fs=require('fs'),glob=require('fs').globSync;\n",[110,140,142],{"class":112,"line":141},4,[110,143,144],{"class":131},"const set=new Map();\n",[110,146,148],{"class":112,"line":147},5,[110,149,150],{"class":131},"for (const f of glob('dist\u002F**\u002F*.html')) {\n",[110,152,154],{"class":112,"line":153},6,[110,155,156],{"class":131},"  for (const m of fs.readFileSync(f,'utf8').matchAll(\u002F\u003Cscript(?![^>]*\\bsrc=)[^>]*>([\\s\\S]*?)\u003C\\\u002Fscript>\u002Fg))\n",[110,158,160],{"class":112,"line":159},7,[110,161,162],{"class":131},"    set.set(m[1], (set.get(m[1])||0)+1);\n",[110,164,166],{"class":112,"line":165},8,[110,167,168],{"class":131},"}\n",[110,170,172],{"class":112,"line":171},9,[110,173,174],{"class":131},"for (const [s,n] of set) console.log(n, s.slice(0,60).replace(\u002F\\s+\u002Fg,' '));\n",[110,176,178],{"class":112,"line":177},10,[110,179,180],{"class":131},"\"\n",[101,182,187],{"className":183,"code":185,"language":186,"meta":106},[184],"language-text","712 (function(){try{var t=localStorage.getItem(\"theme\")...\n712 (()=>{var e=async t=>{await(await t())()};(self.Astro||...\n391 (()=>{var l=(n,t)=>{let i=async()=>{await(await n())()}...\n 12 window.dataLayer=window.dataLayer||[];function gtag(){...\n","text",[18,188,185],{"__ignoreMap":106},[190,191,192,339],"figure",{},[193,194,201,202,201,206,201,210,201,217,201,325],"svg",{"viewBox":195,"role":196,"ariaLabelledBy":197,"xmlns":200},"0 0 760 280","img",[198,199],"hca-flow-title","hca-flow-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[203,204,205],"title",{"id":198},"From build output to hashed policy",[207,208,209],"desc",{"id":199},"Astro builds the site to dist. A post-build script scans every HTML file, extracts each inline script's exact text, computes a SHA-256 hash in base64 and collects the unique set. It writes those hashes into the script-src directive of the _headers file. The host serves the header; the browser runs only inline scripts whose hash matches.",[211,212],"rect",{"x":213,"y":213,"width":214,"height":215,"fill":216},"0","760","280","#ffffff",[218,219,221,222,221,229,221,239,221,245,221,251,221,256,221,260,221,263,221,270,221,274,221,277,221,284,221,287,221,289,221,305,221,315,221,321,201],"g",{"style":220},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[186,223,228],{"x":224,"y":225,"fill":226,"style":227},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","Hashes are computed from the exact bytes that ship",[211,230],{"x":231,"y":232,"width":233,"height":234,"rx":235,"fill":236,"opacity":237,"stroke":236,"style":238},"30","80","140","60","10","#1982c4","0.14","stroke-width:1.5px",[186,240,244],{"x":241,"y":242,"fill":226,"style":243},"100","106","font-weight:700;text-anchor:middle","astro build",[186,246,250],{"x":241,"y":247,"fill":248,"style":249},"124","#556071","font-size:11px;text-anchor:middle","712 HTML files",[211,252],{"x":253,"y":232,"width":254,"height":234,"rx":235,"fill":255,"opacity":237,"stroke":255,"style":238},"210","170","#6a4c93",[186,257,259],{"x":258,"y":242,"fill":226,"style":243},"295","extract inline",[186,261,262],{"x":258,"y":247,"fill":248,"style":249},"4 unique scripts",[211,264],{"x":265,"y":232,"width":266,"height":234,"rx":235,"fill":267,"opacity":268,"stroke":269,"style":238},"420","150","#ffca3a","0.26","#a97b00",[186,271,273],{"x":272,"y":242,"fill":226,"style":243},"495","sha256 → base64",[186,275,276],{"x":272,"y":247,"fill":248,"style":249},"'sha256-…' × 4",[211,278],{"x":279,"y":232,"width":280,"height":234,"rx":235,"fill":281,"opacity":282,"stroke":283,"style":238},"610","120","#8ac926","0.18","#5a8a16",[186,285,52],{"x":286,"y":242,"fill":226,"style":243},"670",[186,288,24],{"x":286,"y":247,"fill":248,"style":249},[218,290,293,294,293,299,293,302,221],{"stroke":248,"fill":291,"style":292},"none","stroke-width:2px","\n      ",[295,296],"path",{"d":297,"style":298},"M172 110 L206 110","marker-end:url(#hca-arrow)",[295,300],{"d":301,"style":298},"M382 110 L416 110",[295,303],{"d":304,"style":298},"M572 110 L606 110",[211,306],{"x":253,"y":307,"width":308,"height":309,"rx":310,"fill":311,"opacity":312,"stroke":313,"style":314},"176","520","36","8","#ff595e","0.1","#d83b41","stroke-width:1px",[186,316,320],{"x":317,"y":318,"fill":226,"style":319},"470","199","text-anchor:middle","an injected inline script has no listed hash → blocked",[186,322,324],{"x":224,"y":323,"fill":248,"style":249},"252","The step runs after every build, so an edited script gets a new hash automatically",[326,327,221,328,201],"defs",{},[329,330,293,336,221],"marker",{"id":331,"viewBox":332,"refX":310,"refY":333,"markerWidth":334,"markerHeight":334,"orient":335},"hca-arrow","0 0 10 10","5","7","auto-start-reverse",[295,337],{"d":338,"fill":248},"M0 0 L10 5 L0 10 z",[340,341,342],"figcaption",{},"Because the hashes come from the final output rather than from source files, integrations and minification cannot put them out of step.",[39,344,346],{"id":345},"step-1-write-the-post-build-script","Step 1: Write the Post-Build Script",[14,348,349,350,352,353,356],{},"The script hashes each inline script's exact text content — the bytes between ",[18,351,69],{}," and ",[18,354,355],{},"\u003C\u002Fscript>",", whitespace included — because that is what the browser hashes.",[101,358,362],{"className":359,"code":360,"language":361,"meta":106,"style":106},"language-js shiki shiki-themes github-light github-dark","\u002F\u002F scripts\u002Fcsp-hashes.mjs\nimport { createHash } from 'node:crypto';\nimport { globSync, readFileSync, writeFileSync } from 'node:fs';\n\nconst INLINE = \u002F\u003Cscript(?![^>]*\\bsrc=)(?![^>]*type=\"(?:application\\\u002Fld\\+json|speculationrules)\")[^>]*>([\\s\\S]*?)\u003C\\\u002Fscript>\u002Fg;\nconst hashes = new Set();\nfor (const file of globSync('dist\u002F**\u002F*.html')) {\n  for (const [, body] of readFileSync(file, 'utf8').matchAll(INLINE)) {\n    if (!body.trim()) continue;\n    hashes.add(`'sha256-${createHash('sha256').update(body, 'utf8').digest('base64')}'`);\n  }\n}\n\nconst policy = [\n  \"default-src 'self'\",\n  `script-src 'self' ${[...hashes].sort().join(' ')} https:\u002F\u002Fplausible.io 'wasm-unsafe-eval'`,\n  \"style-src 'self'\",\n  \"img-src 'self' data: https:\u002F\u002Fimg.example-cdn.net\",\n  \"connect-src 'self' https:\u002F\u002Fplausible.io\",\n  \"frame-ancestors 'none'\", \"base-uri 'self'\", \"object-src 'none'\",\n  'report-to csp-endpoint',\n].join('; ');\n\nconst headers = readFileSync('dist\u002F_headers', 'utf8')\n  .replace('__CSP__', policy);\nwriteFileSync('dist\u002F_headers', headers);\nconsole.log(`[csp] ${hashes.size} inline script hash(es)`);\n","js",[18,363,364,369,388,402,408,504,522,550,593,617,671,677,682,687,700,709,747,755,763,771,789,797,811,816,840,857,870],{"__ignoreMap":106},[110,365,366],{"class":112,"line":113},[110,367,368],{"class":116},"\u002F\u002F scripts\u002Fcsp-hashes.mjs\n",[110,370,371,375,379,382,385],{"class":112,"line":120},[110,372,374],{"class":373},"szBVR","import",[110,376,378],{"class":377},"sVt8B"," { createHash } ",[110,380,381],{"class":373},"from",[110,383,384],{"class":131}," 'node:crypto'",[110,386,387],{"class":377},";\n",[110,389,390,392,395,397,400],{"class":112,"line":135},[110,391,374],{"class":373},[110,393,394],{"class":377}," { globSync, readFileSync, writeFileSync } ",[110,396,381],{"class":373},[110,398,399],{"class":131}," 'node:fs'",[110,401,387],{"class":377},[110,403,404],{"class":112,"line":141},[110,405,407],{"emptyLinePlaceholder":406},true,"\n",[110,409,410,413,416,419,422,426,429,432,435,438,441,443,445,447,450,453,457,460,463,466,469,472,474,476,478,480,483,486,489,492,494,497,500,502],{"class":112,"line":147},[110,411,412],{"class":373},"const",[110,414,415],{"class":127}," INLINE",[110,417,418],{"class":373}," =",[110,420,421],{"class":131}," \u002F",[110,423,425],{"class":424},"sA_wV","\u003Cscript(?!",[110,427,428],{"class":127},"[",[110,430,431],{"class":373},"^",[110,433,434],{"class":127},">]",[110,436,437],{"class":373},"*\\b",[110,439,440],{"class":424},"src=)(?!",[110,442,428],{"class":127},[110,444,431],{"class":373},[110,446,434],{"class":127},[110,448,449],{"class":373},"*",[110,451,452],{"class":424},"type=\"(?:application",[110,454,456],{"class":455},"snhLl","\\\u002F",[110,458,459],{"class":424},"ld",[110,461,462],{"class":455},"\\+",[110,464,465],{"class":424},"json",[110,467,468],{"class":373},"|",[110,470,471],{"class":424},"speculationrules)\")",[110,473,428],{"class":127},[110,475,431],{"class":373},[110,477,434],{"class":127},[110,479,449],{"class":373},[110,481,482],{"class":424},">(",[110,484,485],{"class":127},"[\\s\\S]",[110,487,488],{"class":373},"*?",[110,490,491],{"class":424},")\u003C",[110,493,456],{"class":455},[110,495,496],{"class":424},"script>",[110,498,499],{"class":131},"\u002F",[110,501,218],{"class":373},[110,503,387],{"class":377},[110,505,506,508,511,513,516,519],{"class":112,"line":153},[110,507,412],{"class":373},[110,509,510],{"class":127}," hashes",[110,512,418],{"class":373},[110,514,515],{"class":373}," new",[110,517,518],{"class":123}," Set",[110,520,521],{"class":377},"();\n",[110,523,524,527,530,532,535,538,541,544,547],{"class":112,"line":159},[110,525,526],{"class":373},"for",[110,528,529],{"class":377}," (",[110,531,412],{"class":373},[110,533,534],{"class":127}," file",[110,536,537],{"class":373}," of",[110,539,540],{"class":123}," globSync",[110,542,543],{"class":377},"(",[110,545,546],{"class":131},"'dist\u002F**\u002F*.html'",[110,548,549],{"class":377},")) {\n",[110,551,552,555,557,559,562,565,568,571,574,577,580,583,586,588,591],{"class":112,"line":165},[110,553,554],{"class":373},"  for",[110,556,529],{"class":377},[110,558,412],{"class":373},[110,560,561],{"class":377}," [, ",[110,563,564],{"class":127},"body",[110,566,567],{"class":377},"] ",[110,569,570],{"class":373},"of",[110,572,573],{"class":123}," readFileSync",[110,575,576],{"class":377},"(file, ",[110,578,579],{"class":131},"'utf8'",[110,581,582],{"class":377},").",[110,584,585],{"class":123},"matchAll",[110,587,543],{"class":377},[110,589,590],{"class":127},"INLINE",[110,592,549],{"class":377},[110,594,595,598,600,603,606,609,612,615],{"class":112,"line":171},[110,596,597],{"class":373},"    if",[110,599,529],{"class":377},[110,601,602],{"class":373},"!",[110,604,605],{"class":377},"body.",[110,607,608],{"class":123},"trim",[110,610,611],{"class":377},"()) ",[110,613,614],{"class":373},"continue",[110,616,387],{"class":377},[110,618,619,622,625,627,630,633,635,638,640,643,645,647,650,652,654,657,659,662,665,668],{"class":112,"line":177},[110,620,621],{"class":377},"    hashes.",[110,623,624],{"class":123},"add",[110,626,543],{"class":377},[110,628,629],{"class":131},"`'sha256-${",[110,631,632],{"class":123},"createHash",[110,634,543],{"class":131},[110,636,637],{"class":131},"'sha256'",[110,639,582],{"class":131},[110,641,642],{"class":123},"update",[110,644,543],{"class":131},[110,646,564],{"class":377},[110,648,649],{"class":131},", ",[110,651,579],{"class":131},[110,653,582],{"class":131},[110,655,656],{"class":123},"digest",[110,658,543],{"class":131},[110,660,661],{"class":131},"'base64'",[110,663,664],{"class":131},")",[110,666,667],{"class":131},"}'`",[110,669,670],{"class":377},");\n",[110,672,674],{"class":112,"line":673},11,[110,675,676],{"class":377},"  }\n",[110,678,680],{"class":112,"line":679},12,[110,681,168],{"class":377},[110,683,685],{"class":112,"line":684},13,[110,686,407],{"emptyLinePlaceholder":406},[110,688,690,692,695,697],{"class":112,"line":689},14,[110,691,412],{"class":373},[110,693,694],{"class":127}," policy",[110,696,418],{"class":373},[110,698,699],{"class":377}," [\n",[110,701,703,706],{"class":112,"line":702},15,[110,704,705],{"class":131},"  \"default-src 'self'\"",[110,707,708],{"class":377},",\n",[110,710,712,715,717,720,723,726,729,732,735,737,740,742,745],{"class":112,"line":711},16,[110,713,714],{"class":131},"  `script-src 'self' ${",[110,716,428],{"class":131},[110,718,719],{"class":373},"...",[110,721,722],{"class":377},"hashes",[110,724,725],{"class":131},"].",[110,727,728],{"class":123},"sort",[110,730,731],{"class":131},"().",[110,733,734],{"class":123},"join",[110,736,543],{"class":131},[110,738,739],{"class":131},"' '",[110,741,664],{"class":131},[110,743,744],{"class":131},"} https:\u002F\u002Fplausible.io 'wasm-unsafe-eval'`",[110,746,708],{"class":377},[110,748,750,753],{"class":112,"line":749},17,[110,751,752],{"class":131},"  \"style-src 'self'\"",[110,754,708],{"class":377},[110,756,758,761],{"class":112,"line":757},18,[110,759,760],{"class":131},"  \"img-src 'self' data: https:\u002F\u002Fimg.example-cdn.net\"",[110,762,708],{"class":377},[110,764,766,769],{"class":112,"line":765},19,[110,767,768],{"class":131},"  \"connect-src 'self' https:\u002F\u002Fplausible.io\"",[110,770,708],{"class":377},[110,772,774,777,779,782,784,787],{"class":112,"line":773},20,[110,775,776],{"class":131},"  \"frame-ancestors 'none'\"",[110,778,649],{"class":377},[110,780,781],{"class":131},"\"base-uri 'self'\"",[110,783,649],{"class":377},[110,785,786],{"class":131},"\"object-src 'none'\"",[110,788,708],{"class":377},[110,790,792,795],{"class":112,"line":791},21,[110,793,794],{"class":131},"  'report-to csp-endpoint'",[110,796,708],{"class":377},[110,798,800,802,804,806,809],{"class":112,"line":799},22,[110,801,725],{"class":377},[110,803,734],{"class":123},[110,805,543],{"class":377},[110,807,808],{"class":131},"'; '",[110,810,670],{"class":377},[110,812,814],{"class":112,"line":813},23,[110,815,407],{"emptyLinePlaceholder":406},[110,817,819,821,824,826,828,830,833,835,837],{"class":112,"line":818},24,[110,820,412],{"class":373},[110,822,823],{"class":127}," headers",[110,825,418],{"class":373},[110,827,573],{"class":123},[110,829,543],{"class":377},[110,831,832],{"class":131},"'dist\u002F_headers'",[110,834,649],{"class":377},[110,836,579],{"class":131},[110,838,839],{"class":377},")\n",[110,841,843,846,849,851,854],{"class":112,"line":842},25,[110,844,845],{"class":377},"  .",[110,847,848],{"class":123},"replace",[110,850,543],{"class":377},[110,852,853],{"class":131},"'__CSP__'",[110,855,856],{"class":377},", policy);\n",[110,858,860,863,865,867],{"class":112,"line":859},26,[110,861,862],{"class":123},"writeFileSync",[110,864,543],{"class":377},[110,866,832],{"class":131},[110,868,869],{"class":377},", headers);\n",[110,871,873,876,879,881,884,886,888,891,894],{"class":112,"line":872},27,[110,874,875],{"class":377},"console.",[110,877,878],{"class":123},"log",[110,880,543],{"class":377},[110,882,883],{"class":131},"`[csp] ${",[110,885,722],{"class":377},[110,887,37],{"class":131},[110,889,890],{"class":377},"size",[110,892,893],{"class":131},"} inline script hash(es)`",[110,895,670],{"class":377},[14,897,898,899,901,902,905,906,909],{},"JSON-LD blocks and speculation rules are excluded: they are data, not executable script, and CSP does not require them to be listed. The ",[18,900,52],{}," template in ",[18,903,904],{},"public\u002F"," contains a ",[18,907,908],{},"__CSP__"," placeholder the script fills in:",[101,911,914],{"className":912,"code":913,"language":186,"meta":106},[184],"# public\u002F_headers\n\u002F*\n  Content-Security-Policy: __CSP__\n  Reporting-Endpoints: csp-endpoint=\"\u002Fapi\u002Fcsp-report\"\n",[18,915,913],{"__ignoreMap":106},[14,917,918,919,37],{},"Add the script to the build: ",[18,920,921],{},"\"build\": \"astro build && node scripts\u002Fcsp-hashes.mjs\"",[39,923,925],{"id":924},"step-2-remove-what-should-not-be-inline","Step 2: Remove What Should Not Be Inline",[14,927,928],{},"Hashing works for any inline script, but the fewer there are, the easier the policy is to reason about. Two of the four were removable:",[44,930,931,934],{},[47,932,933],{},"The Google Tag Manager snippet (12 pages) was a leftover from a campaign. Removed.",[47,935,936],{},"The island bootstrap on 391 pages is Astro's own and changes only when Astro upgrades. It stays, hashed.",[14,938,939,940,943],{},"The theme bootstrap must stay inline — it sets the ",[18,941,942],{},"data-theme"," attribute before first paint to avoid a light flash in dark mode — so it stays and is hashed.",[14,945,946,947,950,951,954,955,958],{},"Inline event handlers (",[18,948,949],{},"onclick=\"…\"",") are not covered by script hashes without ",[18,952,953],{},"'unsafe-hashes'",". The site had two, in a Markdown-embedded HTML snippet; both were replaced with an ",[18,956,957],{},"addEventListener"," in a bundled script.",[39,960,962],{"id":961},"step-3-verify-before-enforcing","Step 3: Verify Before Enforcing",[14,964,965,966,969],{},"Deploy with the header name ",[18,967,968],{},"Content-Security-Policy-Report-Only"," first and load each template with DevTools open. A hash mismatch appears as a console error naming the hash the browser computed, which is the fastest way to spot a script whose content differs between pages (for example, one that includes a page-specific value). Scripts that vary per page need either per-path policies or to be rewritten to read the variable from a data attribute, so the script body stays identical everywhere:",[101,971,975],{"className":972,"code":973,"language":974,"meta":106,"style":106},"language-html shiki shiki-themes github-light github-dark","\u003C!-- before: body differs per page, hash differs per page -->\n\u003Cscript is:inline>window.PAGE_ID = \"guides\u002Fdeploying-hugo\";\u003C\u002Fscript>\n\u003C!-- after: body identical everywhere, value read from the DOM -->\n\u003Cbody data-page-id=\"guides\u002Fdeploying-hugo\">\n","html",[18,976,977,982,1013,1018],{"__ignoreMap":106},[110,978,979],{"class":112,"line":113},[110,980,981],{"class":116},"\u003C!-- before: body differs per page, hash differs per page -->\n",[110,983,984,987,991,994,997,1000,1002,1005,1008,1010],{"class":112,"line":120},[110,985,986],{"class":377},"\u003C",[110,988,990],{"class":989},"s9eBZ","script",[110,992,993],{"class":123}," is:inline",[110,995,996],{"class":377},">window.",[110,998,999],{"class":127},"PAGE_ID",[110,1001,418],{"class":373},[110,1003,1004],{"class":131}," \"guides\u002Fdeploying-hugo\"",[110,1006,1007],{"class":377},";\u003C\u002F",[110,1009,990],{"class":989},[110,1011,1012],{"class":377},">\n",[110,1014,1015],{"class":112,"line":135},[110,1016,1017],{"class":116},"\u003C!-- after: body identical everywhere, value read from the DOM -->\n",[110,1019,1020,1022,1024,1027,1030,1033],{"class":112,"line":141},[110,1021,986],{"class":377},[110,1023,564],{"class":989},[110,1025,1026],{"class":123}," data-page-id",[110,1028,1029],{"class":377},"=",[110,1031,1032],{"class":131},"\"guides\u002Fdeploying-hugo\"",[110,1034,1012],{"class":377},[190,1036,1037,1106],{},[193,1038,201,1043,201,1046,201,1049,201,1052],{"viewBox":1039,"role":196,"ariaLabelledBy":1040,"xmlns":200},"0 0 760 240",[1041,1042],"hca-var-title","hca-var-desc",[203,1044,1045],{"id":1041},"Per-page script bodies versus a shared body",[207,1047,1048],{"id":1042},"Before, 38 pages each had an inline script with a different page ID embedded, producing 38 different hashes. After, the page ID moved into a data attribute and every page shares one identical script, producing a single hash.",[211,1050],{"x":213,"y":213,"width":214,"height":1051,"fill":216},"240",[218,1053,221,1054,221,1057,221,1061,221,1066,221,1071,221,1073,221,1077,221,1080,221,1084,221,1089,221,1093,221,1096,221,1099,221,1103,201],{"style":220},[186,1055,1056],{"x":224,"y":225,"fill":226,"style":227},"Keep inline script bodies identical across pages",[186,1058,1060],{"x":1059,"y":234,"fill":226,"style":243},"190","value inside the script",[211,1062],{"x":1063,"y":1064,"width":241,"height":231,"rx":1065,"fill":311,"opacity":237,"stroke":313,"style":314},"40","74","4",[186,1067,1070],{"x":1068,"y":1069,"fill":226,"style":249},"90","94","hash A",[211,1072],{"x":266,"y":1064,"width":241,"height":231,"rx":1065,"fill":311,"opacity":237,"stroke":313,"style":314},[186,1074,1076],{"x":1075,"y":1069,"fill":226,"style":249},"200","hash B",[211,1078],{"x":1079,"y":1064,"width":241,"height":231,"rx":1065,"fill":311,"opacity":237,"stroke":313,"style":314},"260",[186,1081,1083],{"x":1082,"y":1069,"fill":226,"style":249},"310","… hash 38",[186,1085,1088],{"x":1059,"y":1086,"fill":1087,"style":243},"136","#b32b30","38 hashes, policy grows per page",[186,1090,1092],{"x":1091,"y":234,"fill":226,"style":243},"570","value in a data attribute",[211,1094],{"x":317,"y":1064,"width":1075,"height":231,"rx":1065,"fill":281,"opacity":1095,"stroke":283,"style":314},"0.25",[186,1097,1098],{"x":1091,"y":1069,"fill":226,"style":249},"one shared hash",[186,1100,1102],{"x":1091,"y":1086,"fill":1101,"style":243},"#3f6410","1 hash, one site-wide policy",[186,1104,1105],{"x":224,"y":1075,"fill":248,"style":249},"Reading values from the DOM keeps the script cacheable and the policy short",[340,1107,1108],{},"A per-page value inside an inline script is the usual reason hash lists explode; moving it to the markup fixes both problems.",[14,1110,1111],{},"On this site, 38 pages had carried a page-specific analytics label inline; after the change, the site-wide header needed a single hash for that script, and the header itself stayed under 700 bytes. Header size matters more than it seems: some CDNs and proxies reject or truncate response headers above 8 KB, and a policy with hundreds of per-page hashes can approach that.",[39,1113,1115],{"id":1114},"measured-impact","Measured Impact",[1117,1118,1119,1135],"table",{},[1120,1121,1122],"thead",{},[1123,1124,1125,1129,1132],"tr",{},[1126,1127,1128],"th",{},"Measure",[1126,1130,1131],{},"Before",[1126,1133,1134],{},"After",[1136,1137,1138,1156,1166,1176,1187,1198],"tbody",{},[1123,1139,1140,1145,1150],{},[1141,1142,1143],"td",{},[18,1144,24],{},[1141,1146,1147],{},[18,1148,1149],{},"'self' 'unsafe-inline' https:",[1141,1151,1152,1155],{},[18,1153,1154],{},"'self'"," + 3 hashes + 1 origin",[1123,1157,1158,1161,1163],{},[1141,1159,1160],{},"Distinct inline scripts in build",[1141,1162,1065],{},[1141,1164,1165],{},"3",[1123,1167,1168,1171,1174],{},[1141,1169,1170],{},"Pages with a page-specific inline script",[1141,1172,1173],{},"38",[1141,1175,213],{},[1123,1177,1178,1181,1184],{},[1141,1179,1180],{},"Post-build script run time",[1141,1182,1183],{},"—",[1141,1185,1186],{},"1.4 s (712 pages)",[1123,1188,1189,1192,1195],{},[1141,1190,1191],{},"Mozilla Observatory CSP test",[1141,1193,1194],{},"fail (unsafe-inline)",[1141,1196,1197],{},"pass",[1123,1199,1200,1203,1205],{},[1141,1201,1202],{},"Hash-mismatch reports after enforcing (4 weeks)",[1141,1204,1183],{},[1141,1206,213],{},[190,1208,1209,1292],{},[193,1210,201,1215,201,1218,201,1221,201,1223],{"viewBox":1211,"role":196,"ariaLabelledBy":1212,"xmlns":200},"0 0 760 260",[1213,1214],"hca-before-title","hca-before-desc",[203,1216,1217],{"id":1213},"What the policy permits, before and after",[207,1219,1220],{"id":1214},"Two panels. Before, script-src allowed any inline script and any HTTPS origin, so an injected inline script and a script from an attacker's domain would both run. After, only three hashed inline scripts, the site's own files and one analytics origin are allowed, and both attack examples are blocked.",[211,1222],{"x":213,"y":213,"width":214,"height":1079,"fill":216},[218,1224,221,1225,221,1228,221,1235,221,1240,221,1245,221,1250,221,1254,221,1256,221,1259,221,1261,221,1266,221,1269,221,1273,221,1276,221,1280,221,1282,221,1284,221,1287,221,1289,201],{"style":220},[186,1226,1227],{"x":224,"y":225,"fill":226,"style":227},"Two attacks, two policies",[211,1229],{"x":1063,"y":1230,"width":1231,"height":1232,"rx":1233,"fill":311,"opacity":1234,"stroke":313,"style":238},"50","330","180","12","0.08",[186,1236,1239],{"x":1237,"y":1238,"fill":226,"style":243},"205","76","'unsafe-inline' https:",[186,1241,1244],{"x":1242,"y":1243,"fill":226},"62","112","injected \u003Cscript>alert(1)\u003C\u002Fscript>",[186,1246,1249],{"x":1247,"y":1243,"fill":1087,"style":1248},"340","font-weight:700;text-anchor:end","runs",[186,1251,1253],{"x":1242,"y":1252,"fill":226},"144","\u003Cscript src=\"https:\u002F\u002Fevil.example\">",[186,1255,1249],{"x":1247,"y":1252,"fill":1087,"style":1248},[186,1257,1258],{"x":1242,"y":307,"fill":226},"theme bootstrap",[186,1260,1249],{"x":1247,"y":307,"fill":1101,"style":1248},[186,1262,1265],{"x":1242,"y":1263,"fill":248,"style":1264},"208","font-size:11px","the policy passes nearly everything",[211,1267],{"x":1268,"y":1230,"width":1231,"height":1232,"rx":1233,"fill":281,"opacity":312,"stroke":283,"style":238},"390",[186,1270,1272],{"x":1271,"y":1238,"fill":226,"style":243},"555","'self' + 3 hashes + 1 origin",[186,1274,1244],{"x":1275,"y":1243,"fill":226},"412",[186,1277,1279],{"x":1278,"y":1243,"fill":1101,"style":1248},"700","blocked",[186,1281,1253],{"x":1275,"y":1252,"fill":226},[186,1283,1279],{"x":1278,"y":1252,"fill":1101,"style":1248},[186,1285,1286],{"x":1275,"y":307,"fill":226},"theme bootstrap (hash listed)",[186,1288,1249],{"x":1278,"y":307,"fill":1101,"style":1248},[186,1290,1291],{"x":1275,"y":1263,"fill":248,"style":1264},"only known content executes",[340,1293,1294],{},"The legitimate inline script behaves identically; only the attacker's scripts change outcome.",[39,1296,1298],{"id":1297},"maintaining-it-over-time","Maintaining It Over Time",[14,1300,1301,1302,1306,1307,1310,1311,1314],{},"The post-build step makes the policy self-maintaining for edits: change the theme bootstrap and the next build lists its new hash. Two events still need attention. An ",[1303,1304,1305],"strong",{},"Astro upgrade"," changes the island bootstrap's content, so the hash changes — harmless, because the script regenerates it, but worth noting in the upgrade pull request so reviewers do not mistake it for tampering. A ",[1303,1308,1309],{},"new integration"," may add an inline script; the build log line ",[18,1312,1313],{},"[csp] N inline script hash(es)"," makes that visible, and a CI assertion that N matches an expected value turns it into a deliberate decision rather than an accident. On this site the expected count lives in a one-line file next to the script, and changing it requires a review from the security owner.",[39,1316,1318],{"id":1317},"pitfalls-rollback","Pitfalls & Rollback",[44,1320,1321,1327,1333,1339,1351],{},[47,1322,1323,1326],{},[1303,1324,1325],{},"Hashing source files instead of output."," Minification and integrations change the bytes. Hash the final HTML.",[47,1328,1329,1332],{},[1303,1330,1331],{},"Trimming whitespace before hashing."," The browser hashes the exact content including leading newlines. Hash it byte for byte.",[47,1334,1335,1338],{},[1303,1336,1337],{},"Per-page values in inline scripts."," They produce one hash per page. Move values into data attributes.",[47,1340,1341,1347,1348,1350],{},[1303,1342,1343,1344,1346],{},"Forgetting ",[18,1345,20],{}," fallbacks."," When hashes are present, modern browsers ignore ",[18,1349,20],{}," anyway; remove it so older tooling does not flag the policy.",[47,1352,1353,1356,1357,1359],{},[1303,1354,1355],{},"Rollback:"," switch the header back to report-only, or restore the previous ",[18,1358,52],{}," template. The post-build script can stay; it only fills a placeholder.",[39,1361,1363],{"id":1362},"conclusion","Conclusion",[14,1365,1366,1367,1369],{},"Static output is what makes a hash-based CSP practical: every inline script is known when the build finishes, so a one-second post-build step can hash them all and write the policy. On a 712-page Astro site, that replaced ",[18,1368,1239],{}," with three hashes and one origin, blocked the two classic injection patterns, and has needed no manual policy edits since — only a glance at the hash count when Astro or an integration changes.",[39,1371,1373],{"id":1372},"faq","FAQ",[1375,1376,1378],"h3",{"id":1377},"why-use-hashes-instead-of-nonces-on-a-static-site","Why use hashes instead of nonces on a static site?",[14,1380,1381],{},"Nonces must be random per response, which requires a server to generate them. A static file served from a CDN is identical for every reader, so a nonce would be the same each time and provide no protection. Hashes describe the script content itself and work perfectly with static files.",[1375,1383,1385],{"id":1384},"does-astro-generate-csp-hashes-itself","Does Astro generate CSP hashes itself?",[14,1387,1388],{},"Recent Astro versions include an experimental CSP option that hashes scripts and styles it controls and emits a meta tag. A post-build script that hashes the final HTML covers everything, including inline scripts added by integrations, and can write an HTTP header instead.",[1375,1390,1392],{"id":1391},"what-about-inline-event-handlers-like-onclick","What about inline event handlers like onclick?",[14,1394,1395],{},"Hashes do not cover inline event handler attributes unless you add 'unsafe-hashes', which is weaker. Move handlers into scripts with addEventListener; on a static site this is usually a small refactor.",[1375,1397,1399],{"id":1398},"do-i-need-a-different-policy-per-page","Do I need a different policy per page?",[14,1401,1402],{},"Only if pages have different inline scripts. Most Astro sites have the same few inline scripts on every page, so one site-wide list of hashes works. If a page has unique inline scripts, a per-path header rule keeps the site-wide policy tight.",[39,1404,1406],{"id":1405},"related","Related",[44,1408,1409,1420,1425,1432,1439],{},[47,1410,1411,1414,1415,1419],{},[1303,1412,1413],{},"Parent:"," ",[33,1416,1418],{"href":1417},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002F","Security Headers and Hardening for Static Sites"," — the full hardening set.",[47,1421,1422,1424],{},[33,1423,36],{"href":35}," — inventory and report-only rollout.",[47,1426,1427,1431],{},[33,1428,1430],{"href":1429},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsubresource-integrity-for-third-party-assets\u002F","Subresource Integrity for Third-Party Assets"," — hashing external scripts too.",[47,1433,1434,1438],{},[33,1435,1437],{"href":1436},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance\u002F","Astro Islands vs Full Hydration Performance"," — where the island bootstrap comes from.",[47,1440,1441,1445],{},[33,1442,1444],{"href":1443},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Floading-google-tag-manager-without-hurting-inp\u002F","Loading Google Tag Manager Without Hurting INP"," — if a tag manager must stay.",[1447,1448,1449],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":106,"searchDepth":120,"depth":120,"links":1451},[1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1467],{"id":41,"depth":120,"text":42},{"id":62,"depth":120,"text":63},{"id":345,"depth":120,"text":346},{"id":924,"depth":120,"text":925},{"id":961,"depth":120,"text":962},{"id":1114,"depth":120,"text":1115},{"id":1297,"depth":120,"text":1298},{"id":1317,"depth":120,"text":1318},{"id":1362,"depth":120,"text":1363},{"id":1372,"depth":120,"text":1373,"children":1462},[1463,1464,1465,1466],{"id":1377,"depth":135,"text":1378},{"id":1384,"depth":135,"text":1385},{"id":1391,"depth":135,"text":1392},{"id":1398,"depth":135,"text":1399},{"id":1405,"depth":120,"text":1406},[1469,1471,1474,1475],{"name":1470,"item":499},"Home",{"name":1472,"item":1473},"Production-Ready Deployment & CI\u002FCD Workflows","\u002Fproduction-ready-deployment-cicd-workflows\u002F",{"name":1418,"item":1417},{"name":5,"item":1476},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fhash-based-csp-for-inline-scripts-in-astro\u002F","2026-09-18","Compute SHA-256 hashes for every inline script in an Astro build, write them into the CSP header automatically, and keep unsafe-inline out of the policy.","md",[1481,1482,1483,1484],{"q":1378,"a":1381},{"q":1385,"a":1388},{"q":1392,"a":1395},{"q":1399,"a":1402},{},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fhash-based-csp-for-inline-scripts-in-astro",{"title":5,"description":1478},"production-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fhash-based-csp-for-inline-scripts-in-astro\u002Findex","article","x8eBqDHX7nrOyGyeJEizETiJx2jqLan838jCEpPH91E",[1492,1495,1498,1501,1504,1507,1510,1513,1516,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546,1549,1552,1555,1558,1561,1564,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1705,1708,1711,1714,1717,1720,1723,1726,1729,1732,1735,1737,1740,1743,1746,1749,1752,1755,1758,1761,1764,1767,1770,1773,1776,1779,1782,1785,1788,1791,1794,1797,1800,1803,1806,1809,1812,1815,1818,1820,1823,1826,1829,1832,1835,1838,1841,1844,1847,1850,1853,1856,1859,1862,1865,1868,1871,1874,1877,1880,1883,1886,1889,1892,1895,1898,1901,1904,1907,1910,1913,1916,1919,1922,1925,1928,1931,1934,1937,1940,1943,1946,1949,1952,1955,1958,1961,1964,1967,1970,1973,1976,1979,1982,1985,1986,1988,1991,1993,1995,1998,2001,2004,2007,2010,2013,2016,2019,2022,2025,2028,2031],{"path":1493,"title":1494},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fastro-vs-eleventy-build-times-at-10000-pages","Astro vs Eleventy Build Times at 10,000 Pages",{"path":1496,"title":1497},"\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":1499,"title":1500},"\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":1502,"title":1503},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1505,"title":1506},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fshortcodes-vs-components-for-docs-authors","Shortcodes vs Components for Docs Authors",{"path":1508,"title":1509},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002Fsidebar-navigation-in-astro-and-eleventy","Sidebar Navigation in Astro and Eleventy",{"path":1511,"title":1512},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fcustomizing-starlight-without-forking-the-theme","Customizing Starlight Without Forking the Theme",{"path":1514,"title":1515},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fdocusaurus-vs-starlight-for-product-documentation","Docusaurus vs Starlight for Product Documentation",{"path":1517,"title":1518},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress","Docs Frameworks: Docusaurus, Starlight and VitePress",{"path":1520,"title":1521},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fmdx-vs-markdoc-for-docs-content","MDX vs Markdoc for Docs Content",{"path":1523,"title":1524},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fmigrating-from-mkdocs-to-starlight","Migrating from MkDocs to Starlight",{"path":1526,"title":1527},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fversioned-documentation-with-docusaurus","Versioned Documentation with Docusaurus",{"path":1529,"title":1530},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fvitepress-for-library-documentation","VitePress for Library Documentation",{"path":1532,"title":1533},"\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":1535,"title":1536},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Fhugo-partialcached-for-faster-builds","Hugo partialCached for Faster Builds",{"path":1538,"title":1539},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1541,"title":1542},"\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":1544,"title":1545},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002Freducing-hugo-memory-usage-on-ci-runners","Reducing Hugo Memory Usage on CI Runners",{"path":1547,"title":1548},"\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":1550,"title":1551},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":1553,"title":1554},"\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":1556,"title":1557},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":1559,"title":1560},"\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":1562,"title":1563},"\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":1565,"title":1566},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fspeeding-up-slow-jekyll-builds","Speeding Up Slow Jekyll Builds",{"path":1568,"title":1569},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fupgrading-jekyll-and-ruby-versions-safely","Upgrading Jekyll and Ruby Versions Safely",{"path":1571,"title":1572},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fconverting-front-matter-at-scale-during-migration","Converting Front Matter at Scale During Migration",{"path":1574,"title":1575},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":1577,"title":1578},"\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":1580,"title":1581},"\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":1583,"title":1584},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-gatsby-to-astro","Migrating from Gatsby to Astro",{"path":1586,"title":1587},"\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":1589,"title":1590},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-wordpress-to-a-static-site-generator","Migrating WordPress to a Static Site Generator",{"path":1592,"title":1593},"\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":1595,"title":1596},"\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":1598,"title":1599},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1601,"title":1602},"\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":1604,"title":1605},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Fnextjs-app-router-static-export-limitations","Next.js App Router Static Export Limitations",{"path":1607,"title":1608},"\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":1610,"title":1611},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites\u002Foptimizing-images-in-nextjs-static-export","Optimizing Images in Next.js Static Export",{"path":1613,"title":1614},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fadding-pagefind-to-an-astro-site","Adding Pagefind to an Astro Site",{"path":1616,"title":1617},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fbuilding-a-lunr-index-at-build-time-in-eleventy","Building a Lunr Index at Build Time in Eleventy",{"path":1619,"title":1620},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites","Search for Static Sites",{"path":1622,"title":1623},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Findexing-hugo-sites-with-pagefind","Indexing Hugo Sites with Pagefind",{"path":1625,"title":1626},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fmultilingual-search-on-static-sites","Multilingual Search on Static Sites",{"path":1628,"title":1629},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fpagefind-vs-algolia-docsearch","Pagefind vs Algolia DocSearch",{"path":1631,"title":1632},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fsearch-index-size-budgets-for-large-docs","Search Index Size Budgets for Large Docs",{"path":1634,"title":1635},"\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":1637,"title":1638},"\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":1640,"title":1641},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fevaluating-ssg-accessibility-defaults","Evaluating SSG Accessibility Defaults",{"path":1643,"title":1644},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":1646,"title":1647},"\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":1649,"title":1650},"\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":1652,"title":1653},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Ftotal-cost-of-ownership-for-static-site-generators","Total Cost of Ownership for Static Site Generators",{"path":1655,"title":1656},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fcache-busting-with-content-hashed-filenames","Cache Busting with Content-Hashed Filenames",{"path":1658,"title":1659},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":1661,"title":1662},"\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":1664,"title":1665},"\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":1667,"title":1668},"\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":1670,"title":1671},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fstale-while-revalidate-for-static-html","Stale-While-Revalidate for Static HTML",{"path":1673,"title":1674},"\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":1676,"title":1677},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-cookie-banners","Fixing CLS from Cookie Banners",{"path":1679,"title":1680},"\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":1682,"title":1683},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites\u002Ffixing-cls-from-sticky-headers-and-anchor-links","Fixing CLS from Sticky Headers and Anchor Links",{"path":1685,"title":1686},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":1688,"title":1689},"\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":1691,"title":1692},"\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":1694,"title":1695},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Ffont-display-optional-vs-swap","font-display: optional vs swap",{"path":1697,"title":1698},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":1700,"title":1701},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fmetric-matched-fallback-fonts-with-size-adjust","Metric-Matched Fallback Fonts with size-adjust",{"path":1703,"title":1704},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fpreloading-fonts-without-double-downloads","Preloading Fonts Without Double Downloads",{"path":1706,"title":1707},"\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":1709,"title":1710},"\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":1712,"title":1713},"\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":1715,"title":1716},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Fgenerating-open-graph-images-at-build-time","Generating Open Graph Images at Build Time",{"path":1718,"title":1719},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":1721,"title":1722},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Flazy-loading-images-without-hurting-lcp","Lazy-Loading Images Without Hurting LCP",{"path":1724,"title":1725},"\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":1727,"title":1728},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002Fresponsive-images-with-srcset-in-eleventy","Responsive Images with srcset in Eleventy",{"path":1730,"title":1731},"\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":1733,"title":1734},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":1736,"title":1437},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance",{"path":1738,"title":1739},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fdeferring-hydration-with-client-visible-in-astro","Deferring Hydration with client:visible in Astro",{"path":1741,"title":1742},"\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":1744,"title":1745},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":1747,"title":1748},"\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":1750,"title":1751},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Freplacing-react-islands-with-web-components","Replacing React Islands with Web Components",{"path":1753,"title":1754},"\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":1756,"title":1757},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Ffixing-lcp-on-text-heavy-documentation-pages","Fixing LCP on Text-Heavy Documentation Pages",{"path":1759,"title":1760},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":1762,"title":1763},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites\u002Fmeasuring-lcp-subparts-with-devtools","Measuring LCP Subparts with DevTools",{"path":1765,"title":1766},"\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":1768,"title":1769},"\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":1771,"title":1772},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Fcomparing-lab-and-field-data-with-crux","Comparing Lab and Field Data with CrUX",{"path":1774,"title":1775},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci","Performance Budgets and Lighthouse CI",{"path":1777,"title":1778},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Freducing-lighthouse-score-variance-in-ci","Reducing Lighthouse Score Variance in CI",{"path":1780,"title":1781},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Frunning-webpagetest-scripts-against-preview-deploys","Running WebPageTest Scripts Against Preview Deploys",{"path":1783,"title":1784},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Fsetting-up-lighthouse-ci-for-a-static-site","Setting Up Lighthouse CI for a Static Site",{"path":1786,"title":1787},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Ftracking-bundle-size-per-pull-request","Tracking Bundle Size per Pull Request",{"path":1789,"title":1790},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci\u002Fwriting-a-performance-budget-that-fails-builds","Writing a Performance Budget That Fails Builds",{"path":1792,"title":1793},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fauditing-unused-preloads","Auditing Unused Preloads",{"path":1795,"title":1796},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed","Resource Hints and Navigation Speed",{"path":1798,"title":1799},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Finstant-navigation-with-speculation-rules","Instant Navigation with Speculation Rules",{"path":1801,"title":1802},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fpreconnect-vs-dns-prefetch-on-static-sites","Preconnect vs DNS-Prefetch on Static Sites",{"path":1804,"title":1805},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fprefetching-links-in-astro","Prefetching Links in Astro",{"path":1807,"title":1808},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fview-transitions-on-multi-page-static-sites","View Transitions on Multi-Page Static Sites",{"path":1810,"title":1811},"\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":1813,"title":1814},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":1816,"title":1817},"\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":1819,"title":1444},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Floading-google-tag-manager-without-hurting-inp",{"path":1821,"title":1822},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Frunning-third-party-scripts-in-a-web-worker-with-partytown","Running Third-Party Scripts in a Web Worker with Partytown",{"path":1824,"title":1825},"\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":1827,"title":1828},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":1830,"title":1831},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fconfiguring-redirects-on-cloudflare-pages","Configuring Redirects on Cloudflare Pages",{"path":1833,"title":1834},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fcustom-domains-and-tls-on-cloudflare-pages","Custom Domains and TLS on Cloudflare Pages",{"path":1836,"title":1837},"\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":1839,"title":1840},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":1842,"title":1843},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fmigrating-from-cloudflare-pages-to-workers-static-assets","Migrating from Cloudflare Pages to Workers Static Assets",{"path":1845,"title":1846},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fchecking-links-in-pull-requests","Checking Links in Pull Requests",{"path":1848,"title":1849},"\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":1851,"title":1852},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Feditorial-checks-with-vale-in-ci","Editorial Checks with Vale in CI",{"path":1854,"title":1855},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":1857,"title":1858},"\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":1860,"title":1861},"\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":1863,"title":1864},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fbuilding-astro-sites-with-github-actions","Building Astro Sites with GitHub Actions",{"path":1866,"title":1867},"\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":1869,"title":1870},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fdeploying-to-github-pages-with-actions","Deploying to GitHub Pages with Actions",{"path":1872,"title":1873},"\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":1875,"title":1876},"\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":1878,"title":1879},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":1881,"title":1882},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fmatrix-builds-for-multi-site-monorepos","Matrix Builds for Multi-Site Monorepos",{"path":1884,"title":1885},"\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":1887,"title":1888},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":1890,"title":1891},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fincremental-builds-in-astro-with-the-content-layer","Incremental Builds in Astro with the Content Layer",{"path":1893,"title":1894},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":1896,"title":1897},"\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":1899,"title":1900},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fremote-caching-with-turborepo-for-ssg-monorepos","Remote Caching with Turborepo for SSG Monorepos",{"path":1902,"title":1903},"\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":1905,"title":1906},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":1908,"title":1909},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Falerting-on-cache-hit-ratio-drops","Alerting on Cache Hit Ratio Drops",{"path":1911,"title":1912},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Fbuilding-a-core-web-vitals-dashboard-from-rum-data","Building a Core Web Vitals Dashboard from RUM Data",{"path":1914,"title":1915},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Fcrawling-for-broken-links-on-a-schedule","Crawling for Broken Links on a Schedule",{"path":1917,"title":1918},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production","Monitoring Static Sites in Production",{"path":1920,"title":1921},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Flogging-404s-at-the-edge","Logging 404s at the Edge",{"path":1923,"title":1924},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Fuptime-and-synthetic-checks-for-static-sites","Uptime and Synthetic Checks for Static Sites",{"path":1926,"title":1927},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fconfiguring-vercel-for-hugo-and-eleventy","Configuring Vercel for Hugo and Eleventy",{"path":1929,"title":1930},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":1932,"title":1933},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":1935,"title":1936},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-redirects-and-rewrites-for-static-sites","Netlify Redirects and Rewrites for Static Sites",{"path":1938,"title":1939},"\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":1941,"title":1942},"\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":1944,"title":1945},"\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":1947,"title":1948},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":1950,"title":1951},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":1953,"title":1954},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpassword-protecting-preview-deployments","Password-Protecting Preview Deployments",{"path":1956,"title":1957},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpreviewing-headless-cms-drafts","Previewing Headless CMS Drafts",{"path":1959,"title":1960},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fvisual-regression-testing-on-preview-deploys","Visual Regression Testing on Preview Deploys",{"path":1962,"title":1963},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":1965,"title":1966},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fcanary-releases-for-static-sites","Canary Releases for Static Sites",{"path":1968,"title":1969},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Ffeature-flags-on-static-sites","Feature Flags on Static Sites",{"path":1971,"title":1972},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":1974,"title":1975},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frolling-back-a-bad-static-deploy-in-under-a-minute","Rolling Back a Bad Static Deploy in Under a Minute",{"path":1977,"title":1978},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url","Running Smoke Tests Against a Preview URL",{"path":1980,"title":1981},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fauditing-npm-dependencies-in-ssg-pipelines","Auditing npm Dependencies in SSG Pipelines",{"path":1983,"title":1984},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fenabling-hsts-and-preload-safely","Enabling HSTS and Preload Safely",{"path":1486,"title":5},{"path":1987,"title":1418},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites",{"path":1989,"title":1990},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsecuring-deploy-credentials-with-github-oidc","Securing Deploy Credentials with GitHub OIDC",{"path":1992,"title":1430},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsubresource-integrity-for-third-party-assets",{"path":1994,"title":36},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fwriting-a-content-security-policy-for-a-static-site",{"path":1996,"title":1997},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fclean-urls-and-trailing-slashes-on-s3","Clean URLs and Trailing Slashes on S3",{"path":1999,"title":2000},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fcloudfront-functions-for-redirects","CloudFront Functions for Redirects",{"path":2002,"title":2003},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fdeploying-a-static-site-to-s3-and-cloudfront","Deploying a Static Site to S3 and CloudFront",{"path":2005,"title":2006},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites","Self-Hosting Static Sites on S3, Nginx and Caddy",{"path":2008,"title":2009},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-caddy","Serving a Static Site with Caddy",{"path":2011,"title":2012},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx","Serving a Static Site with Nginx",{"path":2014,"title":2015},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fzero-downtime-deploys-with-symlink-swaps","Zero-Downtime Deploys with Symlink Swaps",{"path":2017,"title":2018},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fadding-a-contact-form-with-cloudflare-workers","Adding a Contact Form with Cloudflare Workers",{"path":2020,"title":2021},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fhandling-form-submissions-on-a-static-site","Handling Form Submissions on a Static Site",{"path":2023,"title":2024},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites","Serverless Functions for Static Sites",{"path":2026,"title":2027},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fnetlify-functions-vs-cloudflare-workers","Netlify Functions vs Cloudflare Workers",{"path":2029,"title":2030},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fprotecting-a-static-site-behind-authentication","Protecting a Static Site Behind Authentication",{"path":2032,"title":2033},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fproxying-third-party-apis-from-an-edge-function","Proxying Third-Party APIs from an Edge Function",1789722847339]