[{"data":1,"prerenderedAt":2005},["ShallowReactive",2],{"page:\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx":3,"all-docs-nav":1465},{"id":4,"title":5,"body":6,"breadcrumb":1441,"dateModified":1451,"datePublished":1451,"description":1452,"extension":1453,"faq":1454,"meta":1459,"navigation":98,"path":1460,"seo":1461,"slug":12,"stem":1462,"type":1463,"__hash__":1464},"content\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx\u002Findex.md","Serving a Static Site with Nginx",{"type":7,"value":8,"toc":1422},"minimark",[9,13,36,41,66,70,368,372,398,433,443,589,593,622,637,732,745,749,760,874,892,896,907,914,925,929,940,1030,1117,1121,1128,1237,1245,1249,1332,1336,1342,1346,1351,1354,1358,1361,1365,1368,1372,1375,1379,1418],[10,11,5],"h1",{"id":12},"serving-a-static-site-with-nginx",[14,15,16,17,21,22,25,26,29,30,35],"p",{},"Nginx has served static files for two decades, and for a static site it needs only a fraction of its configuration language. That small fraction still contains a few traps: ",[18,19,20],"code",{},"add_header"," inheritance that silently drops security headers, ",[18,23,24],{},"try_files"," orders that redirect when they should not, ",[18,27,28],{},"index"," behaviour that produces trailing-slash loops, and compression that is either missing or burns CPU on every request. This guide builds a complete, production server block for a 1,500-page Hugo site and explains each directive. It is part of ",[31,32,34],"a",{"href":33},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002F","Self-Hosting Static Sites on S3, Nginx and Caddy",".",[37,38,40],"h2",{"id":39},"prerequisites","Prerequisites",[42,43,44,52,63],"ul",{},[45,46,47,48,51],"li",{},"A Linux VM with nginx 1.24 or newer (the ",[18,49,50],{},"brotli"," module is optional but recommended).",[45,53,54,55,58,59,35],{},"A static build deployed to ",[18,56,57],{},"\u002Fsrv\u002Fsite\u002Fcurrent"," — ideally via release directories and a symlink, as in ",[31,60,62],{"href":61},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fzero-downtime-deploys-with-symlink-swaps\u002F","Zero-Downtime Deploys with Symlink Swaps",[45,64,65],{},"A TLS certificate, from certbot or your organisation's CA.",[37,67,69],{"id":68},"the-complete-server-block","The Complete Server Block",[71,72,77],"pre",{"className":73,"code":74,"language":75,"meta":76,"style":76},"language-nginx shiki shiki-themes github-light github-dark","# \u002Fetc\u002Fnginx\u002Fsites-available\u002Fdocs.example.com\nmap $uri $redirect_to { include \u002Fsrv\u002Fsite\u002Fcurrent\u002F_redirects.map; }\n\nserver {\n  listen 80;\n  listen [::]:80;\n  server_name docs.example.com;\n  return 301 https:\u002F\u002F$host$request_uri;\n}\n\nserver {\n  listen 443 ssl;\n  listen [::]:443 ssl;\n  http2 on;\n  server_name docs.example.com;\n\n  ssl_certificate     \u002Fetc\u002Fletsencrypt\u002Flive\u002Fdocs.example.com\u002Ffullchain.pem;\n  ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fdocs.example.com\u002Fprivkey.pem;\n  ssl_protocols TLSv1.2 TLSv1.3;\n\n  root \u002Fsrv\u002Fsite\u002Fcurrent;\n  index index.html;\n  disable_symlinks off;\n  server_tokens off;\n\n  gzip_static on;\n  brotli_static on;\n  gzip on;\n  gzip_types text\u002Fcss application\u002Fjavascript application\u002Fjson image\u002Fsvg+xml application\u002Fxml;\n\n  include snippets\u002Fsite-headers.conf;\n  add_header Cache-Control \"public, max-age=0, must-revalidate\" always;\n\n  if ($redirect_to) { return 301 $redirect_to; }\n\n  location \u002F {\n    try_files $uri $uri\u002Findex.html $uri.html =404;\n  }\n\n  location ~* ^\u002F(css|js|fonts|images)\u002F.+\\.[0-9a-f]{8,}\\.(css|js|woff2|avif|webp|png|svg)$ {\n    include snippets\u002Fsite-headers.conf;\n    add_header Cache-Control \"public, max-age=31536000, immutable\" always;\n    try_files $uri =404;\n  }\n\n  location ~ \u002F\\. { deny all; return 404; }\n\n  error_page 404 \u002F404.html;\n  location = \u002F404.html { internal; }\n}\n","nginx","",[18,78,79,87,93,100,106,112,118,124,130,136,141,146,152,158,164,169,174,180,186,192,197,203,209,215,221,226,232,238,244,250,255,261,267,272,278,283,289,295,301,306,312,318,324,330,335,340,346,351,357,363],{"__ignoreMap":76},[80,81,84],"span",{"class":82,"line":83},"line",1,[80,85,86],{},"# \u002Fetc\u002Fnginx\u002Fsites-available\u002Fdocs.example.com\n",[80,88,90],{"class":82,"line":89},2,[80,91,92],{},"map $uri $redirect_to { include \u002Fsrv\u002Fsite\u002Fcurrent\u002F_redirects.map; }\n",[80,94,96],{"class":82,"line":95},3,[80,97,99],{"emptyLinePlaceholder":98},true,"\n",[80,101,103],{"class":82,"line":102},4,[80,104,105],{},"server {\n",[80,107,109],{"class":82,"line":108},5,[80,110,111],{},"  listen 80;\n",[80,113,115],{"class":82,"line":114},6,[80,116,117],{},"  listen [::]:80;\n",[80,119,121],{"class":82,"line":120},7,[80,122,123],{},"  server_name docs.example.com;\n",[80,125,127],{"class":82,"line":126},8,[80,128,129],{},"  return 301 https:\u002F\u002F$host$request_uri;\n",[80,131,133],{"class":82,"line":132},9,[80,134,135],{},"}\n",[80,137,139],{"class":82,"line":138},10,[80,140,99],{"emptyLinePlaceholder":98},[80,142,144],{"class":82,"line":143},11,[80,145,105],{},[80,147,149],{"class":82,"line":148},12,[80,150,151],{},"  listen 443 ssl;\n",[80,153,155],{"class":82,"line":154},13,[80,156,157],{},"  listen [::]:443 ssl;\n",[80,159,161],{"class":82,"line":160},14,[80,162,163],{},"  http2 on;\n",[80,165,167],{"class":82,"line":166},15,[80,168,123],{},[80,170,172],{"class":82,"line":171},16,[80,173,99],{"emptyLinePlaceholder":98},[80,175,177],{"class":82,"line":176},17,[80,178,179],{},"  ssl_certificate     \u002Fetc\u002Fletsencrypt\u002Flive\u002Fdocs.example.com\u002Ffullchain.pem;\n",[80,181,183],{"class":82,"line":182},18,[80,184,185],{},"  ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fdocs.example.com\u002Fprivkey.pem;\n",[80,187,189],{"class":82,"line":188},19,[80,190,191],{},"  ssl_protocols TLSv1.2 TLSv1.3;\n",[80,193,195],{"class":82,"line":194},20,[80,196,99],{"emptyLinePlaceholder":98},[80,198,200],{"class":82,"line":199},21,[80,201,202],{},"  root \u002Fsrv\u002Fsite\u002Fcurrent;\n",[80,204,206],{"class":82,"line":205},22,[80,207,208],{},"  index index.html;\n",[80,210,212],{"class":82,"line":211},23,[80,213,214],{},"  disable_symlinks off;\n",[80,216,218],{"class":82,"line":217},24,[80,219,220],{},"  server_tokens off;\n",[80,222,224],{"class":82,"line":223},25,[80,225,99],{"emptyLinePlaceholder":98},[80,227,229],{"class":82,"line":228},26,[80,230,231],{},"  gzip_static on;\n",[80,233,235],{"class":82,"line":234},27,[80,236,237],{},"  brotli_static on;\n",[80,239,241],{"class":82,"line":240},28,[80,242,243],{},"  gzip on;\n",[80,245,247],{"class":82,"line":246},29,[80,248,249],{},"  gzip_types text\u002Fcss application\u002Fjavascript application\u002Fjson image\u002Fsvg+xml application\u002Fxml;\n",[80,251,253],{"class":82,"line":252},30,[80,254,99],{"emptyLinePlaceholder":98},[80,256,258],{"class":82,"line":257},31,[80,259,260],{},"  include snippets\u002Fsite-headers.conf;\n",[80,262,264],{"class":82,"line":263},32,[80,265,266],{},"  add_header Cache-Control \"public, max-age=0, must-revalidate\" always;\n",[80,268,270],{"class":82,"line":269},33,[80,271,99],{"emptyLinePlaceholder":98},[80,273,275],{"class":82,"line":274},34,[80,276,277],{},"  if ($redirect_to) { return 301 $redirect_to; }\n",[80,279,281],{"class":82,"line":280},35,[80,282,99],{"emptyLinePlaceholder":98},[80,284,286],{"class":82,"line":285},36,[80,287,288],{},"  location \u002F {\n",[80,290,292],{"class":82,"line":291},37,[80,293,294],{},"    try_files $uri $uri\u002Findex.html $uri.html =404;\n",[80,296,298],{"class":82,"line":297},38,[80,299,300],{},"  }\n",[80,302,304],{"class":82,"line":303},39,[80,305,99],{"emptyLinePlaceholder":98},[80,307,309],{"class":82,"line":308},40,[80,310,311],{},"  location ~* ^\u002F(css|js|fonts|images)\u002F.+\\.[0-9a-f]{8,}\\.(css|js|woff2|avif|webp|png|svg)$ {\n",[80,313,315],{"class":82,"line":314},41,[80,316,317],{},"    include snippets\u002Fsite-headers.conf;\n",[80,319,321],{"class":82,"line":320},42,[80,322,323],{},"    add_header Cache-Control \"public, max-age=31536000, immutable\" always;\n",[80,325,327],{"class":82,"line":326},43,[80,328,329],{},"    try_files $uri =404;\n",[80,331,333],{"class":82,"line":332},44,[80,334,300],{},[80,336,338],{"class":82,"line":337},45,[80,339,99],{"emptyLinePlaceholder":98},[80,341,343],{"class":82,"line":342},46,[80,344,345],{},"  location ~ \u002F\\. { deny all; return 404; }\n",[80,347,349],{"class":82,"line":348},47,[80,350,99],{"emptyLinePlaceholder":98},[80,352,354],{"class":82,"line":353},48,[80,355,356],{},"  error_page 404 \u002F404.html;\n",[80,358,360],{"class":82,"line":359},49,[80,361,362],{},"  location = \u002F404.html { internal; }\n",[80,364,366],{"class":82,"line":365},50,[80,367,135],{},[37,369,371],{"id":370},"headers-that-survive-nested-blocks","Headers That Survive Nested Blocks",[14,373,374,375,378,379,381,382,389,390,393,394,397],{},"The ",[18,376,377],{},"include snippets\u002Fsite-headers.conf"," appears twice on purpose. In nginx, ",[18,380,20],{}," is inherited from an outer block ",[383,384,385,386,388],"strong",{},"only if the inner block has no ",[18,387,20],{}," of its own",". The asset location adds ",[18,391,392],{},"Cache-Control",", so without repeating the include it would lose HSTS, CSP and every other security header. The same applies to ",[18,395,396],{},"if"," blocks and nested locations.",[71,399,401],{"className":73,"code":400,"language":75,"meta":76,"style":76},"# \u002Fetc\u002Fnginx\u002Fsnippets\u002Fsite-headers.conf\nadd_header Strict-Transport-Security \"max-age=31536000; includeSubDomains\" always;\nadd_header X-Content-Type-Options \"nosniff\" always;\nadd_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\nadd_header X-Frame-Options \"DENY\" always;\nadd_header Content-Security-Policy \"default-src 'self'; img-src 'self' data:; frame-ancestors 'none'\" always;\n",[18,402,403,408,413,418,423,428],{"__ignoreMap":76},[80,404,405],{"class":82,"line":83},[80,406,407],{},"# \u002Fetc\u002Fnginx\u002Fsnippets\u002Fsite-headers.conf\n",[80,409,410],{"class":82,"line":89},[80,411,412],{},"add_header Strict-Transport-Security \"max-age=31536000; includeSubDomains\" always;\n",[80,414,415],{"class":82,"line":95},[80,416,417],{},"add_header X-Content-Type-Options \"nosniff\" always;\n",[80,419,420],{"class":82,"line":102},[80,421,422],{},"add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n",[80,424,425],{"class":82,"line":108},[80,426,427],{},"add_header X-Frame-Options \"DENY\" always;\n",[80,429,430],{"class":82,"line":114},[80,431,432],{},"add_header Content-Security-Policy \"default-src 'self'; img-src 'self' data:; frame-ancestors 'none'\" always;\n",[14,434,374,435,438,439,35],{},[18,436,437],{},"always"," parameter makes nginx add the header to error responses too; without it, a 404 page ships with no headers at all. The header values themselves are explained in ",[31,440,442],{"href":441},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002F","Security Headers and Hardening for Static Sites",[444,445,446,585],"figure",{},[447,448,455,456,455,460,455,464,455,471,455,570],"svg",{"viewBox":449,"role":450,"ariaLabelledBy":451,"xmlns":454},"0 0 760 280","img",[452,453],"ngx-inh-title","ngx-inh-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[457,458,459],"title",{"id":452},"How add_header inheritance drops headers",[461,462,463],"desc",{"id":453},"The server block sets five security headers. The root location adds none of its own, so it inherits all five. The asset location adds Cache-Control, and without an include it inherits nothing, shipping only Cache-Control. With the include repeated, the asset location sends all six headers.",[465,466],"rect",{"x":467,"y":467,"width":468,"height":469,"fill":470},"0","760","280","#ffffff",[472,473,475,476,475,484,475,494,475,499,475,505,475,514,475,519,475,524,475,529,475,532,475,536,475,540,475,543,475,547,475,550,475,566,455],"g",{"style":474},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[477,478,483],"text",{"x":479,"y":480,"fill":481,"style":482},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","add_header in a child block replaces, it does not merge",[465,485],{"x":486,"y":487,"width":488,"height":489,"rx":490,"fill":491,"opacity":492,"stroke":491,"style":493},"260","50","240","46","10","#6a4c93","0.14","stroke-width:1.5px",[477,495,498],{"x":479,"y":496,"fill":481,"style":497},"70","font-weight:700;text-anchor:middle","server { 5 security headers }",[477,500,504],{"x":479,"y":501,"fill":502,"style":503},"86","#556071","font-size:11px;text-anchor:middle","HSTS · nosniff · referrer · frame · CSP",[465,506],{"x":507,"y":508,"width":509,"height":510,"rx":490,"fill":511,"opacity":512,"stroke":513,"style":493},"30","140","210","80","#8ac926","0.18","#5a8a16",[477,515,518],{"x":516,"y":517,"fill":481,"style":497},"135","164","location \u002F { }",[477,520,523],{"x":516,"y":521,"fill":522,"style":497},"186","#3f6410","inherits all 5",[465,525],{"x":526,"y":508,"width":509,"height":510,"rx":490,"fill":527,"opacity":492,"stroke":528,"style":493},"275","#ff595e","#d83b41",[477,530,531],{"x":479,"y":517,"fill":481,"style":497},"assets { Cache-Control }",[477,533,535],{"x":479,"y":521,"fill":534,"style":497},"#b32b30","sends 1: Cache-Control",[477,537,539],{"x":479,"y":538,"fill":502,"style":503},"206","security headers lost",[465,541],{"x":542,"y":508,"width":509,"height":510,"rx":490,"fill":511,"opacity":512,"stroke":513,"style":493},"520",[477,544,546],{"x":545,"y":517,"fill":481,"style":497},"625","assets { include + CC }",[477,548,549],{"x":545,"y":521,"fill":522,"style":497},"sends all 6",[472,551,554,555,554,560,554,563,475],{"stroke":502,"fill":552,"style":553},"none","stroke-width:2px","\n      ",[556,557],"path",{"d":558,"style":559},"M300 98 L170 136","marker-end:url(#ngx-arrow)",[556,561],{"d":562,"style":559},"M380 98 L380 136",[556,564],{"d":565,"style":559},"M460 98 L600 136",[477,567,569],{"x":479,"y":568,"fill":502,"style":503},"256","Found on 3 of 4 self-hosted sites audited: assets served with no HSTS or CSP",[571,572,475,573,455],"defs",{},[574,575,554,582,475],"marker",{"id":576,"viewBox":577,"refX":578,"refY":579,"markerWidth":580,"markerHeight":580,"orient":581},"ngx-arrow","0 0 10 10","8","5","7","auto-start-reverse",[556,583],{"d":584,"fill":502},"M0 0 L10 5 L0 10 z",[586,587,588],"figcaption",{},"The fix is mechanical — an include in every block that adds a header — but it has to be deliberate, because nothing warns you.",[37,590,592],{"id":591},"clean-urls-without-redirect-loops","Clean URLs Without Redirect Loops",[14,594,595,598,599,602,603,606,607,610,611,614,615,617,618,621],{},[18,596,597],{},"try_files $uri $uri\u002Findex.html $uri.html =404"," resolves ",[18,600,601],{},"\u002Fguides\u002Fdeploying\u002F"," to ",[18,604,605],{},"guides\u002Fdeploying\u002Findex.html"," directly, with no redirect. Two alternatives cause trouble. Using ",[18,608,609],{},"$uri\u002F"," instead of ",[18,612,613],{},"$uri\u002Findex.html"," makes nginx issue a 301 to add a trailing slash, then serve the index — an extra round trip on every directory URL without one. And relying on ",[18,616,28],{}," alone with ",[18,619,620],{},"try_files $uri $uri\u002F"," produces the same redirect. Decide your canonical form (Hugo, Astro and Eleventy all default to trailing slashes) and add a single explicit rule to redirect the other form, rather than letting nginx improvise:",[71,623,625],{"className":73,"code":624,"language":75,"meta":76,"style":76},"# canonical trailing slash: \u002Fguides\u002Fdeploying → \u002Fguides\u002Fdeploying\u002F\nlocation ~ ^([^.]*[^\u002F])$ { return 301 $1\u002F; }\n",[18,626,627,632],{"__ignoreMap":76},[80,628,629],{"class":82,"line":83},[80,630,631],{},"# canonical trailing slash: \u002Fguides\u002Fdeploying → \u002Fguides\u002Fdeploying\u002F\n",[80,633,634],{"class":82,"line":89},[80,635,636],{},"location ~ ^([^.]*[^\u002F])$ { return 301 $1\u002F; }\n",[444,638,639,729],{},[447,640,455,645,455,648,455,651,455,654,455,722],{"viewBox":641,"role":450,"ariaLabelledBy":642,"xmlns":454},"0 0 760 250",[643,644],"ngx-try-title","ngx-try-desc",[457,646,647],{"id":643},"How try_files resolves a request",[461,649,650],{"id":644},"A request for \u002Fguides\u002Fdeploying\u002F is checked against four candidates in order: the exact path as a file, the path plus index.html, the path plus .html, and finally a 404. The second candidate exists, so nginx serves guides\u002Fdeploying\u002Findex.html with status 200 and no redirect.",[465,652],{"x":467,"y":467,"width":468,"height":653,"fill":470},"250",[472,655,475,656,475,659,475,665,475,670,475,674,475,678,475,681,475,685,475,689,475,693,475,696,475,699,475,703,475,705,475,711,475,717,455],{"style":474},[477,657,658],{"x":479,"y":480,"fill":481,"style":482},"GET \u002Fguides\u002Fdeploying\u002F — first match wins",[465,660],{"x":507,"y":496,"width":661,"height":662,"rx":578,"fill":663,"stroke":664,"style":493},"160","56","#f8fafc","#d9e2ef",[477,666,669],{"x":667,"y":668,"fill":481,"style":497},"110","94","$uri",[477,671,673],{"x":667,"y":672,"fill":502,"style":503},"112","is a directory: skip",[465,675],{"x":509,"y":496,"width":676,"height":662,"rx":578,"fill":511,"opacity":677,"stroke":513,"style":493},"170","0.2",[477,679,613],{"x":680,"y":668,"fill":481,"style":497},"295",[477,682,684],{"x":680,"y":672,"fill":522,"style":683},"font-size:11px;font-weight:700;text-anchor:middle","exists → 200",[465,686],{"x":687,"y":496,"width":688,"height":662,"rx":578,"fill":663,"stroke":664,"style":493},"400","150",[477,690,692],{"x":691,"y":668,"fill":502,"style":497},"475","$uri.html",[477,694,695],{"x":691,"y":672,"fill":502,"style":503},"not reached",[465,697],{"x":698,"y":496,"width":661,"height":662,"rx":578,"fill":663,"stroke":664,"style":493},"570",[477,700,702],{"x":701,"y":668,"fill":502,"style":497},"650","=404",[477,704,695],{"x":701,"y":672,"fill":502,"style":503},[472,706,554,707,475],{"stroke":502,"fill":552,"style":553},[556,708],{"d":709,"style":710},"M192 98 L206 98","marker-end:url(#ngx-t-arrow)",[465,712],{"x":507,"y":661,"width":713,"height":714,"rx":578,"fill":527,"opacity":715,"stroke":528,"style":716},"700","40","0.08","stroke-width:1px",[477,718,721],{"x":479,"y":719,"fill":481,"style":720},"185","text-anchor:middle","using $uri\u002F instead would 301 to add a slash first, costing a round trip",[571,723,475,724,455],{},[574,725,554,727,475],{"id":726,"viewBox":577,"refX":578,"refY":579,"markerWidth":580,"markerHeight":580,"orient":581},"ngx-t-arrow",[556,728],{"d":584,"fill":502},[586,730,731],{},"Listing the index file explicitly keeps every canonical URL to a single request.",[14,733,374,734,736,737,740,741,744],{},[18,735,692],{}," fallback supports generators configured for extensionless URLs without directories — Hugo's ",[18,738,739],{},"uglyURLs"," or VitePress's ",[18,742,743],{},"cleanUrls"," — so the same block serves either output style. If your generator writes only one style, removing the unused candidate saves a filesystem lookup per request, though at these volumes the difference is too small to measure.",[37,746,748],{"id":747},"precompressed-assets","Precompressed Assets",[14,750,751,752,755,756,759],{},"Compressing on every request costs CPU; precompressing at build time costs nothing per request and allows maximum compression levels. Generate ",[18,753,754],{},".br"," and ",[18,757,758],{},".gz"," siblings after the build:",[71,761,765],{"className":762,"code":763,"language":764,"meta":76,"style":76},"language-bash shiki shiki-themes github-light github-dark","find public -type f \\( -name '*.html' -o -name '*.css' -o -name '*.js' -o -name '*.svg' -o -name '*.json' -o -name '*.xml' \\) \\\n  -exec brotli -q 11 -k {} \\; -exec gzip -9 -k {} \\;\n","bash",[18,766,767,835],{"__ignoreMap":76},[80,768,769,773,777,781,784,787,790,793,796,798,801,803,805,808,810,812,815,817,819,822,824,826,829,832],{"class":82,"line":83},[80,770,772],{"class":771},"sScJk","find",[80,774,776],{"class":775},"sZZnC"," public",[80,778,780],{"class":779},"sj4cs"," -type",[80,782,783],{"class":775}," f",[80,785,786],{"class":779}," \\(",[80,788,789],{"class":779}," -name",[80,791,792],{"class":775}," '*.html'",[80,794,795],{"class":779}," -o",[80,797,789],{"class":779},[80,799,800],{"class":775}," '*.css'",[80,802,795],{"class":779},[80,804,789],{"class":779},[80,806,807],{"class":775}," '*.js'",[80,809,795],{"class":779},[80,811,789],{"class":779},[80,813,814],{"class":775}," '*.svg'",[80,816,795],{"class":779},[80,818,789],{"class":779},[80,820,821],{"class":775}," '*.json'",[80,823,795],{"class":779},[80,825,789],{"class":779},[80,827,828],{"class":775}," '*.xml'",[80,830,831],{"class":779}," \\)",[80,833,834],{"class":779}," \\\n",[80,836,837,840,843,846,849,852,855,858,861,864,867,869,871],{"class":82,"line":89},[80,838,839],{"class":779},"  -exec",[80,841,842],{"class":775}," brotli",[80,844,845],{"class":779}," -q",[80,847,848],{"class":779}," 11",[80,850,851],{"class":779}," -k",[80,853,854],{"class":775}," {}",[80,856,857],{"class":779}," \\;",[80,859,860],{"class":779}," -exec",[80,862,863],{"class":775}," gzip",[80,865,866],{"class":779}," -9",[80,868,851],{"class":779},[80,870,854],{"class":775},[80,872,873],{"class":779}," \\;\n",[14,875,876,877,755,880,883,884,887,888,891],{},"With ",[18,878,879],{},"brotli_static on",[18,881,882],{},"gzip_static on",", nginx sends ",[18,885,886],{},"page.html.br"," to browsers that accept brotli, ",[18,889,890],{},"page.html.gz"," to those that accept only gzip, and the original otherwise. On this site the median HTML page shrank from 38 KB raw to 7.4 KB with brotli level 11, against 8.9 KB with nginx's on-the-fly gzip level 6 — and CPU per request dropped to near zero.",[37,893,895],{"id":894},"redirects-from-a-map","Redirects From a Map",[14,897,898,899,902,903,906],{},"Hundreds of redirects written as ",[18,900,901],{},"location"," blocks are slow to evaluate and hard to review. A ",[18,904,905],{},"map"," generated at build time keeps them in one data file:",[71,908,912],{"className":909,"code":911,"language":477,"meta":76},[910],"language-text","# public\u002F_redirects.map (generated by the build)\n\u002Fdocs\u002Fold-install\u002F     \u002Fguides\u002Finstall\u002F;\n\u002Fblog\u002F2019\u002F            \u002Fblog\u002F;\n~^\u002Fv1\u002F(.*)$            \u002Farchive\u002Fv1\u002F$1;\n",[18,913,911],{"__ignoreMap":76},[14,915,916,917,920,921,35],{},"Maps are hashed for exact matches, so lookup cost stays flat as the list grows. Regex entries (prefixed ",[18,918,919],{},"~",") are evaluated in order after exact matches; keep them few. Generating the map from the same source as your other hosts' redirect files means one list serves every deploy target — see ",[31,922,924],{"href":923},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fkeeping-redirects-working-after-an-ssg-migration\u002F","Keeping Redirects Working After an SSG Migration",[37,926,928],{"id":927},"measured-impact","Measured Impact",[14,930,931,932,935,936,939],{},"The site moved from nginx with a default configuration to the block above. Measurements from ",[18,933,934],{},"curl",", Lighthouse and ",[18,937,938],{},"wrk"," on a 2 vCPU VM.",[941,942,943,959],"table",{},[944,945,946],"thead",{},[947,948,949,953,956],"tr",{},[950,951,952],"th",{},"Measure",[950,954,955],{},"Default config",[950,957,958],{},"Tuned config",[960,961,962,974,983,994,1005,1019],"tbody",{},[947,963,964,968,971],{},[965,966,967],"td",{},"Security headers on hashed assets",[965,969,970],{},"0 of 5",[965,972,973],{},"5 of 5",[947,975,976,979,981],{},[965,977,978],{},"Security headers on 404 responses",[965,980,970],{},[965,982,973],{},[947,984,985,988,991],{},[965,986,987],{},"Redirects for slashless directory URLs",[965,989,990],{},"1 extra hop",[965,992,993],{},"canonical 301, then direct",[947,995,996,999,1002],{},[965,997,998],{},"Median HTML transfer size",[965,1000,1001],{},"11.2 KB (gzip 6)",[965,1003,1004],{},"7.4 KB (brotli 11)",[947,1006,1007,1013,1016],{},[965,1008,1009,1010,1012],{},"Requests\u002Fs, cached HTML (",[18,1011,938],{},", 64 conns)",[965,1014,1015],{},"9,800",[965,1017,1018],{},"14,200",[947,1020,1021,1024,1027],{},[965,1022,1023],{},"CPU at 1,000 req\u002Fs",[965,1025,1026],{},"21%",[965,1028,1029],{},"6%",[444,1031,1032,1114],{},[447,1033,455,1038,455,1041,455,1044,455,1046],{"viewBox":1034,"role":450,"ariaLabelledBy":1035,"xmlns":454},"0 0 760 260",[1036,1037],"ngx-perf-title","ngx-perf-desc",[457,1039,1040],{"id":1036},"Throughput and CPU before and after tuning",[461,1042,1043],{"id":1037},"Two bar pairs. Requests per second rose from 9,800 to 14,200 after switching to precompressed files. CPU use at a steady 1,000 requests per second fell from 21 percent to 6 percent.",[465,1045],{"x":467,"y":467,"width":468,"height":486,"fill":470},[472,1047,475,1048,475,1051,475,1056,475,1059,475,1065,475,1069,475,1074,475,1076,475,1078,475,1081,475,1086,475,1090,475,1095,475,1099,475,1103,475,1106,475,1108,475,1110,455],{"style":474},[477,1049,1050],{"x":479,"y":480,"fill":481,"style":482},"Precompression: more throughput, less CPU (2 vCPU VM)",[477,1052,1055],{"x":1053,"y":1054,"fill":481,"style":497},"120","60","requests \u002F second",[82,1057],{"x1":1054,"y1":509,"x2":1058,"y2":509,"stroke":502,"style":493},"320",[465,1060],{"x":1061,"y":672,"width":510,"height":1062,"fill":1063,"opacity":1064,"stroke":1063,"style":716},"90","98","#1982c4","0.25",[477,1066,1015],{"x":1067,"y":1068,"fill":481,"style":497},"130","104",[465,1070],{"x":509,"y":1071,"width":510,"height":1072,"fill":511,"opacity":1073,"stroke":513,"style":716},"68","142","0.35",[477,1075,1018],{"x":653,"y":1054,"fill":522,"style":497},[477,1077,1023],{"x":698,"y":1054,"fill":481,"style":497},[82,1079],{"x1":1080,"y1":509,"x2":713,"y2":509,"stroke":502,"style":493},"440",[465,1082],{"x":1083,"y":1084,"width":510,"height":1085,"fill":527,"opacity":1064,"stroke":528,"style":716},"470","84","126",[477,1087,1026],{"x":1088,"y":1089,"fill":481,"style":497},"510","76",[465,1091],{"x":1092,"y":1093,"width":510,"height":1094,"fill":511,"opacity":1073,"stroke":513,"style":716},"590","174","36",[477,1096,1029],{"x":1097,"y":1098,"fill":522,"style":497},"630","166",[477,1100,1102],{"x":1067,"y":1101,"fill":502,"style":503},"228","on-the-fly gzip",[477,1104,1105],{"x":653,"y":1101,"fill":502,"style":503},"precompressed",[477,1107,1102],{"x":1088,"y":1101,"fill":502,"style":503},[477,1109,1105],{"x":1097,"y":1101,"fill":502,"style":503},[477,1111,1113],{"x":479,"y":1112,"fill":502,"style":503},"252","wrk -t4 -c64 -d30s against a 38 KB HTML page, local network",[586,1115,1116],{},"Compression moved from request time to build time, where it runs once per deploy instead of once per reader.",[37,1118,1120],{"id":1119},"testing-the-configuration","Testing the Configuration",[14,1122,1123,1124,1127],{},"Treat the server block like code. ",[18,1125,1126],{},"nginx -t"," catches syntax errors but not behaviour, so keep a short script that runs after every config change and every deploy, checking status codes, headers and redirects for a representative set of URLs:",[71,1129,1131],{"className":762,"code":1130,"language":764,"meta":76,"style":76},"check() { curl -s -o \u002Fdev\u002Fnull -w \"%{http_code} %{redirect_url}\\n\" \"$1\"; }\ncheck https:\u002F\u002Fdocs.example.com\u002Fguides\u002Fdeploying\u002F          # 200\ncheck https:\u002F\u002Fdocs.example.com\u002Fguides\u002Fdeploying           # 301 → ...\u002Fdeploying\u002F\ncheck https:\u002F\u002Fdocs.example.com\u002Fdocs\u002Fold-install\u002F          # 301 → \u002Fguides\u002Finstall\u002F\ncheck https:\u002F\u002Fdocs.example.com\u002F.git\u002Fconfig                # 404\ncurl -sI https:\u002F\u002Fdocs.example.com\u002Fcss\u002Fsite.4f2a9c1e.css | grep -ci 'strict-transport'   # 1\n",[18,1132,1133,1170,1181,1191,1201,1211],{"__ignoreMap":76},[80,1134,1135,1138,1142,1144,1147,1149,1152,1155,1158,1161,1164,1167],{"class":82,"line":83},[80,1136,1137],{"class":771},"check",[80,1139,1141],{"class":1140},"sVt8B","() { ",[80,1143,934],{"class":771},[80,1145,1146],{"class":779}," -s",[80,1148,795],{"class":779},[80,1150,1151],{"class":775}," \u002Fdev\u002Fnull",[80,1153,1154],{"class":779}," -w",[80,1156,1157],{"class":775}," \"%{http_code} %{redirect_url}\\n\"",[80,1159,1160],{"class":775}," \"",[80,1162,1163],{"class":779},"$1",[80,1165,1166],{"class":775},"\"",[80,1168,1169],{"class":1140},"; }\n",[80,1171,1172,1174,1177],{"class":82,"line":89},[80,1173,1137],{"class":771},[80,1175,1176],{"class":775}," https:\u002F\u002Fdocs.example.com\u002Fguides\u002Fdeploying\u002F",[80,1178,1180],{"class":1179},"sJ8bj","          # 200\n",[80,1182,1183,1185,1188],{"class":82,"line":95},[80,1184,1137],{"class":771},[80,1186,1187],{"class":775}," https:\u002F\u002Fdocs.example.com\u002Fguides\u002Fdeploying",[80,1189,1190],{"class":1179},"           # 301 → ...\u002Fdeploying\u002F\n",[80,1192,1193,1195,1198],{"class":82,"line":102},[80,1194,1137],{"class":771},[80,1196,1197],{"class":775}," https:\u002F\u002Fdocs.example.com\u002Fdocs\u002Fold-install\u002F",[80,1199,1200],{"class":1179},"          # 301 → \u002Fguides\u002Finstall\u002F\n",[80,1202,1203,1205,1208],{"class":82,"line":108},[80,1204,1137],{"class":771},[80,1206,1207],{"class":775}," https:\u002F\u002Fdocs.example.com\u002F.git\u002Fconfig",[80,1209,1210],{"class":1179},"                # 404\n",[80,1212,1213,1215,1218,1221,1225,1228,1231,1234],{"class":82,"line":114},[80,1214,934],{"class":771},[80,1216,1217],{"class":779}," -sI",[80,1219,1220],{"class":775}," https:\u002F\u002Fdocs.example.com\u002Fcss\u002Fsite.4f2a9c1e.css",[80,1222,1224],{"class":1223},"szBVR"," |",[80,1226,1227],{"class":771}," grep",[80,1229,1230],{"class":779}," -ci",[80,1232,1233],{"class":775}," 'strict-transport'",[80,1235,1236],{"class":1179},"   # 1\n",[14,1238,1239,1240,1244],{},"Wire it into CI as a post-deploy smoke test; it is the self-hosted equivalent of ",[31,1241,1243],{"href":1242},"\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",". Keep the nginx config in the same repository as the site, deployed by the same pipeline, so a routing change and the content that depends on it ship together.",[37,1246,1248],{"id":1247},"pitfalls-rollback","Pitfalls & Rollback",[42,1250,1251,1259,1267,1281,1299,1308,1322],{},[45,1252,1253,1258],{},[383,1254,1255,1256,35],{},"Forgetting ",[18,1257,437],{}," Without it, headers are omitted from 4xx and 5xx responses.",[45,1260,1261,1266],{},[383,1262,1263,1265],{},[18,1264,20],{}," in a child block without the include."," Every parent header is dropped for that block.",[45,1268,1269,1272,1273,1276,1277,1280],{},[383,1270,1271],{},"Serving dotfiles."," A ",[18,1274,1275],{},".git"," directory accidentally copied into the web root is fully downloadable. Deny ",[18,1278,1279],{},"\u002F\\."," explicitly.",[45,1282,1283,1288,1289,1291,1292,1294,1295,755,1297,35],{},[383,1284,1285,1287],{},[18,1286,396],{}," for routing."," Beyond the single redirect-map check, avoid ",[18,1290,396],{}," inside ",[18,1293,901],{},"; use ",[18,1296,905],{},[18,1298,24],{},[45,1300,1301,1304,1305,1307],{},[383,1302,1303],{},"Stale precompressed files."," If the build emits new HTML but an old ",[18,1306,754],{}," sibling survives, nginx serves the old compressed version. Generate compressed files into each fresh release directory, never into a shared one.",[45,1309,1310,1313,1314,1317,1318,1321],{},[383,1311,1312],{},"Open file cache surprises."," ",[18,1315,1316],{},"open_file_cache"," speeds up lookups but can serve a file from a previous release for its validity period after a symlink swap; keep ",[18,1319,1320],{},"open_file_cache_valid"," short or reload nginx after each deploy.",[45,1323,1324,1327,1328,1331],{},[383,1325,1326],{},"Rollback:"," keep configs in Git and deploy them with the site. ",[18,1329,1330],{},"nginx -t && systemctl reload nginx"," applies a change without dropping connections; reverting is the same command on the previous file.",[37,1333,1335],{"id":1334},"conclusion","Conclusion",[14,1337,1338,1339,1341],{},"A production nginx setup for a static site is short: HTTP to HTTPS redirect, ",[18,1340,24],{}," for clean URLs, two cache tiers, precompressed files, a redirect map and a header include repeated in every block that adds headers. That last detail is the one most self-hosted sites miss. On this site the tuned block restored security headers to every asset and error response, cut HTML transfer by a third and nearly halved CPU per request.",[37,1343,1345],{"id":1344},"faq","FAQ",[1347,1348,1350],"h3",{"id":1349},"why-do-my-security-headers-disappear-on-some-files","Why do my security headers disappear on some files?",[14,1352,1353],{},"Because nginx's add_header directives in a location block replace, rather than extend, those inherited from the server block. Any location that adds its own header, such as Cache-Control for assets, drops the inherited ones. Put the header set in an include file and include it in every block that adds headers.",[1347,1355,1357],{"id":1356},"how-does-nginx-serve-guide-from-guideindexhtml","How does nginx serve \u002Fguide\u002F from guide\u002Findex.html?",[14,1359,1360],{},"With try_files $uri $uri\u002Findex.html $uri.html =404. It checks the exact path, then a directory index, then an .html file, and returns 404 if none exist, without a redirect.",[1347,1362,1364],{"id":1363},"should-nginx-compress-on-the-fly-or-serve-precompressed-files","Should nginx compress on the fly or serve precompressed files?",[14,1366,1367],{},"Serve precompressed files when you can. Generate .br and .gz files at build time and enable brotli_static and gzip_static, so nginx sends the smallest file without spending CPU per request. Fall back to on-the-fly gzip for anything not precompressed.",[1347,1369,1371],{"id":1370},"is-nginx-fast-enough-without-a-cdn","Is nginx fast enough without a CDN?",[14,1373,1374],{},"For throughput, easily; a small VM serves thousands of static requests per second. The limit is latency for distant readers, which a CDN in front solves.",[37,1376,1378],{"id":1377},"related","Related",[42,1380,1381,1389,1396,1404,1411],{},[45,1382,1383,1313,1386,1388],{},[383,1384,1385],{},"Parent:",[31,1387,34],{"href":33}," — comparing self-hosting options.",[45,1390,1391,1395],{},[31,1392,1394],{"href":1393},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-caddy\u002F","Serving a Static Site with Caddy"," — the same features with less configuration.",[45,1397,1398,1400,1401,1403],{},[31,1399,62],{"href":61}," — how ",[18,1402,57],{}," gets updated.",[45,1405,1406,1410],{},[31,1407,1409],{"href":1408},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fcache-busting-with-content-hashed-filenames\u002F","Cache Busting with Content-Hashed Filenames"," — why the asset location can be immutable.",[45,1412,1413,1417],{},[31,1414,1416],{"href":1415},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fenabling-hsts-and-preload-safely\u002F","Enabling HSTS and Preload Safely"," — rolling out the HSTS header in the include.",[1419,1420,1421],"style",{},"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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":76,"searchDepth":89,"depth":89,"links":1423},[1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1440],{"id":39,"depth":89,"text":40},{"id":68,"depth":89,"text":69},{"id":370,"depth":89,"text":371},{"id":591,"depth":89,"text":592},{"id":747,"depth":89,"text":748},{"id":894,"depth":89,"text":895},{"id":927,"depth":89,"text":928},{"id":1119,"depth":89,"text":1120},{"id":1247,"depth":89,"text":1248},{"id":1334,"depth":89,"text":1335},{"id":1344,"depth":89,"text":1345,"children":1435},[1436,1437,1438,1439],{"id":1349,"depth":95,"text":1350},{"id":1356,"depth":95,"text":1357},{"id":1363,"depth":95,"text":1364},{"id":1370,"depth":95,"text":1371},{"id":1377,"depth":89,"text":1378},[1442,1445,1448,1449],{"name":1443,"item":1444},"Home","\u002F",{"name":1446,"item":1447},"Production-Ready Deployment & CI\u002FCD Workflows","\u002Fproduction-ready-deployment-cicd-workflows\u002F",{"name":34,"item":33},{"name":5,"item":1450},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx\u002F","2026-09-18","A production nginx config for a static site: clean URLs, two cache tiers, brotli, security headers that survive nested blocks, redirects, TLS and the 404 page.","md",[1455,1456,1457,1458],{"q":1350,"a":1353},{"q":1357,"a":1360},{"q":1364,"a":1367},{"q":1371,"a":1374},{},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx",{"title":5,"description":1452},"production-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx\u002Findex","article","8IcDYDVq3xRG2QKve3XTCtMlrDlRYlorn_X3qrHRuqw",[1466,1469,1472,1475,1478,1481,1484,1487,1490,1493,1496,1499,1502,1505,1508,1511,1514,1517,1520,1523,1526,1529,1532,1535,1538,1541,1544,1547,1550,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,1629,1632,1635,1638,1641,1644,1647,1650,1653,1656,1659,1662,1665,1668,1671,1674,1677,1680,1683,1686,1689,1692,1695,1698,1701,1704,1707,1710,1713,1716,1719,1722,1725,1728,1731,1734,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,1821,1824,1827,1830,1833,1836,1839,1842,1845,1848,1851,1854,1857,1860,1863,1866,1869,1872,1875,1878,1881,1884,1887,1890,1893,1896,1899,1902,1905,1908,1911,1914,1917,1920,1923,1926,1929,1932,1935,1938,1941,1944,1947,1950,1952,1955,1957,1960,1962,1965,1968,1971,1974,1977,1980,1982,1984,1985,1987,1990,1993,1996,1999,2002],{"path":1467,"title":1468},"\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":1470,"title":1471},"\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":1473,"title":1474},"\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":1476,"title":1477},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1479,"title":1480},"\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":1482,"title":1483},"\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":1485,"title":1486},"\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":1488,"title":1489},"\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":1491,"title":1492},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress","Docs Frameworks: Docusaurus, Starlight and VitePress",{"path":1494,"title":1495},"\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":1497,"title":1498},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fmigrating-from-mkdocs-to-starlight","Migrating from MkDocs to Starlight",{"path":1500,"title":1501},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fversioned-documentation-with-docusaurus","Versioned Documentation with Docusaurus",{"path":1503,"title":1504},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fvitepress-for-library-documentation","VitePress for Library Documentation",{"path":1506,"title":1507},"\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":1509,"title":1510},"\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":1512,"title":1513},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1515,"title":1516},"\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":1518,"title":1519},"\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":1521,"title":1522},"\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":1524,"title":1525},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":1527,"title":1528},"\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":1530,"title":1531},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":1533,"title":1534},"\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":1536,"title":1537},"\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":1539,"title":1540},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fspeeding-up-slow-jekyll-builds","Speeding Up Slow Jekyll Builds",{"path":1542,"title":1543},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fupgrading-jekyll-and-ruby-versions-safely","Upgrading Jekyll and Ruby Versions Safely",{"path":1545,"title":1546},"\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":1548,"title":1549},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":1551,"title":924},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fkeeping-redirects-working-after-an-ssg-migration",{"path":1553,"title":1554},"\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":1556,"title":1557},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-gatsby-to-astro","Migrating from Gatsby to Astro",{"path":1559,"title":1560},"\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":1562,"title":1563},"\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":1565,"title":1566},"\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":1568,"title":1569},"\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":1571,"title":1572},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1574,"title":1575},"\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":1577,"title":1578},"\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":1580,"title":1581},"\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":1583,"title":1584},"\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":1586,"title":1587},"\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":1589,"title":1590},"\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":1592,"title":1593},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites","Search for Static Sites",{"path":1595,"title":1596},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Findexing-hugo-sites-with-pagefind","Indexing Hugo Sites with Pagefind",{"path":1598,"title":1599},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fmultilingual-search-on-static-sites","Multilingual Search on Static Sites",{"path":1601,"title":1602},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fpagefind-vs-algolia-docsearch","Pagefind vs Algolia DocSearch",{"path":1604,"title":1605},"\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":1607,"title":1608},"\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":1610,"title":1611},"\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":1613,"title":1614},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fevaluating-ssg-accessibility-defaults","Evaluating SSG Accessibility Defaults",{"path":1616,"title":1617},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":1619,"title":1620},"\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":1622,"title":1623},"\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":1625,"title":1626},"\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":1628,"title":1409},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs\u002Fcache-busting-with-content-hashed-filenames",{"path":1630,"title":1631},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":1633,"title":1634},"\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":1636,"title":1637},"\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":1639,"title":1640},"\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":1642,"title":1643},"\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":1645,"title":1646},"\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":1648,"title":1649},"\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":1651,"title":1652},"\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":1654,"title":1655},"\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":1657,"title":1658},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":1660,"title":1661},"\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":1663,"title":1664},"\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":1666,"title":1667},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Ffont-display-optional-vs-swap","font-display: optional vs swap",{"path":1669,"title":1670},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":1672,"title":1673},"\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":1675,"title":1676},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fpreloading-fonts-without-double-downloads","Preloading Fonts Without Double Downloads",{"path":1678,"title":1679},"\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":1681,"title":1682},"\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":1684,"title":1685},"\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":1687,"title":1688},"\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":1690,"title":1691},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":1693,"title":1694},"\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":1696,"title":1697},"\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":1699,"title":1700},"\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":1702,"title":1703},"\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":1705,"title":1706},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":1708,"title":1709},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":1711,"title":1712},"\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":1714,"title":1715},"\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":1717,"title":1718},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":1720,"title":1721},"\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":1723,"title":1724},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Freplacing-react-islands-with-web-components","Replacing React Islands with Web Components",{"path":1726,"title":1727},"\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":1729,"title":1730},"\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":1732,"title":1733},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":1735,"title":1736},"\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":1738,"title":1739},"\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":1741,"title":1742},"\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":1744,"title":1745},"\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":1747,"title":1748},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci","Performance Budgets and Lighthouse CI",{"path":1750,"title":1751},"\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":1753,"title":1754},"\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":1756,"title":1757},"\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":1759,"title":1760},"\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":1762,"title":1763},"\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":1765,"title":1766},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fauditing-unused-preloads","Auditing Unused Preloads",{"path":1768,"title":1769},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed","Resource Hints and Navigation Speed",{"path":1771,"title":1772},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Finstant-navigation-with-speculation-rules","Instant Navigation with Speculation Rules",{"path":1774,"title":1775},"\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":1777,"title":1778},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fprefetching-links-in-astro","Prefetching Links in Astro",{"path":1780,"title":1781},"\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":1783,"title":1784},"\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":1786,"title":1787},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":1789,"title":1790},"\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":1792,"title":1793},"\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":1795,"title":1796},"\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":1798,"title":1799},"\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":1801,"title":1802},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":1804,"title":1805},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fconfiguring-redirects-on-cloudflare-pages","Configuring Redirects on Cloudflare Pages",{"path":1807,"title":1808},"\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":1810,"title":1811},"\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":1813,"title":1814},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":1816,"title":1817},"\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":1819,"title":1820},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fchecking-links-in-pull-requests","Checking Links in Pull Requests",{"path":1822,"title":1823},"\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":1825,"title":1826},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Feditorial-checks-with-vale-in-ci","Editorial Checks with Vale in CI",{"path":1828,"title":1829},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":1831,"title":1832},"\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":1834,"title":1835},"\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":1837,"title":1838},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fbuilding-astro-sites-with-github-actions","Building Astro Sites with GitHub Actions",{"path":1840,"title":1841},"\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":1843,"title":1844},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fdeploying-to-github-pages-with-actions","Deploying to GitHub Pages with Actions",{"path":1846,"title":1847},"\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":1849,"title":1850},"\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":1852,"title":1853},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":1855,"title":1856},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fmatrix-builds-for-multi-site-monorepos","Matrix Builds for Multi-Site Monorepos",{"path":1858,"title":1859},"\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":1861,"title":1862},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":1864,"title":1865},"\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":1867,"title":1868},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":1870,"title":1871},"\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":1873,"title":1874},"\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":1876,"title":1877},"\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":1879,"title":1880},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":1882,"title":1883},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Falerting-on-cache-hit-ratio-drops","Alerting on Cache Hit Ratio Drops",{"path":1885,"title":1886},"\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":1888,"title":1889},"\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":1891,"title":1892},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production","Monitoring Static Sites in Production",{"path":1894,"title":1895},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Flogging-404s-at-the-edge","Logging 404s at the Edge",{"path":1897,"title":1898},"\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":1900,"title":1901},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fconfiguring-vercel-for-hugo-and-eleventy","Configuring Vercel for Hugo and Eleventy",{"path":1903,"title":1904},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":1906,"title":1907},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":1909,"title":1910},"\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":1912,"title":1913},"\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":1915,"title":1916},"\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":1918,"title":1919},"\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":1921,"title":1922},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":1924,"title":1925},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":1927,"title":1928},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpassword-protecting-preview-deployments","Password-Protecting Preview Deployments",{"path":1930,"title":1931},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpreviewing-headless-cms-drafts","Previewing Headless CMS Drafts",{"path":1933,"title":1934},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fvisual-regression-testing-on-preview-deploys","Visual Regression Testing on Preview Deploys",{"path":1936,"title":1937},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":1939,"title":1940},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fcanary-releases-for-static-sites","Canary Releases for Static Sites",{"path":1942,"title":1943},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Ffeature-flags-on-static-sites","Feature Flags on Static Sites",{"path":1945,"title":1946},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":1948,"title":1949},"\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":1951,"title":1243},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url",{"path":1953,"title":1954},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fauditing-npm-dependencies-in-ssg-pipelines","Auditing npm Dependencies in SSG Pipelines",{"path":1956,"title":1416},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fenabling-hsts-and-preload-safely",{"path":1958,"title":1959},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fhash-based-csp-for-inline-scripts-in-astro","Hash-Based CSP for Inline Scripts in Astro",{"path":1961,"title":442},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites",{"path":1963,"title":1964},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsecuring-deploy-credentials-with-github-oidc","Securing Deploy Credentials with GitHub OIDC",{"path":1966,"title":1967},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsubresource-integrity-for-third-party-assets","Subresource Integrity for Third-Party Assets",{"path":1969,"title":1970},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fwriting-a-content-security-policy-for-a-static-site","Writing a Content Security Policy for a Static Site",{"path":1972,"title":1973},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fclean-urls-and-trailing-slashes-on-s3","Clean URLs and Trailing Slashes on S3",{"path":1975,"title":1976},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fcloudfront-functions-for-redirects","CloudFront Functions for Redirects",{"path":1978,"title":1979},"\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":1981,"title":34},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites",{"path":1983,"title":1394},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-caddy",{"path":1460,"title":5},{"path":1986,"title":62},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fzero-downtime-deploys-with-symlink-swaps",{"path":1988,"title":1989},"\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":1991,"title":1992},"\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":1994,"title":1995},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites","Serverless Functions for Static Sites",{"path":1997,"title":1998},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fnetlify-functions-vs-cloudflare-workers","Netlify Functions vs Cloudflare Workers",{"path":2000,"title":2001},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fprotecting-a-static-site-behind-authentication","Protecting a Static Site Behind Authentication",{"path":2003,"title":2004},"\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",1789722847554]