[{"data":1,"prerenderedAt":2128},["ShallowReactive",2],{"page:\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites":3,"all-docs-nav":1593},{"id":4,"title":5,"body":6,"breadcrumb":1567,"dateModified":1574,"datePublished":1574,"description":1575,"extension":1576,"faq":1577,"meta":1585,"navigation":1586,"path":1587,"seo":1588,"slug":1589,"stem":1590,"type":1591,"__hash__":1592},"content\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Findex.md","Security Headers and Hardening for Static Sites",{"type":7,"value":8,"toc":1543},"minimark",[9,13,17,40,251,256,259,269,302,305,309,316,323,329,341,477,481,503,507,510,571,581,585,588,609,619,713,717,720,734,744,758,772,782,786,789,904,930,934,937,1106,1114,1118,1137,1159,1162,1166,1169,1191,1205,1222,1234,1247,1251,1259,1263,1266,1366,1370,1420,1424,1444,1448,1453,1456,1460,1463,1467,1470,1474,1477,1481,1484,1488,1539],[10,11,5],"h1",{"id":12},"security-headers-and-hardening-for-static-sites",[14,15,16],"p",{},"\"It's just static files\" is the most common reason static sites ship without security headers. There is no database to inject into and no server-side code to exploit, so the threat model feels empty. It is not. Browsers run whatever JavaScript a page loads, and static sites load plenty: analytics, embeds, consent managers, chat widgets and their own bundles, all built from hundreds of npm packages by a CI pipeline holding a token that can overwrite every page. Security for a static site is about limiting what that JavaScript can do, making sure the right JavaScript is served over the right connection, and keeping the pipeline that publishes it from being turned against you.",[14,18,19,20,24,25,28,29,34,35,39],{},"This topic covers both halves. The ",[21,22,23],"strong",{},"edge half"," is response headers — Content-Security-Policy, Strict-Transport-Security, Subresource Integrity, framing and referrer controls — set once at the host and applied to every page. The ",[21,26,27],{},"pipeline half"," is dependency auditing and deploy credentials. It sits inside ",[30,31,33],"a",{"href":32},"\u002Fproduction-ready-deployment-cicd-workflows\u002F","Production-Ready Deployment & CI\u002FCD Workflows"," and builds on the header mechanics from ",[30,36,38],{"href":37},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002F","Cloudflare Pages Edge Caching Setup",".",[41,42,43,247],"figure",{},[44,45,52,53,52,57,52,61,52,68,52,233],"svg",{"viewBox":46,"role":47,"ariaLabelledBy":48,"xmlns":51},"0 0 780 320","img",[49,50],"shs-threat-title","shs-threat-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[54,55,56],"title",{"id":49},"Threats to a static site and the control for each",[58,59,60],"desc",{"id":50},"A pipeline from source to reader with threats and controls at each stage. At the dependency stage, a malicious npm package is countered by lockfiles, audits and provenance. At the CI stage, a leaked deploy token is countered by short-lived OIDC credentials. At the network stage, a downgrade to HTTP is countered by HSTS. In the browser, injected or compromised scripts are countered by CSP and SRI, and clickjacking by frame-ancestors.",[62,63],"rect",{"x":64,"y":64,"width":65,"height":66,"fill":67},"0","780","320","#ffffff",[69,70,72,73,72,81,72,90,72,96,72,102,72,105,72,109,72,112,72,115,72,119,72,122,72,125,72,129,72,132,72,148,72,156,72,162,72,166,72,168,72,171,72,174,72,176,72,179,72,182,72,184,72,187,72,190,72,197,72,201,72,205,72,207,72,210,72,213,72,215,72,218,72,221,72,223,72,226,72,229,52],"g",{"style":71},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[74,75,80],"text",{"x":76,"y":77,"fill":78,"style":79},"390","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","Four places an attacker can get in, four controls",[62,82],{"x":83,"y":84,"width":85,"height":84,"rx":86,"fill":87,"stroke":88,"style":89},"30","60","160","10","#f8fafc","#d9e2ef","stroke-width:1.5px",[74,91,95],{"x":92,"y":93,"fill":78,"style":94},"110","86","font-weight:700;text-anchor:middle","Dependencies",[74,97,101],{"x":92,"y":98,"fill":99,"style":100},"106","#556071","font-size:11px;text-anchor:middle","npm, themes, plugins",[62,103],{"x":104,"y":84,"width":85,"height":84,"rx":86,"fill":87,"stroke":88,"style":89},"220",[74,106,108],{"x":107,"y":93,"fill":78,"style":94},"300","CI pipeline",[74,110,111],{"x":107,"y":98,"fill":99,"style":100},"build + deploy token",[62,113],{"x":114,"y":84,"width":85,"height":84,"rx":86,"fill":87,"stroke":88,"style":89},"410",[74,116,118],{"x":117,"y":93,"fill":78,"style":94},"490","Network",[74,120,121],{"x":117,"y":98,"fill":99,"style":100},"reader to edge",[62,123],{"x":124,"y":84,"width":85,"height":84,"rx":86,"fill":87,"stroke":88,"style":89},"600",[74,126,128],{"x":127,"y":93,"fill":78,"style":94},"680","Browser",[74,130,131],{"x":127,"y":98,"fill":99,"style":100},"scripts, frames",[69,133,136,137,136,142,136,145,72],{"stroke":99,"fill":134,"style":135},"none","stroke-width:2px","\n      ",[138,139],"path",{"d":140,"style":141},"M192 90 L216 90","marker-end:url(#shs-arrow)",[138,143],{"d":144,"style":141},"M382 90 L406 90",[138,146],{"d":147,"style":141},"M572 90 L596 90",[62,149],{"x":83,"y":150,"width":85,"height":151,"rx":152,"fill":153,"opacity":154,"stroke":155,"style":89},"140","50","8","#ff595e","0.12","#d83b41",[74,157,161],{"x":92,"y":158,"fill":159,"style":160},"162","#b32b30","font-size:11px;font-weight:700;text-anchor:middle","malicious package",[74,163,165],{"x":92,"y":164,"fill":99,"style":100},"178","runs at build time",[62,167],{"x":104,"y":150,"width":85,"height":151,"rx":152,"fill":153,"opacity":154,"stroke":155,"style":89},[74,169,170],{"x":107,"y":158,"fill":159,"style":160},"leaked token",[74,172,173],{"x":107,"y":164,"fill":99,"style":100},"publishes anything",[62,175],{"x":114,"y":150,"width":85,"height":151,"rx":152,"fill":153,"opacity":154,"stroke":155,"style":89},[74,177,178],{"x":117,"y":158,"fill":159,"style":160},"HTTP downgrade",[74,180,181],{"x":117,"y":164,"fill":99,"style":100},"content tampered",[62,183],{"x":124,"y":150,"width":85,"height":151,"rx":152,"fill":153,"opacity":154,"stroke":155,"style":89},[74,185,186],{"x":127,"y":158,"fill":159,"style":160},"injected script",[74,188,189],{"x":127,"y":164,"fill":99,"style":100},"clickjacking",[62,191],{"x":83,"y":192,"width":85,"height":193,"rx":152,"fill":194,"opacity":195,"stroke":196,"style":89},"210","70","#8ac926","0.16","#5a8a16",[74,198,200],{"x":92,"y":199,"fill":78,"style":94},"236","lockfile, audit,",[74,202,204],{"x":92,"y":203,"fill":78,"style":94},"254","provenance",[62,206],{"x":104,"y":192,"width":85,"height":193,"rx":152,"fill":194,"opacity":195,"stroke":196,"style":89},[74,208,209],{"x":107,"y":199,"fill":78,"style":94},"OIDC, scoped,",[74,211,212],{"x":107,"y":203,"fill":78,"style":94},"short-lived",[62,214],{"x":114,"y":192,"width":85,"height":193,"rx":152,"fill":194,"opacity":195,"stroke":196,"style":89},[74,216,217],{"x":117,"y":199,"fill":78,"style":94},"HSTS",[74,219,220],{"x":117,"y":203,"fill":78,"style":94},"+ preload",[62,222],{"x":124,"y":192,"width":85,"height":193,"rx":152,"fill":194,"opacity":195,"stroke":196,"style":89},[74,224,225],{"x":127,"y":199,"fill":78,"style":94},"CSP, SRI,",[74,227,228],{"x":127,"y":203,"fill":78,"style":94},"frame-ancestors",[74,230,232],{"x":76,"y":231,"fill":99,"style":100},"306","Headers protect the right half; pipeline hygiene protects the left half",[234,235,72,236,52],"defs",{},[237,238,136,244,72],"marker",{"id":239,"viewBox":240,"refX":152,"refY":241,"markerWidth":242,"markerHeight":242,"orient":243},"shs-arrow","0 0 10 10","5","7","auto-start-reverse",[138,245],{"d":246,"fill":99},"M0 0 L10 5 L0 10 z",[248,249,250],"figcaption",{},"Static sites remove the server from the threat model, not the browser or the pipeline — which is where the controls in this topic apply.",[252,253,255],"h2",{"id":254},"a-baseline-header-set","A Baseline Header Set",[14,257,258],{},"Every static site should ship these headers on every HTML response. They cost nothing in performance, and none of them requires changes to page content:",[260,261,266],"pre",{"className":262,"code":264,"language":74,"meta":265},[263],"language-text","# _headers (Cloudflare Pages \u002F Netlify syntax)\n\u002F*\n  Strict-Transport-Security: max-age=31536000; includeSubDomains\n  X-Content-Type-Options: nosniff\n  Referrer-Policy: strict-origin-when-cross-origin\n  X-Frame-Options: DENY\n  Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()\n  Cross-Origin-Opener-Policy: same-origin\n","",[267,268,264],"code",{"__ignoreMap":265},[14,270,271,272,274,275,278,279,282,283,286,287,290,291,293,294,297,298,301],{},"What each does, in one line: ",[21,273,217],{}," tells browsers to use HTTPS for your domain for a year, closing the downgrade window on the first plain-HTTP request. ",[21,276,277],{},"nosniff"," stops browsers guessing content types, so a file served as ",[267,280,281],{},"text\u002Fplain"," is never executed as script. ",[21,284,285],{},"Referrer-Policy"," stops full URLs — which may include query parameters with tokens — leaking to other sites. ",[21,288,289],{},"X-Frame-Options"," (and the CSP ",[267,292,228],{}," directive that supersedes it) prevents your pages being framed for clickjacking. ",[21,295,296],{},"Permissions-Policy"," disables powerful browser features no page on the site uses, so an injected script cannot request them. ",[21,299,300],{},"COOP"," isolates your browsing context from cross-origin windows it opens.",[14,303,304],{},"On a documentation site, adding this set moved the Mozilla Observatory grade from F to B, and the securityheaders.com grade from F to A-, with zero content changes. The remaining gap in both was the Content-Security-Policy.",[252,306,308],{"id":307},"content-security-policy","Content-Security-Policy",[14,310,311,312,315],{},"CSP is the most powerful header and the one that takes real work, because it describes which sources of script, style, image, font and connection the page may use. A good policy makes an injected ",[267,313,314],{},"\u003Cscript>"," or a compromised third-party script unable to load code from anywhere unexpected or to send data anywhere unexpected.",[14,317,318,319,322],{},"Static sites are well suited to strict CSP because their HTML is fixed at build time: every inline script can be hashed during the build, so the policy can forbid ",[267,320,321],{},"unsafe-inline"," entirely.",[260,324,327],{"className":325,"code":326,"language":74,"meta":265},[263],"Content-Security-Policy:\n  default-src 'self';\n  script-src 'self' 'sha256-Qm9vdHN0cmFwIHRoZW1lIHNjcmlwdA==' https:\u002F\u002Fplausible.io;\n  style-src 'self';\n  img-src 'self' data: https:\u002F\u002Fimg.example-cdn.net;\n  font-src 'self';\n  connect-src 'self' https:\u002F\u002Fplausible.io;\n  frame-ancestors 'none';\n  base-uri 'self';\n  form-action 'self' https:\u002F\u002Fforms.example.com;\n  upgrade-insecure-requests\n",[267,328,326],{"__ignoreMap":265},[14,330,331,332,336,337,39],{},"Writing one from scratch and rolling it out safely is covered in ",[30,333,335],{"href":334},"\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","; computing the hashes automatically at build time for Astro's inline scripts is in ",[30,338,340],{"href":339},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fhash-based-csp-for-inline-scripts-in-astro\u002F","Hash-Based CSP for Inline Scripts in Astro",[41,342,343,471],{},[44,344,52,349,52,352,52,355,52,358,52,464],{"viewBox":345,"role":47,"ariaLabelledBy":346,"xmlns":51},"0 0 780 290",[347,348],"shs-csp-title","shs-csp-desc",[54,350,351],{"id":347},"What a strict CSP allows and blocks",[58,353,354],{"id":348},"A page's script requests pass through a CSP check. The site's own bundle from self is allowed. An inline theme script whose hash is listed is allowed. The analytics script from an allow-listed origin is allowed. An injected inline script without a matching hash is blocked. A script from an unknown origin is blocked. A data exfiltration request to an unlisted origin is blocked by connect-src.",[62,356],{"x":64,"y":64,"width":65,"height":357,"fill":67},"290",[69,359,72,360,72,363,72,369,72,372,72,375,72,379,72,384,72,387,72,391,72,394,72,398,72,402,72,406,72,409,72,413,72,430,72,434,72,440,72,444,72,447,72,451,72,455,52],{"style":71},[74,361,362],{"x":76,"y":77,"fill":78,"style":79},"The policy is an allow-list the browser enforces",[62,364],{"x":365,"y":92,"width":85,"height":193,"rx":366,"fill":367,"opacity":368,"stroke":367,"style":89},"310","12","#6a4c93","0.14",[74,370,371],{"x":76,"y":150,"fill":78,"style":94},"CSP check",[74,373,374],{"x":76,"y":85,"fill":99,"style":100},"per request",[62,376],{"x":83,"y":151,"width":104,"height":377,"rx":152,"fill":87,"stroke":88,"style":378},"36","stroke-width:1px",[74,380,383],{"x":150,"y":381,"fill":78,"style":382},"73","text-anchor:middle","\u002F_astro\u002Fapp.js ('self')",[62,385],{"x":83,"y":386,"width":104,"height":377,"rx":152,"fill":87,"stroke":88,"style":378},"98",[74,388,390],{"x":150,"y":389,"fill":78,"style":382},"121","inline theme script (hash)",[62,392],{"x":83,"y":393,"width":104,"height":377,"rx":152,"fill":87,"stroke":88,"style":378},"146",[74,395,397],{"x":150,"y":396,"fill":78,"style":382},"169","plausible.io\u002Fjs (listed)",[62,399],{"x":83,"y":400,"width":104,"height":377,"rx":152,"fill":153,"opacity":401,"stroke":155,"style":378},"194","0.1",[74,403,405],{"x":150,"y":404,"fill":78,"style":382},"217","injected inline \u003Cscript>",[62,407],{"x":83,"y":408,"width":104,"height":377,"rx":152,"fill":153,"opacity":401,"stroke":155,"style":378},"242",[74,410,412],{"x":150,"y":411,"fill":78,"style":382},"265","evil.example\u002Fx.js",[69,414,136,415,136,418,136,421,136,424,136,427,72],{"stroke":99,"fill":134,"style":89},[138,416],{"d":417},"M252 68 L306 126",[138,419],{"d":420},"M252 116 L306 136",[138,422],{"d":423},"M252 164 L306 150",[138,425],{"d":426},"M252 212 L306 162",[138,428],{"d":429},"M252 260 L306 172",[62,431],{"x":432,"y":193,"width":104,"height":84,"rx":86,"fill":194,"opacity":433,"stroke":196,"style":89},"530","0.18",[74,435,439],{"x":436,"y":437,"fill":438,"style":94},"640","96","#3f6410","allowed (3)",[74,441,443],{"x":436,"y":442,"fill":99,"style":100},"116","own code, hashed inline, listed vendor",[62,445],{"x":432,"y":446,"width":104,"height":84,"rx":86,"fill":153,"opacity":368,"stroke":155,"style":89},"170",[74,448,450],{"x":436,"y":449,"fill":159,"style":94},"196","blocked (2)",[74,452,454],{"x":436,"y":453,"fill":99,"style":100},"216","and reported to report-to",[69,456,136,457,136,461,72],{"stroke":99,"fill":134,"style":135},[138,458],{"d":459,"style":460},"M472 130 L526 104","marker-end:url(#shs-csp-arrow)",[138,462],{"d":463,"style":460},"M472 160 L526 196",[234,465,72,466,52],{},[237,467,136,469,72],{"id":468,"viewBox":240,"refX":152,"refY":241,"markerWidth":242,"markerHeight":242,"orient":243},"shs-csp-arrow",[138,470],{"d":246,"fill":99},[248,472,473,474,476],{},"Hashing inline scripts at build time is what lets a static site drop ",[267,475,321],{},", the directive that makes most real-world policies ineffective.",[252,478,480],{"id":479},"hsts-and-preload","HSTS and Preload",[14,482,483,486,487,490,491,494,495,498,499,39],{},[267,484,485],{},"Strict-Transport-Security"," only protects visitors after their first successful HTTPS visit, because they must receive the header once. The HSTS preload list, built into browsers, removes that first-visit gap by hard-coding your domain as HTTPS-only. Getting onto it requires ",[267,488,489],{},"max-age"," of at least a year, ",[267,492,493],{},"includeSubDomains"," and the ",[267,496,497],{},"preload"," directive — and it is hard to reverse, because removal propagates through browser releases over months. The staged rollout that avoids locking out a forgotten HTTP-only subdomain is in ",[30,500,502],{"href":501},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fenabling-hsts-and-preload-safely\u002F","Enabling HSTS and Preload Safely",[252,504,506],{"id":505},"subresource-integrity-for-third-party-assets","Subresource Integrity for Third-Party Assets",[14,508,509],{},"If your pages load a script or stylesheet from a CDN you do not control, SRI lets the browser verify the file's hash before executing it. A tampered file fails the check and does not run.",[260,511,515],{"className":512,"code":513,"language":514,"meta":265,"style":265},"language-html shiki shiki-themes github-light github-dark","\u003Cscript src=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fchart.js@4.4.4\u002Fdist\u002Fchart.umd.min.js\"\n        integrity=\"sha384-…\" crossorigin=\"anonymous\" defer>\u003C\u002Fscript>\n","html",[267,516,517,541],{"__ignoreMap":265},[518,519,522,526,530,534,537],"span",{"class":520,"line":521},"line",1,[518,523,525],{"class":524},"sVt8B","\u003C",[518,527,529],{"class":528},"s9eBZ","script",[518,531,533],{"class":532},"sScJk"," src",[518,535,536],{"class":524},"=",[518,538,540],{"class":539},"sZZnC","\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fchart.js@4.4.4\u002Fdist\u002Fchart.umd.min.js\"\n",[518,542,544,547,549,552,555,557,560,563,566,568],{"class":520,"line":543},2,[518,545,546],{"class":532},"        integrity",[518,548,536],{"class":524},[518,550,551],{"class":539},"\"sha384-…\"",[518,553,554],{"class":532}," crossorigin",[518,556,536],{"class":524},[518,558,559],{"class":539},"\"anonymous\"",[518,561,562],{"class":532}," defer",[518,564,565],{"class":524},">\u003C\u002F",[518,567,529],{"class":528},[518,569,570],{"class":524},">\n",[14,572,573,574,577,578,39],{},"SRI only works for versioned, immutable URLs — a ",[267,575,576],{},"latest"," URL changes content and breaks the hash. On static sites the better default is to self-host third-party code at build time, which removes the external origin entirely; SRI is for the cases where that is not possible. The details, including generating hashes in the build, are in ",[30,579,506],{"href":580},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsubresource-integrity-for-third-party-assets\u002F",[252,582,584],{"id":583},"the-pipeline-dependencies-and-deploy-credentials","The Pipeline: Dependencies and Deploy Credentials",[14,586,587],{},"Response headers cannot help if the attacker publishes the page. For static sites, two pipeline risks dominate.",[14,589,590,593,594,597,598,601,602,604,605,39],{},[21,591,592],{},"Dependencies."," A typical Astro or Next.js site installs 400–1,200 npm packages. Every one can run code during ",[267,595,596],{},"npm install"," (via lifecycle scripts) and during the build, with access to environment variables — including deploy tokens if they are present. Lockfiles with integrity hashes, ",[267,599,600],{},"npm ci"," instead of ",[267,603,596],{},", disabling lifecycle scripts where possible, automated audit in CI, and a review step for new dependencies reduce this risk substantially; see ",[30,606,608],{"href":607},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fauditing-npm-dependencies-in-ssg-pipelines\u002F","Auditing npm Dependencies in SSG Pipelines",[14,610,611,614,615,39],{},[21,612,613],{},"Deploy credentials."," A long-lived API token stored as a CI secret is a standing key to every page on the site. Short-lived credentials issued via OpenID Connect, scoped to one project and one branch, cannot be reused if leaked and expire within minutes. The setup for GitHub Actions to Cloudflare, AWS and Netlify is in ",[30,616,618],{"href":617},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsecuring-deploy-credentials-with-github-oidc\u002F","Securing Deploy Credentials with GitHub OIDC",[41,620,621,710],{},[44,622,52,627,52,630,52,633,52,636,52,703],{"viewBox":623,"role":47,"ariaLabelledBy":624,"xmlns":51},"0 0 780 280",[625,626],"shs-pipe-title","shs-pipe-desc",[54,628,629],{"id":625},"Where build-time code runs and what it can reach",[58,631,632],{"id":626},"The CI job runs npm ci, which executes install scripts from hundreds of packages, then the build, which executes plugin and bundler code, then the deploy step. If a long-lived deploy token is in the job's environment from the start, every earlier step can read it. With OIDC, the credential is minted only in the deploy step and expires in minutes.",[62,634],{"x":64,"y":64,"width":65,"height":635,"fill":67},"280",[69,637,72,638,72,641,72,648,72,652,72,656,72,658,72,661,72,664,72,667,72,671,72,674,72,683,72,688,72,692,72,695,72,699,52],{"style":71},[74,639,640],{"x":76,"y":77,"fill":78,"style":79},"Keep the publish credential away from third-party code",[62,642],{"x":83,"y":84,"width":643,"height":644,"rx":86,"fill":645,"opacity":646,"stroke":647,"style":89},"200","56","#ffca3a","0.22","#a97b00",[74,649,600],{"x":650,"y":651,"fill":78,"style":94},"130","84",[74,653,655],{"x":650,"y":654,"fill":99,"style":100},"102","~900 packages' scripts",[62,657],{"x":357,"y":84,"width":643,"height":644,"rx":86,"fill":645,"opacity":646,"stroke":647,"style":89},[74,659,660],{"x":76,"y":651,"fill":78,"style":94},"build",[74,662,663],{"x":76,"y":654,"fill":99,"style":100},"plugins, bundler, loaders",[62,665],{"x":666,"y":84,"width":643,"height":644,"rx":86,"fill":367,"opacity":368,"stroke":367,"style":89},"550",[74,668,670],{"x":669,"y":651,"fill":78,"style":94},"650","deploy",[74,672,673],{"x":669,"y":654,"fill":99,"style":100},"first-party step only",[69,675,136,676,136,680,72],{"stroke":99,"fill":134,"style":135},[138,677],{"d":678,"style":679},"M232 88 L286 88","marker-end:url(#shs-p-arrow)",[138,681],{"d":682,"style":679},"M492 88 L546 88",[62,684],{"x":83,"y":685,"width":686,"height":687,"rx":152,"fill":153,"opacity":368,"stroke":155,"style":89},"144","720","34",[74,689,691],{"x":76,"y":690,"fill":159,"style":94},"166","long-lived token in job env: readable by every step, valid for months",[62,693],{"x":666,"y":449,"width":643,"height":687,"rx":152,"fill":194,"opacity":694,"stroke":196,"style":89},"0.2",[74,696,698],{"x":669,"y":697,"fill":438,"style":94},"218","OIDC: minted here, ~15 min",[74,700,702],{"x":76,"y":701,"fill":99,"style":100},"262","A dependency cannot steal a credential that does not exist yet when it runs",[234,704,72,705,52],{},[237,706,136,708,72],{"id":707,"viewBox":240,"refX":152,"refY":241,"markerWidth":242,"markerHeight":242,"orient":243},"shs-p-arrow",[138,709],{"d":246,"fill":99},[248,711,712],{},"Moving credential issuance into the deploy step shrinks the window from \"whole job, for months\" to \"one step, for minutes\".",[252,714,716],{"id":715},"headers-beyond-the-baseline","Headers Beyond the Baseline",[14,718,719],{},"A few more headers are worth knowing about, even if not every site needs them.",[14,721,722,725,726,729,730,733],{},[21,723,724],{},"Cross-Origin-Resource-Policy"," (",[267,727,728],{},"same-origin"," or ",[267,731,732],{},"same-site",") stops other sites embedding your images, scripts and fonts as subresources. It protects against a class of side-channel attacks and, as a side benefit, stops hotlinking of large assets. Apply it to asset paths, not to pages meant to be linked.",[14,735,736,739,740,743],{},[21,737,738],{},"Cross-Origin-Embedder-Policy"," is only needed if the site uses features such as ",[267,741,742],{},"SharedArrayBuffer",". Setting it without that need tends to break third-party embeds, so leave it unset on most content sites.",[14,745,746,749,750,753,754,39],{},[21,747,748],{},"Cache-Control on sensitive responses."," Static sites rarely serve personal data, but preview deployments sometimes contain unreleased content. ",[267,751,752],{},"X-Robots-Tag: noindex"," on preview hostnames, plus access control, keeps drafts out of search engines and shared caches; see ",[30,755,757],{"href":756},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpassword-protecting-preview-deployments\u002F","Password-Protecting Preview Deployments",[14,759,760,763,764,767,768,771],{},[21,761,762],{},"Remove what you do not need."," Many hosts and frameworks add headers that advertise software versions (",[267,765,766],{},"X-Powered-By",", ",[267,769,770],{},"Server"," with a version string). They give attackers free reconnaissance and nothing to readers. Strip them where the host allows.",[14,773,774,775,778,779,781],{},"Finally, prefer one source of truth. Headers scattered across a ",[267,776,777],{},"_headers"," file, a meta tag and a middleware function drift apart; generate the ",[267,780,777],{}," file from a single configuration object at build time so the policy is reviewable in one diff.",[252,783,785],{"id":784},"setting-headers-on-each-host","Setting Headers on Each Host",[14,787,788],{},"The same header set translates directly between hosts:",[790,791,792,808],"table",{},[793,794,795],"thead",{},[796,797,798,802,805],"tr",{},[799,800,801],"th",{},"Host",[799,803,804],{},"Where headers live",[799,806,807],{},"Notes",[809,810,811,829,852,872,883],"tbody",{},[796,812,813,817,822],{},[814,815,816],"td",{},"Cloudflare Pages \u002F Workers static assets",[814,818,819,821],{},[267,820,777],{}," file in the output directory",[814,823,824,825,828],{},"path patterns with ",[267,826,827],{},"*","; later rules add to earlier ones",[796,830,831,834,846],{},[814,832,833],{},"Netlify",[814,835,836,838,839,842,843],{},[267,837,777],{}," file or ",[267,840,841],{},"[[headers]]"," in ",[267,844,845],{},"netlify.toml",[814,847,848,849,851],{},"same ",[267,850,777],{}," syntax as Cloudflare",[796,853,854,857,866],{},[814,855,856],{},"Vercel",[814,858,859,862,863],{},[267,860,861],{},"headers"," array in ",[267,864,865],{},"vercel.json",[814,867,868,871],{},[267,869,870],{},"source"," patterns; applies to static and functions",[796,873,874,877,880],{},[814,875,876],{},"S3 + CloudFront",[814,878,879],{},"Response headers policy",[814,881,882],{},"managed \"SecurityHeadersPolicy\" as a starting point",[796,884,885,888,898],{},[814,886,887],{},"nginx \u002F Caddy",[814,889,890,893,894,897],{},[267,891,892],{},"add_header"," \u002F ",[267,895,896],{},"header"," directives",[814,899,900,901,903],{},"nginx drops parent ",[267,902,892],{},"s in nested blocks unless repeated",[14,905,906,907,909,910,913,914,917,918,921,922,925,926,39],{},"The nginx behaviour catches many self-hosters: an ",[267,908,892],{}," in a ",[267,911,912],{},"location"," block replaces, rather than extends, the ones set at the ",[267,915,916],{},"server"," level, so a location that adds a ",[267,919,920],{},"Cache-Control"," header silently loses all the security headers. Repeat them or use an ",[267,923,924],{},"include"," file, as shown in ",[30,927,929],{"href":928},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx\u002F","Serving a Static Site with Nginx",[252,931,933],{"id":932},"verifying-headers-in-ci","Verifying Headers in CI",[14,935,936],{},"Headers are configuration, so test them like configuration. A smoke test against each preview deploy can assert that every template returns the full set:",[260,938,942],{"className":939,"code":940,"language":941,"meta":265,"style":265},"language-bash shiki shiki-themes github-light github-dark","for path in \u002F \u002Fguides\u002F \u002Fguides\u002Fdeploying-hugo\u002F \u002F404.html; do\n  h=$(curl -sI \"$PREVIEW$path\")\n  for want in strict-transport-security content-security-policy x-content-type-options referrer-policy; do\n    echo \"$h\" | grep -qi \"^$want:\" || { echo \"FAIL $path missing $want\"; exit 1; }\n  done\ndone\n","bash",[267,943,944,974,1003,1030,1094,1100],{"__ignoreMap":265},[518,945,946,950,953,956,959,962,965,968,971],{"class":520,"line":521},[518,947,949],{"class":948},"szBVR","for",[518,951,952],{"class":524}," path ",[518,954,955],{"class":948},"in",[518,957,958],{"class":539}," \u002F",[518,960,961],{"class":539}," \u002Fguides\u002F",[518,963,964],{"class":539}," \u002Fguides\u002Fdeploying-hugo\u002F",[518,966,967],{"class":539}," \u002F404.html",[518,969,970],{"class":524},"; ",[518,972,973],{"class":948},"do\n",[518,975,976,979,981,984,987,991,994,997,1000],{"class":520,"line":543},[518,977,978],{"class":524},"  h",[518,980,536],{"class":948},[518,982,983],{"class":524},"$(",[518,985,986],{"class":532},"curl",[518,988,990],{"class":989},"sj4cs"," -sI",[518,992,993],{"class":539}," \"",[518,995,996],{"class":524},"$PREVIEW$path",[518,998,999],{"class":539},"\"",[518,1001,1002],{"class":524},")\n",[518,1004,1006,1009,1012,1014,1017,1020,1023,1026,1028],{"class":520,"line":1005},3,[518,1007,1008],{"class":948},"  for",[518,1010,1011],{"class":524}," want ",[518,1013,955],{"class":948},[518,1015,1016],{"class":539}," strict-transport-security",[518,1018,1019],{"class":539}," content-security-policy",[518,1021,1022],{"class":539}," x-content-type-options",[518,1024,1025],{"class":539}," referrer-policy",[518,1027,970],{"class":524},[518,1029,973],{"class":948},[518,1031,1033,1036,1038,1041,1043,1046,1049,1052,1055,1058,1061,1064,1067,1070,1073,1076,1079,1081,1083,1085,1088,1091],{"class":520,"line":1032},4,[518,1034,1035],{"class":989},"    echo",[518,1037,993],{"class":539},[518,1039,1040],{"class":524},"$h",[518,1042,999],{"class":539},[518,1044,1045],{"class":948}," |",[518,1047,1048],{"class":532}," grep",[518,1050,1051],{"class":989}," -qi",[518,1053,1054],{"class":539}," \"^",[518,1056,1057],{"class":524},"$want",[518,1059,1060],{"class":539},":\"",[518,1062,1063],{"class":948}," ||",[518,1065,1066],{"class":524}," { ",[518,1068,1069],{"class":989},"echo",[518,1071,1072],{"class":539}," \"FAIL ",[518,1074,1075],{"class":524},"$path",[518,1077,1078],{"class":539}," missing ",[518,1080,1057],{"class":524},[518,1082,999],{"class":539},[518,1084,970],{"class":524},[518,1086,1087],{"class":989},"exit",[518,1089,1090],{"class":989}," 1",[518,1092,1093],{"class":524},"; }\n",[518,1095,1097],{"class":520,"line":1096},5,[518,1098,1099],{"class":948},"  done\n",[518,1101,1103],{"class":520,"line":1102},6,[518,1104,1105],{"class":948},"done\n",[14,1107,1108,1109,1113],{},"This belongs in the same job as the smoke tests from ",[30,1110,1112],{"href":1111},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url\u002F","Running Smoke Tests Against a Preview URL",". The 404 page is included deliberately: error responses are frequently served by a different code path that skips header rules.",[252,1115,1117],{"id":1116},"collecting-and-reading-csp-reports","Collecting and Reading CSP Reports",[14,1119,1120,1121,1124,1125,1128,1129,1132,1133,39],{},"A policy without reporting is a policy you cannot improve. Add a ",[267,1122,1123],{},"report-to"," directive (with a matching ",[267,1126,1127],{},"Reporting-Endpoints"," header) and, for older browsers, ",[267,1130,1131],{},"report-uri",", pointing at an endpoint you control. A small edge function that validates the JSON, drops obviously malformed reports, and writes the rest to a log store is enough; the pattern is the same as the form handler in ",[30,1134,1136],{"href":1135},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fhandling-form-submissions-on-a-static-site\u002F","Handling Form Submissions on a Static Site",[14,1138,1139,1140,1143,1144,729,1147,1150,1151,1154,1155,1158],{},"Expect noise. On the documentation site measured below, 94% of reports in the first week came from browser extensions injecting scripts and styles into every page — password managers, ad blockers, translation tools — identifiable by ",[267,1141,1142],{},"source-file"," values such as ",[267,1145,1146],{},"chrome-extension",[267,1148,1149],{},"moz-extension",". Filter those out before anyone reads the data. What remains is signal: a forgotten inline script in one template, a vendor that moved its assets to a new domain, an embed that loads a font from a CDN you never listed. Group reports by ",[267,1152,1153],{},"effective-directive"," and ",[267,1156,1157],{},"blocked-uri",", and review the top ten weekly until the list is empty except for extensions.",[14,1160,1161],{},"Keep reporting on after switching to enforcing mode. A spike in violations after a deploy is one of the fastest indicators that something changed that should not have — either a legitimate change nobody updated the policy for, or an injection attempt the policy just blocked.",[252,1163,1165],{"id":1164},"embeds-forms-and-other-exceptions","Embeds, Forms and Other Exceptions",[14,1167,1168],{},"Most static sites have a handful of features that do not fit a strict policy by default. Handle each one deliberately rather than loosening the whole policy.",[14,1170,1171,1174,1175,1178,1179,767,1182,1185,1186,1190],{},[21,1172,1173],{},"Video and social embeds"," need ",[267,1176,1177],{},"frame-src"," entries for their domains (",[267,1180,1181],{},"https:\u002F\u002Fwww.youtube-nocookie.com",[267,1183,1184],{},"https:\u002F\u002Fplayer.vimeo.com","). Loading them behind a click-to-load facade, as in ",[30,1187,1189],{"href":1188},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Flazy-loading-youtube-embeds-on-static-sites\u002F","Lazy-Loading YouTube Embeds on Static Sites",", is also a security win: the third-party frame never loads for readers who do not ask for it.",[14,1192,1193,1196,1197,1200,1201,1204],{},[21,1194,1195],{},"Forms"," posting to a third-party service need that origin in ",[267,1198,1199],{},"form-action",". Posting to your own edge function instead keeps ",[267,1202,1203],{},"form-action 'self'"," intact and keeps submissions under your control.",[14,1206,1207,1210,1211,842,1214,1217,1218,1221],{},[21,1208,1209],{},"Search tools"," that use WebAssembly, such as Pagefind, need ",[267,1212,1213],{},"'wasm-unsafe-eval'",[267,1215,1216],{},"script-src",". That keyword permits compiling WebAssembly only, not JavaScript ",[267,1219,1220],{},"eval",", so it is a narrow and acceptable exception.",[14,1223,1224,1227,1228,729,1230,1233],{},[21,1225,1226],{},"Analytics and consent managers"," are the hardest. Many inject inline scripts at runtime, which no build-time hash can cover. Prefer vendors that load as a single external script, self-host their code where the licence allows, and treat any vendor that requires ",[267,1229,321],{},[267,1231,1232],{},"unsafe-eval"," as a cost to be justified in writing.",[14,1235,1236,1237,1239,1240,1243,1244,1246],{},"Scope exceptions by path where the host allows it. On Cloudflare Pages or Netlify, a ",[267,1238,777],{}," rule for ",[267,1241,1242],{},"\u002Fvideos\u002F*"," can extend ",[267,1245,1177],{}," for the few pages that embed video, while every other page keeps the stricter policy.",[252,1248,1250],{"id":1249},"when-something-goes-wrong","When Something Goes Wrong",[14,1252,1253,1254,1258],{},"Static sites make incident response unusually simple, and it is worth writing the steps down before they are needed. If a compromised dependency or a leaked token has published malicious content, the order is: roll back to the last known-good deploy (on atomic hosts this takes seconds, as described in ",[30,1255,1257],{"href":1256},"\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","); revoke every credential the pipeline could reach; purge the CDN cache so no edge keeps serving the bad version; and only then investigate. Because every deploy is an immutable artifact tied to a commit, the investigation can diff the bad deploy's output against the previous one file by file to see exactly what was injected. Keep at least thirty days of deploy history for this reason, and make sure the rollback does not depend on the same credential that may have leaked.",[252,1260,1262],{"id":1261},"measured-impact","Measured Impact",[14,1264,1265],{},"A 700-page Astro documentation site on Cloudflare Pages applied the full set over three weeks: baseline headers, a hash-based CSP rolled out via report-only, HSTS with preload, self-hosted third-party scripts, OIDC deploys and dependency auditing.",[790,1267,1268,1281],{},[793,1269,1270],{},[796,1271,1272,1275,1278],{},[799,1273,1274],{},"Measure",[799,1276,1277],{},"Before",[799,1279,1280],{},"After",[809,1282,1283,1294,1305,1315,1326,1336,1345,1356],{},[796,1284,1285,1288,1291],{},[814,1286,1287],{},"Mozilla Observatory score",[814,1289,1290],{},"0\u002F100 (F)",[814,1292,1293],{},"115\u002F100 (A+)",[796,1295,1296,1299,1302],{},[814,1297,1298],{},"securityheaders.com grade",[814,1300,1301],{},"F",[814,1303,1304],{},"A+",[796,1306,1307,1310,1313],{},[814,1308,1309],{},"Inline scripts allowed without hash",[814,1311,1312],{},"all",[814,1314,64],{},[796,1316,1317,1320,1323],{},[814,1318,1319],{},"Third-party script origins",[814,1321,1322],{},"4",[814,1324,1325],{},"1",[796,1327,1328,1331,1334],{},[814,1329,1330],{},"Long-lived deploy secrets in CI",[814,1332,1333],{},"2",[814,1335,64],{},[796,1337,1338,1341,1343],{},[814,1339,1340],{},"Known-vulnerable dependencies (high\u002Fcritical)",[814,1342,242],{},[814,1344,64],{},[796,1346,1347,1350,1353],{},[814,1348,1349],{},"CSP violation reports per day (enforcing, week 4)",[814,1351,1352],{},"—",[814,1354,1355],{},"3–8, all browser extensions",[796,1357,1358,1361,1363],{},[814,1359,1360],{},"Change in LCP p75",[814,1362,1352],{},[814,1364,1365],{},"none measurable",[252,1367,1369],{"id":1368},"common-pitfalls","Common Pitfalls",[1371,1372,1373,1384,1392,1398,1408,1414],"ul",{},[1374,1375,1376,1379,1380,1383],"li",{},[21,1377,1378],{},"Enforcing CSP on day one."," Always ship ",[267,1381,1382],{},"Content-Security-Policy-Report-Only"," first and read the reports.",[1374,1385,1386,1391],{},[21,1387,1388,1390],{},[267,1389,321],{}," for convenience."," It defeats most of the protection. Hash inline scripts at build time instead.",[1374,1393,1394,1397],{},[21,1395,1396],{},"HSTS preload before auditing subdomains."," An internal tool on plain HTTP becomes unreachable for months.",[1374,1399,1400,1403,1404,1407],{},[21,1401,1402],{},"Headers only on HTML."," Error pages and some assets are served via different rules; check ",[267,1405,1406],{},"404.html"," and redirects explicitly.",[1374,1409,1410,1413],{},[21,1411,1412],{},"Secrets available to every CI step."," Scope deploy credentials to the deploy step, and prefer OIDC to stored tokens.",[1374,1415,1416,1419],{},[21,1417,1418],{},"Treating grades as the goal."," Observatory and securityheaders.com are checklists, not threat models. The CSP and the pipeline controls are where the real risk reduction is.",[252,1421,1423],{"id":1422},"key-takeaways","Key Takeaways",[1371,1425,1426,1429,1432,1438,1441],{},[1374,1427,1428],{},"Static sites still run third-party JavaScript and still have a publishing pipeline; both need hardening.",[1374,1430,1431],{},"A baseline of HSTS, nosniff, Referrer-Policy, frame protection and Permissions-Policy costs nothing and belongs on every site.",[1374,1433,1434,1435,1437],{},"A hash-based CSP without ",[267,1436,321],{}," is achievable on static sites because inline scripts are known at build time.",[1374,1439,1440],{},"Self-host third-party code where possible; use SRI where it must stay external.",[1374,1442,1443],{},"Dependency auditing and short-lived OIDC deploy credentials protect against the attacks headers cannot stop.",[252,1445,1447],{"id":1446},"faq","FAQ",[1449,1450,1452],"h3",{"id":1451},"does-a-static-site-need-security-headers-at-all","Does a static site need security headers at all?",[14,1454,1455],{},"Yes. There is no server-side code to exploit, but browsers still execute whatever scripts the page loads, including injected or compromised third-party scripts. Headers such as Content-Security-Policy and HSTS limit what an attacker can do if content or a dependency is tampered with.",[1449,1457,1459],{"id":1458},"which-headers-matter-most","Which headers matter most?",[14,1461,1462],{},"Strict-Transport-Security, a Content-Security-Policy, X-Content-Type-Options set to nosniff, a Referrer-Policy, and frame-ancestors or X-Frame-Options against clickjacking. A Permissions-Policy that disables unused browser features is a useful addition.",[1449,1464,1466],{"id":1465},"where-are-headers-configured-on-a-static-host","Where are headers configured on a static host?",[14,1468,1469],{},"In the host's header configuration rather than in HTML: a _headers file on Cloudflare Pages and Netlify, the headers key in vercel.json, response header policies on CloudFront, or add_header directives in nginx. A few, such as CSP, can also be set with a meta tag, with limitations.",[1449,1471,1473],{"id":1472},"what-is-the-biggest-real-risk-for-a-static-site","What is the biggest real risk for a static site?",[14,1475,1476],{},"The build pipeline and its dependencies. A compromised npm package or leaked deploy token can publish malicious content to every page. Pinning dependencies, auditing them and using short-lived deploy credentials matter as much as response headers.",[1449,1478,1480],{"id":1479},"will-a-strict-csp-break-my-site","Will a strict CSP break my site?",[14,1482,1483],{},"It can if it is deployed in enforcing mode without testing. Roll it out with Content-Security-Policy-Report-Only first, collect violation reports for a week or two, fix or allow what is legitimate, then switch to enforcing.",[252,1485,1487],{"id":1486},"related","Related",[1371,1489,1490,1499,1504,1509,1514,1519,1524,1529],{},[1374,1491,1492,1495,1496,1498],{},[21,1493,1494],{},"Up:"," ",[30,1497,33],{"href":32}," — where security fits in the deploy pipeline.",[1374,1500,1501,1503],{},[30,1502,335],{"href":334}," — from report-only to enforcing.",[1374,1505,1506,1508],{},[30,1507,340],{"href":339}," — computing hashes at build time.",[1374,1510,1511,1513],{},[30,1512,502],{"href":501}," — the staged rollout.",[1374,1515,1516,1518],{},[30,1517,506],{"href":580}," — verifying code you do not host.",[1374,1520,1521,1523],{},[30,1522,608],{"href":607}," — supply-chain controls.",[1374,1525,1526,1528],{},[30,1527,618],{"href":617}," — no more long-lived tokens.",[1374,1530,1531,1535,1536,1538],{},[30,1532,1534],{"href":1533},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fsetting-cache-control-headers-on-cloudflare-pages\u002F","Setting Cache-Control Headers on Cloudflare Pages"," — the same ",[267,1537,777],{}," file for caching.",[1540,1541,1542],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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 .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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":265,"searchDepth":543,"depth":543,"links":1544},[1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1566],{"id":254,"depth":543,"text":255},{"id":307,"depth":543,"text":308},{"id":479,"depth":543,"text":480},{"id":505,"depth":543,"text":506},{"id":583,"depth":543,"text":584},{"id":715,"depth":543,"text":716},{"id":784,"depth":543,"text":785},{"id":932,"depth":543,"text":933},{"id":1116,"depth":543,"text":1117},{"id":1164,"depth":543,"text":1165},{"id":1249,"depth":543,"text":1250},{"id":1261,"depth":543,"text":1262},{"id":1368,"depth":543,"text":1369},{"id":1422,"depth":543,"text":1423},{"id":1446,"depth":543,"text":1447,"children":1560},[1561,1562,1563,1564,1565],{"id":1451,"depth":1005,"text":1452},{"id":1458,"depth":1005,"text":1459},{"id":1465,"depth":1005,"text":1466},{"id":1472,"depth":1005,"text":1473},{"id":1479,"depth":1005,"text":1480},{"id":1486,"depth":543,"text":1487},[1568,1571,1572],{"name":1569,"item":1570},"Home","\u002F",{"name":33,"item":32},{"name":5,"item":1573},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002F","2026-09-18","Harden a static site end to end: CSP, HSTS, SRI, framing and referrer policies at the edge, plus a supply chain and deploy pipeline built to resist abuse.","md",[1578,1579,1580,1583,1584],{"q":1452,"a":1455},{"q":1459,"a":1462},{"q":1466,"a":1581},{"In the host's header configuration rather than in HTML":1582},"a _headers file on Cloudflare Pages and Netlify, the headers key in vercel.json, response header policies on CloudFront, or add_header directives in nginx. A few, such as CSP, can also be set with a meta tag, with limitations.",{"q":1473,"a":1476},{"q":1480,"a":1483},{},true,"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites",{"title":5,"description":1575},"security-headers-for-static-sites","production-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Findex","guide","yVL6_mO72WzEen1kZOzZGIbd6HXxxQOXcjI-hQ54vpw",[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,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1920,1923,1926,1929,1932,1935,1938,1941,1943,1946,1949,1952,1955,1958,1961,1964,1967,1970,1973,1976,1979,1982,1985,1988,1991,1994,1997,2000,2003,2006,2009,2012,2015,2018,2021,2024,2027,2030,2033,2036,2039,2042,2045,2048,2051,2054,2056,2059,2062,2065,2068,2071,2074,2076,2078,2080,2082,2084,2085,2087,2089,2091,2094,2097,2100,2103,2106,2108,2111,2114,2116,2119,2122,2125],{"path":1595,"title":1596},"\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":1598,"title":1599},"\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":1601,"title":1602},"\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":1604,"title":1605},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1607,"title":1608},"\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":1610,"title":1611},"\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":1613,"title":1614},"\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":1616,"title":1617},"\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":1619,"title":1620},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress","Docs Frameworks: Docusaurus, Starlight and VitePress",{"path":1622,"title":1623},"\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":1625,"title":1626},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fmigrating-from-mkdocs-to-starlight","Migrating from MkDocs to Starlight",{"path":1628,"title":1629},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fversioned-documentation-with-docusaurus","Versioned Documentation with Docusaurus",{"path":1631,"title":1632},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fvitepress-for-library-documentation","VitePress for Library Documentation",{"path":1634,"title":1635},"\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":1637,"title":1638},"\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":1640,"title":1641},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1643,"title":1644},"\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":1646,"title":1647},"\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":1649,"title":1650},"\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":1652,"title":1653},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":1655,"title":1656},"\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":1658,"title":1659},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":1661,"title":1662},"\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":1664,"title":1665},"\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":1667,"title":1668},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fspeeding-up-slow-jekyll-builds","Speeding Up Slow Jekyll Builds",{"path":1670,"title":1671},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fupgrading-jekyll-and-ruby-versions-safely","Upgrading Jekyll and Ruby Versions Safely",{"path":1673,"title":1674},"\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":1676,"title":1677},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":1679,"title":1680},"\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":1682,"title":1683},"\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":1685,"title":1686},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-gatsby-to-astro","Migrating from Gatsby to Astro",{"path":1688,"title":1689},"\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":1691,"title":1692},"\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":1694,"title":1695},"\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":1697,"title":1698},"\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":1700,"title":1701},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1703,"title":1704},"\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":1706,"title":1707},"\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":1709,"title":1710},"\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":1712,"title":1713},"\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":1715,"title":1716},"\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":1718,"title":1719},"\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":1721,"title":1722},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites","Search for Static Sites",{"path":1724,"title":1725},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Findexing-hugo-sites-with-pagefind","Indexing Hugo Sites with Pagefind",{"path":1727,"title":1728},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fmultilingual-search-on-static-sites","Multilingual Search on Static Sites",{"path":1730,"title":1731},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fpagefind-vs-algolia-docsearch","Pagefind vs Algolia DocSearch",{"path":1733,"title":1734},"\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":1736,"title":1737},"\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":1739,"title":1740},"\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":1742,"title":1743},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fevaluating-ssg-accessibility-defaults","Evaluating SSG Accessibility Defaults",{"path":1745,"title":1746},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":1748,"title":1749},"\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":1751,"title":1752},"\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":1754,"title":1755},"\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":1757,"title":1758},"\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":1760,"title":1761},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":1763,"title":1764},"\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":1766,"title":1767},"\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":1769,"title":1770},"\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":1772,"title":1773},"\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":1775,"title":1776},"\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":1778,"title":1779},"\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":1781,"title":1782},"\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":1784,"title":1785},"\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":1787,"title":1788},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":1790,"title":1791},"\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":1793,"title":1794},"\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":1796,"title":1797},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Ffont-display-optional-vs-swap","font-display: optional vs swap",{"path":1799,"title":1800},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":1802,"title":1803},"\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":1805,"title":1806},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fpreloading-fonts-without-double-downloads","Preloading Fonts Without Double Downloads",{"path":1808,"title":1809},"\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":1811,"title":1812},"\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":1814,"title":1815},"\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":1817,"title":1818},"\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":1820,"title":1821},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":1823,"title":1824},"\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":1826,"title":1827},"\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":1829,"title":1830},"\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":1832,"title":1833},"\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":1835,"title":1836},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":1838,"title":1839},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":1841,"title":1842},"\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":1844,"title":1845},"\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":1847,"title":1848},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":1850,"title":1851},"\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":1853,"title":1854},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Freplacing-react-islands-with-web-components","Replacing React Islands with Web Components",{"path":1856,"title":1857},"\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":1859,"title":1860},"\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":1862,"title":1863},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":1865,"title":1866},"\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":1868,"title":1869},"\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":1871,"title":1872},"\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":1874,"title":1875},"\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":1877,"title":1878},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci","Performance Budgets and Lighthouse CI",{"path":1880,"title":1881},"\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":1883,"title":1884},"\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":1886,"title":1887},"\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":1889,"title":1890},"\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":1892,"title":1893},"\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":1895,"title":1896},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fauditing-unused-preloads","Auditing Unused Preloads",{"path":1898,"title":1899},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed","Resource Hints and Navigation Speed",{"path":1901,"title":1902},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Finstant-navigation-with-speculation-rules","Instant Navigation with Speculation Rules",{"path":1904,"title":1905},"\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":1907,"title":1908},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fprefetching-links-in-astro","Prefetching Links in Astro",{"path":1910,"title":1911},"\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":1913,"title":1914},"\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":1916,"title":1917},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":1919,"title":1189},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Flazy-loading-youtube-embeds-on-static-sites",{"path":1921,"title":1922},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites\u002Floading-google-tag-manager-without-hurting-inp","Loading Google Tag Manager Without Hurting INP",{"path":1924,"title":1925},"\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":1927,"title":1928},"\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":1930,"title":1931},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":1933,"title":1934},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fconfiguring-redirects-on-cloudflare-pages","Configuring Redirects on Cloudflare Pages",{"path":1936,"title":1937},"\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":1939,"title":1940},"\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":1942,"title":38},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup",{"path":1944,"title":1945},"\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":1947,"title":1948},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fchecking-links-in-pull-requests","Checking Links in Pull Requests",{"path":1950,"title":1951},"\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":1953,"title":1954},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Feditorial-checks-with-vale-in-ci","Editorial Checks with Vale in CI",{"path":1956,"title":1957},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":1959,"title":1960},"\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":1962,"title":1963},"\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":1965,"title":1966},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fbuilding-astro-sites-with-github-actions","Building Astro Sites with GitHub Actions",{"path":1968,"title":1969},"\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":1971,"title":1972},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fdeploying-to-github-pages-with-actions","Deploying to GitHub Pages with Actions",{"path":1974,"title":1975},"\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":1977,"title":1978},"\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":1980,"title":1981},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":1983,"title":1984},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fmatrix-builds-for-multi-site-monorepos","Matrix Builds for Multi-Site Monorepos",{"path":1986,"title":1987},"\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":1989,"title":1990},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":1992,"title":1993},"\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":1995,"title":1996},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":1998,"title":1999},"\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":2001,"title":2002},"\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":2004,"title":2005},"\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":2007,"title":2008},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":2010,"title":2011},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Falerting-on-cache-hit-ratio-drops","Alerting on Cache Hit Ratio Drops",{"path":2013,"title":2014},"\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":2016,"title":2017},"\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":2019,"title":2020},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production","Monitoring Static Sites in Production",{"path":2022,"title":2023},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Flogging-404s-at-the-edge","Logging 404s at the Edge",{"path":2025,"title":2026},"\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":2028,"title":2029},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fconfiguring-vercel-for-hugo-and-eleventy","Configuring Vercel for Hugo and Eleventy",{"path":2031,"title":2032},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":2034,"title":2035},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":2037,"title":2038},"\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":2040,"title":2041},"\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":2043,"title":2044},"\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":2046,"title":2047},"\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":2049,"title":2050},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":2052,"title":2053},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":2055,"title":757},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpassword-protecting-preview-deployments",{"path":2057,"title":2058},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpreviewing-headless-cms-drafts","Previewing Headless CMS Drafts",{"path":2060,"title":2061},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fvisual-regression-testing-on-preview-deploys","Visual Regression Testing on Preview Deploys",{"path":2063,"title":2064},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":2066,"title":2067},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fcanary-releases-for-static-sites","Canary Releases for Static Sites",{"path":2069,"title":2070},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Ffeature-flags-on-static-sites","Feature Flags on Static Sites",{"path":2072,"title":2073},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":2075,"title":1257},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frolling-back-a-bad-static-deploy-in-under-a-minute",{"path":2077,"title":1112},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url",{"path":2079,"title":608},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fauditing-npm-dependencies-in-ssg-pipelines",{"path":2081,"title":502},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fenabling-hsts-and-preload-safely",{"path":2083,"title":340},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fhash-based-csp-for-inline-scripts-in-astro",{"path":1587,"title":5},{"path":2086,"title":618},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsecuring-deploy-credentials-with-github-oidc",{"path":2088,"title":506},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsubresource-integrity-for-third-party-assets",{"path":2090,"title":335},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fwriting-a-content-security-policy-for-a-static-site",{"path":2092,"title":2093},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fclean-urls-and-trailing-slashes-on-s3","Clean URLs and Trailing Slashes on S3",{"path":2095,"title":2096},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fcloudfront-functions-for-redirects","CloudFront Functions for Redirects",{"path":2098,"title":2099},"\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":2101,"title":2102},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites","Self-Hosting Static Sites on S3, Nginx and Caddy",{"path":2104,"title":2105},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-caddy","Serving a Static Site with Caddy",{"path":2107,"title":929},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx",{"path":2109,"title":2110},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fzero-downtime-deploys-with-symlink-swaps","Zero-Downtime Deploys with Symlink Swaps",{"path":2112,"title":2113},"\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":2115,"title":1136},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fhandling-form-submissions-on-a-static-site",{"path":2117,"title":2118},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites","Serverless Functions for Static Sites",{"path":2120,"title":2121},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fnetlify-functions-vs-cloudflare-workers","Netlify Functions vs Cloudflare Workers",{"path":2123,"title":2124},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fprotecting-a-static-site-behind-authentication","Protecting a Static Site Behind Authentication",{"path":2126,"title":2127},"\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",1789722846729]