[{"data":1,"prerenderedAt":1897},["ShallowReactive",2],{"page:\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fupgrading-jekyll-and-ruby-versions-safely":3,"all-docs-nav":1355},{"id":4,"title":5,"body":6,"breadcrumb":1330,"dateModified":1340,"datePublished":1340,"description":1341,"extension":1342,"faq":1343,"meta":1348,"navigation":1349,"path":1350,"seo":1351,"slug":12,"stem":1352,"type":1353,"__hash__":1354},"content\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fupgrading-jekyll-and-ruby-versions-safely\u002Findex.md","Upgrading Jekyll and Ruby Versions Safely",{"type":7,"value":8,"toc":1310},"minimark",[9,13,22,31,36,60,64,67,168,175,179,193,255,319,322,500,504,513,516,587,598,609,613,616,746,823,826,830,833,883,894,898,955,965,1058,1062,1096,1100,1185,1189,1225,1229,1232,1236,1241,1244,1248,1251,1255,1258,1262,1265,1269,1306],[10,11,5],"h1",{"id":12},"upgrading-jekyll-and-ruby-versions-safely",[14,15,16,17,21],"p",{},"Many Jekyll sites run on whatever versions they were created with: Jekyll 3.8, Ruby 2.7, a ",[18,19,20],"code",{},"Gemfile"," without a lockfile, plugins last updated years ago. They keep building until the day a CI image drops the old Ruby, a host's build environment updates, or a gem's native extension refuses to compile on a new operating system — and then they fail completely, usually right before a release.",[14,23,24,25,30],{},"Upgrading is not difficult, but it is easy to do badly: bumping everything at once, seeing the build succeed, and shipping pages whose Markdown now renders slightly differently. This guide upgrades a 1,400-page Jekyll 3.8 documentation site on Ruby 2.7 to Jekyll 4.3 on Ruby 3.3 in four small steps, diffing the built output after each so every change is deliberate. It is part of ",[26,27,29],"a",{"href":28},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002F","Jekyll Plugin Ecosystem",".",[32,33,35],"h2",{"id":34},"prerequisites","Prerequisites",[37,38,39,43,57],"ul",{},[40,41,42],"li",{},"The site in Git, building successfully on its current versions (capture that output first — it is the baseline).",[40,44,45,46,49,50,49,53,56],{},"A way to install multiple Ruby versions side by side: ",[18,47,48],{},"rbenv",", ",[18,51,52],{},"asdf",[18,54,55],{},"mise",", or Docker images.",[40,58,59],{},"Time for an afternoon of reviewing diffs; the mechanical steps take minutes.",[32,61,63],{"id":62},"step-0-capture-a-baseline","Step 0: Capture a Baseline",[14,65,66],{},"Build with the current versions and keep the output. Everything afterwards is compared against it.",[68,69,74],"pre",{"className":70,"code":71,"language":72,"meta":73,"style":73},"language-bash shiki shiki-themes github-light github-dark","ruby -v                       # ruby 2.7.8\nbundle exec jekyll -v         # jekyll 3.8.7\nbundle exec jekyll build -d \u002Ftmp\u002Fbaseline\ngit add Gemfile.lock 2>\u002Fdev\u002Fnull || bundle lock && git add Gemfile.lock\n","bash","",[18,75,76,93,111,129],{"__ignoreMap":73},[77,78,81,85,89],"span",{"class":79,"line":80},"line",1,[77,82,84],{"class":83},"sScJk","ruby",[77,86,88],{"class":87},"sj4cs"," -v",[77,90,92],{"class":91},"sJ8bj","                       # ruby 2.7.8\n",[77,94,96,99,103,106,108],{"class":79,"line":95},2,[77,97,98],{"class":83},"bundle",[77,100,102],{"class":101},"sZZnC"," exec",[77,104,105],{"class":101}," jekyll",[77,107,88],{"class":87},[77,109,110],{"class":91},"         # jekyll 3.8.7\n",[77,112,114,116,118,120,123,126],{"class":79,"line":113},3,[77,115,98],{"class":83},[77,117,102],{"class":101},[77,119,105],{"class":101},[77,121,122],{"class":101}," build",[77,124,125],{"class":87}," -d",[77,127,128],{"class":101}," \u002Ftmp\u002Fbaseline\n",[77,130,132,135,138,141,145,148,151,154,157,161,163,165],{"class":79,"line":131},4,[77,133,134],{"class":83},"git",[77,136,137],{"class":101}," add",[77,139,140],{"class":101}," Gemfile.lock",[77,142,144],{"class":143},"szBVR"," 2>",[77,146,147],{"class":101},"\u002Fdev\u002Fnull",[77,149,150],{"class":143}," ||",[77,152,153],{"class":83}," bundle",[77,155,156],{"class":101}," lock",[77,158,160],{"class":159},"sVt8B"," && ",[77,162,134],{"class":83},[77,164,137],{"class":101},[77,166,167],{"class":101}," Gemfile.lock\n",[14,169,170,171,174],{},"If there was no ",[18,172,173],{},"Gemfile.lock",", create and commit one now, pinned to what currently works. Also note the build time — the upgrade should improve it.",[32,176,178],{"id":177},"step-1-upgrade-ruby-keep-jekyll","Step 1: Upgrade Ruby, Keep Jekyll",[14,180,181,182,185,186,49,189,192],{},"Move to a supported Ruby while holding Jekyll and plugins at their current versions. Ruby 3.x removed some standard-library gems from the default set (",[18,183,184],{},"webrick"," for ",[18,187,188],{},"jekyll serve",[18,190,191],{},"kramdown-parser-gfm"," for GFM input in newer Kramdown), so the Gemfile may need them explicitly:",[68,194,197],{"className":195,"code":196,"language":84,"meta":73,"style":73},"language-ruby shiki shiki-themes github-light github-dark","# Gemfile (step 1)\nsource 'https:\u002F\u002Frubygems.org'\ngem 'jekyll', '3.9.5'            # 3.9 is the last 3.x, supports Ruby 3\ngem 'kramdown-parser-gfm'\ngem 'webrick', '~> 1.8'\ngroup :jekyll_plugins do\n  gem 'jekyll-feed', '0.15.1'\n  gem 'jekyll-seo-tag', '2.8.0'\n  gem 'jekyll-redirect-from', '0.16.0'\nend\n",[18,198,199,204,209,214,219,225,231,237,243,249],{"__ignoreMap":73},[77,200,201],{"class":79,"line":80},[77,202,203],{},"# Gemfile (step 1)\n",[77,205,206],{"class":79,"line":95},[77,207,208],{},"source 'https:\u002F\u002Frubygems.org'\n",[77,210,211],{"class":79,"line":113},[77,212,213],{},"gem 'jekyll', '3.9.5'            # 3.9 is the last 3.x, supports Ruby 3\n",[77,215,216],{"class":79,"line":131},[77,217,218],{},"gem 'kramdown-parser-gfm'\n",[77,220,222],{"class":79,"line":221},5,[77,223,224],{},"gem 'webrick', '~> 1.8'\n",[77,226,228],{"class":79,"line":227},6,[77,229,230],{},"group :jekyll_plugins do\n",[77,232,234],{"class":79,"line":233},7,[77,235,236],{},"  gem 'jekyll-feed', '0.15.1'\n",[77,238,240],{"class":79,"line":239},8,[77,241,242],{},"  gem 'jekyll-seo-tag', '2.8.0'\n",[77,244,246],{"class":79,"line":245},9,[77,247,248],{},"  gem 'jekyll-redirect-from', '0.16.0'\n",[77,250,252],{"class":79,"line":251},10,[77,253,254],{},"end\n",[68,256,258],{"className":70,"code":257,"language":72,"meta":73,"style":73},"echo '3.3.5' > .ruby-version\nbundle install && bundle exec jekyll build -d \u002Ftmp\u002Fstep1\ndiff -rq \u002Ftmp\u002Fbaseline \u002Ftmp\u002Fstep1 | wc -l\n",[18,259,260,274,296],{"__ignoreMap":73},[77,261,262,265,268,271],{"class":79,"line":80},[77,263,264],{"class":87},"echo",[77,266,267],{"class":101}," '3.3.5'",[77,269,270],{"class":143}," >",[77,272,273],{"class":101}," .ruby-version\n",[77,275,276,278,281,283,285,287,289,291,293],{"class":79,"line":95},[77,277,98],{"class":83},[77,279,280],{"class":101}," install",[77,282,160],{"class":159},[77,284,98],{"class":83},[77,286,102],{"class":101},[77,288,105],{"class":101},[77,290,122],{"class":101},[77,292,125],{"class":87},[77,294,295],{"class":101}," \u002Ftmp\u002Fstep1\n",[77,297,298,301,304,307,310,313,316],{"class":79,"line":113},[77,299,300],{"class":83},"diff",[77,302,303],{"class":87}," -rq",[77,305,306],{"class":101}," \u002Ftmp\u002Fbaseline",[77,308,309],{"class":101}," \u002Ftmp\u002Fstep1",[77,311,312],{"class":143}," |",[77,314,315],{"class":83}," wc",[77,317,318],{"class":87}," -l\n",[14,320,321],{},"The diff showed 3 changed files: the sitemap and feed timestamps, and one page where an old Liquid filter's output changed under Ruby 3's keyword-argument semantics. Each difference was read and accepted or fixed before moving on.",[323,324,325,496],"figure",{},[326,327,334,335,334,339,334,343,334,350,334,482],"svg",{"viewBox":328,"role":329,"ariaLabelledBy":330,"xmlns":333},"0 0 760 280","img",[331,332],"jup-steps-title","jup-steps-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[336,337,338],"title",{"id":331},"Four upgrade steps, each with a diff gate",[340,341,342],"desc",{"id":332},"A sequence from the baseline Jekyll 3.8 on Ruby 2.7 to Ruby 3.3 with Jekyll 3.9, then Jekyll 4.3, then updated plugins, then locked and automated. After each step the built output is diffed against the previous step and every change is reviewed before continuing.",[344,345],"rect",{"x":346,"y":346,"width":347,"height":348,"fill":349},"0","760","280","#ffffff",[351,352,354,355,354,363,354,373,354,379,354,385,354,390,354,394,354,397,354,401,354,405,354,408,354,414,354,418,354,421,354,428,354,432,354,435,354,454,354,458,354,461,354,464,354,466,354,473,354,478,334],"g",{"style":353},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[356,357,362],"text",{"x":358,"y":359,"fill":360,"style":361},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","Change one thing, diff, then change the next",[344,364],{"x":365,"y":366,"width":367,"height":368,"rx":369,"fill":370,"stroke":371,"style":372},"20","70","130","60","10","#f8fafc","#d9e2ef","stroke-width:1.5px",[356,374,378],{"x":375,"y":376,"fill":360,"style":377},"85","96","font-weight:700;text-anchor:middle","baseline",[356,380,384],{"x":375,"y":381,"fill":382,"style":383},"114","#556071","font-size:11px;text-anchor:middle","Jekyll 3.8 · Ruby 2.7",[344,386],{"x":387,"y":366,"width":367,"height":368,"rx":369,"fill":388,"opacity":389,"stroke":388,"style":372},"170","#1982c4","0.14",[356,391,393],{"x":392,"y":376,"fill":360,"style":377},"235","1 · Ruby",[356,395,396],{"x":392,"y":381,"fill":382,"style":383},"3.3 + Jekyll 3.9",[344,398],{"x":399,"y":366,"width":367,"height":368,"rx":369,"fill":400,"opacity":389,"stroke":400,"style":372},"320","#6a4c93",[356,402,404],{"x":403,"y":376,"fill":360,"style":377},"385","2 · Jekyll",[356,406,407],{"x":403,"y":381,"fill":382,"style":383},"4.3",[344,409],{"x":410,"y":366,"width":367,"height":368,"rx":369,"fill":411,"opacity":412,"stroke":413,"style":372},"470","#ffca3a","0.24","#a97b00",[356,415,417],{"x":416,"y":376,"fill":360,"style":377},"535","3 · plugins",[356,419,420],{"x":416,"y":381,"fill":382,"style":383},"current versions",[344,422],{"x":423,"y":366,"width":424,"height":368,"rx":369,"fill":425,"opacity":426,"stroke":427,"style":372},"620","120","#8ac926","0.18","#5a8a16",[356,429,431],{"x":430,"y":376,"fill":360,"style":377},"680","4 · lock",[356,433,434],{"x":430,"y":381,"fill":382,"style":383},"+ automate",[351,436,439,440,439,445,439,448,439,451,354],{"stroke":382,"fill":437,"style":438},"none","stroke-width:2px","\n      ",[441,442],"path",{"d":443,"style":444},"M152 100 L166 100","marker-end:url(#jup-arrow)",[441,446],{"d":447,"style":444},"M302 100 L316 100",[441,449],{"d":450,"style":444},"M452 100 L466 100",[441,452],{"d":453,"style":444},"M602 100 L616 100",[356,455,457],{"x":392,"y":456,"fill":360,"style":383},"160","3 files differ",[356,459,460],{"x":403,"y":456,"fill":360,"style":383},"214 files differ",[356,462,463],{"x":416,"y":456,"fill":360,"style":383},"41 files differ",[356,465,346],{"x":430,"y":456,"fill":360,"style":383},[344,467],{"x":387,"y":468,"width":469,"height":470,"rx":471,"fill":370,"stroke":371,"style":472},"186","430","34","8","stroke-width:1px",[356,474,477],{"x":403,"y":475,"fill":360,"style":476},"208","text-anchor:middle","diff -r previous-step current-step → review every file",[356,479,481],{"x":358,"y":480,"fill":382,"style":383},"252","One cause per diff means every changed page can be explained",[483,484,354,485,334],"defs",{},[486,487,439,493,354],"marker",{"id":488,"viewBox":489,"refX":471,"refY":490,"markerWidth":491,"markerHeight":491,"orient":492},"jup-arrow","0 0 10 10","5","7","auto-start-reverse",[441,494],{"d":495,"fill":382},"M0 0 L10 5 L0 10 z",[497,498,499],"figcaption",{},"Doing all three upgrades at once would have produced one diff of about 250 files with three mixed causes.",[32,501,503],{"id":502},"step-2-upgrade-jekyll-to-4x","Step 2: Upgrade Jekyll to 4.x",[68,505,507],{"className":195,"code":506,"language":84,"meta":73,"style":73},"gem 'jekyll', '~> 4.3'\n",[18,508,509],{"__ignoreMap":73},[77,510,511],{"class":79,"line":80},[77,512,506],{},[14,514,515],{},"This step produced the largest diff — 214 files — and almost all of it had one cause: Jekyll 4 uses a newer Kramdown whose default settings differ slightly, notably in automatic header IDs for headings containing punctuation and in smart quotes inside code spans. Rather than accept silently changed anchor IDs (which break deep links from other sites), the site pinned the old behaviour:",[68,517,521],{"className":518,"code":519,"language":520,"meta":73,"style":73},"language-yaml shiki shiki-themes github-light github-dark","# _config.yml\nkramdown:\n  input: GFM\n  auto_ids: true\n  auto_id_stripping: true\n  hard_wrap: false\n  syntax_highlighter: rouge\n","yaml",[18,522,523,528,537,548,558,567,577],{"__ignoreMap":73},[77,524,525],{"class":79,"line":80},[77,526,527],{"class":91},"# _config.yml\n",[77,529,530,534],{"class":79,"line":95},[77,531,533],{"class":532},"s9eBZ","kramdown",[77,535,536],{"class":159},":\n",[77,538,539,542,545],{"class":79,"line":113},[77,540,541],{"class":532},"  input",[77,543,544],{"class":159},": ",[77,546,547],{"class":101},"GFM\n",[77,549,550,553,555],{"class":79,"line":131},[77,551,552],{"class":532},"  auto_ids",[77,554,544],{"class":159},[77,556,557],{"class":87},"true\n",[77,559,560,563,565],{"class":79,"line":221},[77,561,562],{"class":532},"  auto_id_stripping",[77,564,544],{"class":159},[77,566,557],{"class":87},[77,568,569,572,574],{"class":79,"line":227},[77,570,571],{"class":532},"  hard_wrap",[77,573,544],{"class":159},[77,575,576],{"class":87},"false\n",[77,578,579,582,584],{"class":79,"line":233},[77,580,581],{"class":532},"  syntax_highlighter",[77,583,544],{"class":159},[77,585,586],{"class":101},"rouge\n",[14,588,589,590,593,594,597],{},"With those options the diff fell to 17 files, each a genuine improvement (correctly escaped HTML in two tables, fixed smart quotes) and accepted. Jekyll 4 also no longer bundles some plugins by default, and ",[18,591,592],{},"site.related_posts"," behaviour and Liquid's handling of ",[18,595,596],{},"nil"," in comparisons changed in minor ways; the diff surfaces each.",[14,599,600,601,604,605,30],{},"Build time dropped from 118 s to 52 s at this step, thanks to Jekyll 4's render cache in ",[18,602,603],{},".jekyll-cache\u002F",", the improvement that also underpins ",[26,606,608],{"href":607},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fspeeding-up-slow-jekyll-builds\u002F","Speeding Up Slow Jekyll Builds",[32,610,612],{"id":611},"reviewing-a-large-diff-efficiently","Reviewing a Large Diff Efficiently",[14,614,615],{},"Two hundred changed files sounds like a day of reading, but diffs from a version upgrade are highly repetitive: the same change applied to many pages. Group them before reading any. Strip the parts that are expected to differ (build timestamps, cache-busting hashes), then normalise whitespace and hash each file's remaining diff; identical diff hashes are the same change on different pages.",[68,617,619],{"className":70,"code":618,"language":72,"meta":73,"style":73},"for f in $(diff -rq \u002Ftmp\u002Fstep1 \u002Ftmp\u002Fstep2 | awk '{print $2}' | sed 's#\u002Ftmp\u002Fstep1\u002F##'); do\n  diff \u003C(sed -E 's\u002F[0-9]{4}-[0-9]{2}-[0-9]{2}T[^\"\u003C]+\u002F\u002Fg' \u002Ftmp\u002Fstep1\u002F$f) \\\n       \u003C(sed -E 's\u002F[0-9]{4}-[0-9]{2}-[0-9]{2}T[^\"\u003C]+\u002F\u002Fg' \u002Ftmp\u002Fstep2\u002F$f) | sha1sum | cut -c1-8\ndone | sort | uniq -c | sort -rn\n",[18,620,621,666,692,721],{"__ignoreMap":73},[77,622,623,626,629,632,635,637,639,641,644,646,649,652,654,657,660,663],{"class":79,"line":80},[77,624,625],{"class":143},"for",[77,627,628],{"class":159}," f ",[77,630,631],{"class":143},"in",[77,633,634],{"class":159}," $(",[77,636,300],{"class":83},[77,638,303],{"class":87},[77,640,309],{"class":101},[77,642,643],{"class":101}," \u002Ftmp\u002Fstep2",[77,645,312],{"class":143},[77,647,648],{"class":83}," awk",[77,650,651],{"class":101}," '{print $2}'",[77,653,312],{"class":143},[77,655,656],{"class":83}," sed",[77,658,659],{"class":101}," 's#\u002Ftmp\u002Fstep1\u002F##'",[77,661,662],{"class":159},"); ",[77,664,665],{"class":143},"do\n",[77,667,668,671,674,677,680,683,686,689],{"class":79,"line":95},[77,669,670],{"class":83},"  diff",[77,672,673],{"class":101}," \u003C(",[77,675,676],{"class":83},"sed",[77,678,679],{"class":87}," -E",[77,681,682],{"class":101}," 's\u002F[0-9]{4}-[0-9]{2}-[0-9]{2}T[^\"\u003C]+\u002F\u002Fg' \u002Ftmp\u002Fstep1\u002F",[77,684,685],{"class":159},"$f",[77,687,688],{"class":101},")",[77,690,691],{"class":87}," \\\n",[77,693,694,697,699,701,704,706,708,710,713,715,718],{"class":79,"line":113},[77,695,696],{"class":101},"       \u003C(",[77,698,676],{"class":83},[77,700,679],{"class":87},[77,702,703],{"class":101}," 's\u002F[0-9]{4}-[0-9]{2}-[0-9]{2}T[^\"\u003C]+\u002F\u002Fg' \u002Ftmp\u002Fstep2\u002F",[77,705,685],{"class":159},[77,707,688],{"class":101},[77,709,312],{"class":143},[77,711,712],{"class":83}," sha1sum",[77,714,312],{"class":143},[77,716,717],{"class":83}," cut",[77,719,720],{"class":87}," -c1-8\n",[77,722,723,726,728,731,733,736,739,741,743],{"class":79,"line":131},[77,724,725],{"class":143},"done",[77,727,312],{"class":143},[77,729,730],{"class":83}," sort",[77,732,312],{"class":143},[77,734,735],{"class":83}," uniq",[77,737,738],{"class":87}," -c",[77,740,312],{"class":143},[77,742,730],{"class":83},[77,744,745],{"class":87}," -rn\n",[323,747,748,820],{},[326,749,334,754,334,757,334,760,334,763],{"viewBox":750,"role":329,"ariaLabelledBy":751,"xmlns":333},"0 0 760 250",[752,753],"jup-diff-title","jup-diff-desc",[336,755,756],{"id":752},"Grouping the Jekyll 4 diff by distinct change",[340,758,759],{"id":753},"214 changed files collapse into 6 distinct change patterns. 162 files share a heading ID change, 28 share a smart-quote change in code spans, 11 share a table escaping fix, 8 share a footnote markup change, 3 a feed timestamp, and 2 are unique changes needing individual review.",[344,761],{"x":346,"y":346,"width":347,"height":762,"fill":349},"250",[351,764,354,765,354,768,354,776,354,781,354,786,354,790,354,794,354,798,354,802,354,807,354,812,354,816,334],{"style":353},[356,766,767],{"x":358,"y":359,"fill":360,"style":361},"214 changed files, 6 distinct changes",[344,769],{"x":770,"y":366,"width":771,"height":772,"fill":773,"opacity":774,"stroke":775,"style":472},"40","455","44","#ff595e","0.25","#d83b41",[356,777,780],{"x":778,"y":779,"fill":360,"style":476},"267","97","heading IDs · 162",[344,782],{"x":783,"y":366,"width":784,"height":772,"fill":411,"opacity":785,"stroke":413,"style":472},"495","79","0.4",[356,787,789],{"x":788,"y":779,"fill":360,"style":383},"534","quotes 28",[344,791],{"x":792,"y":366,"width":793,"height":772,"fill":388,"opacity":774,"stroke":388,"style":472},"574","31",[344,795],{"x":796,"y":366,"width":797,"height":772,"fill":400,"opacity":774,"stroke":400,"style":472},"605","22",[344,799],{"x":800,"y":366,"width":801,"height":772,"fill":370,"stroke":382,"style":472},"627","9",[344,803],{"x":804,"y":366,"width":805,"height":772,"fill":425,"opacity":806,"stroke":427,"style":472},"636","6","0.5",[356,808,811],{"x":809,"y":810,"fill":382,"style":383},"590","138","tables 11 · footnotes 8 · feed 3 · unique 2",[356,813,815],{"x":358,"y":814,"fill":360,"style":476},"188","Read one example of each pattern, then the 2 unique files: 8 files instead of 214",[356,817,819],{"x":358,"y":818,"fill":382,"style":383},"226","Diffs hashed after stripping timestamps; identical hashes = identical change",[497,821,822],{},"Grouping turned a day of reading into twenty minutes, and made the heading-ID pattern impossible to miss.",[14,824,825],{},"Reading one example of each group plus the unique files took about twenty minutes. It is also how the heading-ID change was spotted as the dominant pattern rather than scattered noise, which led directly to pinning the Kramdown options instead of accepting 162 pages of changed anchors.",[32,827,829],{"id":828},"step-3-upgrade-plugins","Step 3: Upgrade Plugins",[14,831,832],{},"Update each plugin to its current release, one at a time for any that changes output:",[68,834,836],{"className":70,"code":835,"language":72,"meta":73,"style":73},"bundle update jekyll-feed jekyll-seo-tag jekyll-redirect-from --conservative\nbundle exec jekyll build -d \u002Ftmp\u002Fstep3 && diff -rq \u002Ftmp\u002Fstep2 \u002Ftmp\u002Fstep3\n",[18,837,838,857],{"__ignoreMap":73},[77,839,840,842,845,848,851,854],{"class":79,"line":80},[77,841,98],{"class":83},[77,843,844],{"class":101}," update",[77,846,847],{"class":101}," jekyll-feed",[77,849,850],{"class":101}," jekyll-seo-tag",[77,852,853],{"class":101}," jekyll-redirect-from",[77,855,856],{"class":87}," --conservative\n",[77,858,859,861,863,865,867,869,872,874,876,878,880],{"class":79,"line":95},[77,860,98],{"class":83},[77,862,102],{"class":101},[77,864,105],{"class":101},[77,866,122],{"class":101},[77,868,125],{"class":87},[77,870,871],{"class":101}," \u002Ftmp\u002Fstep3",[77,873,160],{"class":159},[77,875,300],{"class":83},[77,877,303],{"class":87},[77,879,643],{"class":101},[77,881,882],{"class":101}," \u002Ftmp\u002Fstep3\n",[14,884,885,888,889,893],{},[18,886,887],{},"jekyll-seo-tag","'s newer JSON-LD output changed 41 files — structured data only, validated with a schema checker before acceptance. One unmaintained plugin (a custom category generator last updated in 2017) failed on Jekyll 4's internals and was replaced with a 20-line hook, the approach described in ",[26,890,892],{"href":891},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Freplacing-jekyll-plugins-when-migrating-to-eleventy\u002F","Replacing Jekyll Plugins When Migrating to Eleventy"," for a different destination.",[32,895,897],{"id":896},"step-4-lock-and-automate","Step 4: Lock and Automate",[68,899,901],{"className":518,"code":900,"language":520,"meta":73,"style":73},"# .github\u002Fworkflows\u002Fbuild.yml (excerpt)\n- uses: ruby\u002Fsetup-ruby@v1\n  with: { bundler-cache: true }          # reads .ruby-version, caches gems by Gemfile.lock\n- run: bundle exec jekyll build\n",[18,902,903,908,921,943],{"__ignoreMap":73},[77,904,905],{"class":79,"line":80},[77,906,907],{"class":91},"# .github\u002Fworkflows\u002Fbuild.yml (excerpt)\n",[77,909,910,913,916,918],{"class":79,"line":95},[77,911,912],{"class":159},"- ",[77,914,915],{"class":532},"uses",[77,917,544],{"class":159},[77,919,920],{"class":101},"ruby\u002Fsetup-ruby@v1\n",[77,922,923,926,929,932,934,937,940],{"class":79,"line":113},[77,924,925],{"class":532},"  with",[77,927,928],{"class":159},": { ",[77,930,931],{"class":532},"bundler-cache",[77,933,544],{"class":159},[77,935,936],{"class":87},"true",[77,938,939],{"class":159}," }          ",[77,941,942],{"class":91},"# reads .ruby-version, caches gems by Gemfile.lock\n",[77,944,945,947,950,952],{"class":79,"line":131},[77,946,912],{"class":159},[77,948,949],{"class":532},"run",[77,951,544],{"class":159},[77,953,954],{"class":101},"bundle exec jekyll build\n",[14,956,957,960,961,964],{},[18,958,959],{},"ruby\u002Fsetup-ruby"," reads ",[18,962,963],{},".ruby-version",", so local development and CI use the same Ruby. A dependency bot opens pull requests for gem updates; a CI job builds both the base branch and the pull request and posts the count of changed files, so a \"patch\" update that changes 300 pages gets noticed before merge.",[323,966,967,1055],{},[326,968,334,972,334,975,334,978,334,980],{"viewBox":750,"role":329,"ariaLabelledBy":969,"xmlns":333},[970,971],"jup-time-title","jup-time-desc",[336,973,974],{"id":970},"Build time across the upgrade",[340,976,977],{"id":971},"Bars of build time: 118 seconds on the baseline, 109 after upgrading Ruby, 52 after Jekyll 4, 51 after plugin updates, and 34 on a warm build with the Jekyll cache restored in CI.",[344,979],{"x":346,"y":346,"width":347,"height":762,"fill":349},[351,981,354,982,354,985,354,989,354,994,354,999,354,1005,354,1010,354,1015,354,1019,354,1024,354,1029,354,1031,354,1036,354,1039,354,1042,354,1045,354,1048,354,1051,334],{"style":353},[356,983,984],{"x":358,"y":359,"fill":360,"style":361},"Build seconds at each step (1,400 pages)",[79,986],{"x1":368,"y1":987,"x2":988,"y2":987,"stroke":382,"style":372},"200","720",[344,990],{"x":991,"y":368,"width":991,"height":992,"fill":773,"opacity":993,"stroke":775,"style":472},"90","140","0.28",[356,995,998],{"x":996,"y":997,"fill":360,"style":377},"135","54","118",[344,1000],{"x":1001,"y":1002,"width":991,"height":1003,"fill":773,"opacity":1004,"stroke":775,"style":472},"215","71","129","0.2",[356,1006,1009],{"x":1007,"y":1008,"fill":360,"style":377},"260","65","109",[344,1011],{"x":1012,"y":810,"width":991,"height":1013,"fill":425,"opacity":1014,"stroke":427,"style":472},"340","62","0.35",[356,1016,1018],{"x":403,"y":1017,"fill":360,"style":377},"132","52",[344,1020],{"x":1021,"y":1022,"width":991,"height":1023,"fill":425,"opacity":1014,"stroke":427,"style":472},"465","139","61",[356,1025,1028],{"x":1026,"y":1027,"fill":360,"style":377},"510","133","51",[344,1030],{"x":809,"y":456,"width":991,"height":770,"fill":425,"opacity":806,"stroke":427,"style":472},[356,1032,470],{"x":1033,"y":1034,"fill":1035,"style":377},"635","154","#3f6410",[356,1037,378],{"x":996,"y":1038,"fill":382,"style":383},"218",[356,1040,1041],{"x":1007,"y":1038,"fill":382,"style":383},"Ruby 3.3",[356,1043,1044],{"x":403,"y":1038,"fill":382,"style":383},"Jekyll 4.3",[356,1046,1047],{"x":1026,"y":1038,"fill":382,"style":383},"plugins",[356,1049,1050],{"x":1033,"y":1038,"fill":382,"style":383},"warm cache",[356,1052,1054],{"x":358,"y":1053,"fill":382,"style":383},"242","GitHub Actions ubuntu-latest, median of 3",[497,1056,1057],{},"The upgrade that was justified by security also more than halved build time.",[32,1059,1061],{"id":1060},"native-extensions-and-containers","Native Extensions and Containers",[14,1063,1064,1065,49,1068,49,1071,49,1074,1077,1078,1081,1082,1084,1085,1087,1088,1091,1092,1095],{},"The step most likely to fail outright is not Jekyll at all but gems with native extensions — ",[18,1066,1067],{},"nokogiri",[18,1069,1070],{},"eventmachine",[18,1072,1073],{},"ffi",[18,1075,1076],{},"sassc"," — compiled against the system's C libraries. On a new Ruby or a new operating system image they may need newer versions, or precompiled platform gems. Two measures avoid surprises. Add every platform you build on to the lockfile (",[18,1079,1080],{},"bundle lock --add-platform x86_64-linux aarch64-linux arm64-darwin","), so Bundler can pick precompiled gems instead of compiling. And build in a pinned container image or ",[18,1083,959],{}," with an exact Ruby version rather than whatever the runner provides, so an image update cannot change the compiler underneath you. On this site, replacing the long-deprecated ",[18,1086,1076],{}," with ",[18,1089,1090],{},"sass-embedded"," (which Jekyll 4.3's Sass converter supports) removed the only gem that needed a C compiler at all, and cut ",[18,1093,1094],{},"bundle install"," on a cold CI cache from 3 minutes to 40 seconds.",[32,1097,1099],{"id":1098},"measured-impact","Measured Impact",[1101,1102,1103,1119],"table",{},[1104,1105,1106],"thead",{},[1107,1108,1109,1113,1116],"tr",{},[1110,1111,1112],"th",{},"Measure",[1110,1114,1115],{},"Before",[1110,1117,1118],{},"After",[1120,1121,1122,1134,1145,1156,1166,1176],"tbody",{},[1107,1123,1124,1128,1131],{},[1125,1126,1127],"td",{},"Ruby",[1125,1129,1130],{},"2.7.8 (end of life)",[1125,1132,1133],{},"3.3.5",[1107,1135,1136,1139,1142],{},[1125,1137,1138],{},"Jekyll",[1125,1140,1141],{},"3.8.7",[1125,1143,1144],{},"4.3.4",[1107,1146,1147,1150,1153],{},[1125,1148,1149],{},"Build time (cold \u002F warm)",[1125,1151,1152],{},"118 s \u002F 118 s",[1125,1154,1155],{},"51 s \u002F 34 s",[1107,1157,1158,1161,1164],{},[1125,1159,1160],{},"Pages with changed output, unexplained",[1125,1162,1163],{},"—",[1125,1165,346],{},[1107,1167,1168,1171,1173],{},[1125,1169,1170],{},"Broken deep-link anchors",[1125,1172,1163],{},[1125,1174,1175],{},"0 (auto_id options pinned)",[1107,1177,1178,1181,1183],{},[1125,1179,1180],{},"Known-vulnerable gems",[1125,1182,801],{},[1125,1184,346],{},[32,1186,1188],{"id":1187},"pitfalls-rollback","Pitfalls & Rollback",[37,1190,1191,1198,1204,1213,1219],{},[40,1192,1193,1197],{},[1194,1195,1196],"strong",{},"Upgrading everything at once."," Mixed causes make the diff unreviewable. One change per step.",[40,1199,1200,1203],{},[1194,1201,1202],{},"Accepting changed heading IDs."," They break inbound anchor links; pin Kramdown options or add redirects for anchors that must change.",[40,1205,1206,1209,1210,1212],{},[1194,1207,1208],{},"No lockfile."," Without ",[18,1211,173],{},", CI and local builds resolve different versions.",[40,1214,1215,1218],{},[1194,1216,1217],{},"Forgetting the host's build."," If GitHub Pages builds the site, its allowed Jekyll version governs; build in Actions to choose your own.",[40,1220,1221,1224],{},[1194,1222,1223],{},"Rollback:"," each step is one commit; reverting the last commit restores the previous working combination.",[32,1226,1228],{"id":1227},"conclusion","Conclusion",[14,1230,1231],{},"A safe Jekyll upgrade is a sequence of small, diffed steps: capture a baseline, move Ruby with Jekyll held back, move Jekyll with Kramdown options pinned, update plugins one by one, then lock versions and automate future updates. On a 1,400-page site that took an afternoon, left no unexplained change in any page, removed nine vulnerable gems and cut the build from 118 to 34 seconds.",[32,1233,1235],{"id":1234},"faq","FAQ",[1237,1238,1240],"h3",{"id":1239},"why-upgrade-an-old-jekyll-site-that-still-builds","Why upgrade an old Jekyll site that still builds?",[14,1242,1243],{},"Old Ruby versions stop receiving security fixes and eventually disappear from CI images and hosting build environments, at which point the site stops building with little warning. Jekyll 4 is also considerably faster thanks to its render cache.",[1237,1245,1247],{"id":1246},"what-breaks-when-moving-from-jekyll-3-to-jekyll-4","What breaks when moving from Jekyll 3 to Jekyll 4?",[14,1249,1250],{},"Mostly plugins that relied on internal APIs, a few Liquid behaviour changes, the removal of some default gems from core, and Kramdown option differences. Page output usually changes in small ways, which a diff of the built site reveals.",[1237,1252,1254],{"id":1253},"should-i-upgrade-ruby-and-jekyll-at-the-same-time","Should I upgrade Ruby and Jekyll at the same time?",[14,1256,1257],{},"No. Upgrade one thing per step and diff the output after each, so any change in the site can be traced to a single cause. Ruby first on the old Jekyll if possible, then Jekyll, then plugins.",[1237,1259,1261],{"id":1260},"how-do-i-keep-versions-from-drifting-again","How do I keep versions from drifting again?",[14,1263,1264],{},"Commit Gemfile.lock, pin Ruby with a .ruby-version file used by both local tools and CI, and let a dependency bot open small update pull requests with a diff-checking CI job.",[32,1266,1268],{"id":1267},"related","Related",[37,1270,1271,1280,1285,1292,1299],{},[40,1272,1273,1276,1277,1279],{},[1194,1274,1275],{},"Parent:"," ",[26,1278,29],{"href":28}," — the plugins you are upgrading.",[40,1281,1282,1284],{},[26,1283,608],{"href":607}," — what to do with the time Jekyll 4 saves.",[40,1286,1287,1291],{},[26,1288,1290],{"href":1289},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Frunning-jekyll-on-github-pages-without-plugins\u002F","Running Jekyll on GitHub Pages Without Plugins"," — version constraints on GitHub Pages.",[40,1293,1294,1298],{},[26,1295,1297],{"href":1296},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fauditing-npm-dependencies-in-ssg-pipelines\u002F","Auditing npm Dependencies in SSG Pipelines"," — the same discipline for JavaScript dependencies.",[40,1300,1301,1305],{},[26,1302,1304],{"href":1303},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-a-docs-site-from-jekyll-to-hugo\u002F","Migrating a Docs Site from Jekyll to Hugo"," — if upgrading reveals it is time to move.",[1307,1308,1309],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":73,"searchDepth":95,"depth":95,"links":1311},[1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1329],{"id":34,"depth":95,"text":35},{"id":62,"depth":95,"text":63},{"id":177,"depth":95,"text":178},{"id":502,"depth":95,"text":503},{"id":611,"depth":95,"text":612},{"id":828,"depth":95,"text":829},{"id":896,"depth":95,"text":897},{"id":1060,"depth":95,"text":1061},{"id":1098,"depth":95,"text":1099},{"id":1187,"depth":95,"text":1188},{"id":1227,"depth":95,"text":1228},{"id":1234,"depth":95,"text":1235,"children":1324},[1325,1326,1327,1328],{"id":1239,"depth":113,"text":1240},{"id":1246,"depth":113,"text":1247},{"id":1253,"depth":113,"text":1254},{"id":1260,"depth":113,"text":1261},{"id":1267,"depth":95,"text":1268},[1331,1334,1337,1338],{"name":1332,"item":1333},"Home","\u002F",{"name":1335,"item":1336},"Choosing the Right Static Site Generator for Production","\u002Fchoosing-the-right-static-site-generator-for-production\u002F",{"name":29,"item":28},{"name":5,"item":1339},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fupgrading-jekyll-and-ruby-versions-safely\u002F","2026-09-18","Move an old Jekyll 3 site to Jekyll 4 and a supported Ruby without breaking pages: pin versions, upgrade in steps, diff the built output and fix what changes.","md",[1344,1345,1346,1347],{"q":1240,"a":1243},{"q":1247,"a":1250},{"q":1254,"a":1257},{"q":1261,"a":1264},{},true,"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fupgrading-jekyll-and-ruby-versions-safely",{"title":5,"description":1341},"choosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fupgrading-jekyll-and-ruby-versions-safely\u002Findex","article","3gds7XQz0vsWAYSCT-JXbYte20HfNEbyrQIeQhKA2Xw",[1356,1359,1362,1365,1368,1371,1374,1377,1380,1383,1386,1389,1392,1395,1398,1401,1404,1407,1410,1413,1415,1418,1420,1422,1424,1426,1427,1430,1433,1436,1439,1442,1445,1448,1451,1454,1457,1460,1463,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,1553,1556,1559,1562,1565,1568,1571,1574,1577,1580,1583,1586,1589,1592,1595,1598,1601,1604,1607,1610,1613,1616,1619,1622,1625,1628,1631,1634,1637,1640,1643,1646,1649,1652,1655,1658,1661,1664,1667,1670,1673,1676,1679,1682,1685,1688,1691,1694,1697,1700,1703,1706,1709,1712,1715,1718,1721,1724,1727,1730,1733,1736,1739,1742,1745,1748,1751,1754,1757,1760,1763,1766,1769,1772,1775,1778,1781,1784,1787,1790,1793,1796,1799,1802,1805,1808,1811,1814,1817,1820,1823,1826,1829,1832,1835,1838,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894],{"path":1357,"title":1358},"\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":1360,"title":1361},"\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":1363,"title":1364},"\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":1366,"title":1367},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1369,"title":1370},"\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":1372,"title":1373},"\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":1375,"title":1376},"\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":1378,"title":1379},"\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":1381,"title":1382},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress","Docs Frameworks: Docusaurus, Starlight and VitePress",{"path":1384,"title":1385},"\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":1387,"title":1388},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fmigrating-from-mkdocs-to-starlight","Migrating from MkDocs to Starlight",{"path":1390,"title":1391},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fversioned-documentation-with-docusaurus","Versioned Documentation with Docusaurus",{"path":1393,"title":1394},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fdocs-frameworks-docusaurus-starlight-vitepress\u002Fvitepress-for-library-documentation","VitePress for Library Documentation",{"path":1396,"title":1397},"\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":1399,"title":1400},"\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":1402,"title":1403},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1405,"title":1406},"\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":1408,"title":1409},"\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":1411,"title":1412},"\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":1414,"title":1335},"\u002Fchoosing-the-right-static-site-generator-for-production",{"path":1416,"title":1417},"\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":1419,"title":29},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem",{"path":1421,"title":892},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Freplacing-jekyll-plugins-when-migrating-to-eleventy",{"path":1423,"title":1290},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Frunning-jekyll-on-github-pages-without-plugins",{"path":1425,"title":608},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Fspeeding-up-slow-jekyll-builds",{"path":1350,"title":5},{"path":1428,"title":1429},"\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":1431,"title":1432},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":1434,"title":1435},"\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":1437,"title":1438},"\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":1440,"title":1441},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-gatsby-to-astro","Migrating from Gatsby to Astro",{"path":1443,"title":1444},"\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":1446,"title":1447},"\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":1449,"title":1450},"\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":1452,"title":1453},"\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":1455,"title":1456},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1458,"title":1459},"\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":1461,"title":1462},"\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":1464,"title":1465},"\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":1467,"title":1468},"\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":1470,"title":1471},"\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":1473,"title":1474},"\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":1476,"title":1477},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites","Search for Static Sites",{"path":1479,"title":1480},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Findexing-hugo-sites-with-pagefind","Indexing Hugo Sites with Pagefind",{"path":1482,"title":1483},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fmultilingual-search-on-static-sites","Multilingual Search on Static Sites",{"path":1485,"title":1486},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fsearch-for-static-sites\u002Fpagefind-vs-algolia-docsearch","Pagefind vs Algolia DocSearch",{"path":1488,"title":1489},"\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":1491,"title":1492},"\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":1494,"title":1495},"\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":1497,"title":1498},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fevaluating-ssg-accessibility-defaults","Evaluating SSG Accessibility Defaults",{"path":1500,"title":1501},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":1503,"title":1504},"\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":1506,"title":1507},"\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":1509,"title":1510},"\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":1512,"title":1513},"\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":1515,"title":1516},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":1518,"title":1519},"\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":1521,"title":1522},"\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":1524,"title":1525},"\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":1527,"title":1528},"\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":1530,"title":1531},"\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":1533,"title":1534},"\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":1536,"title":1537},"\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":1539,"title":1540},"\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":1542,"title":1543},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":1545,"title":1546},"\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":1548,"title":1549},"\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":1551,"title":1552},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Ffont-display-optional-vs-swap","font-display: optional vs swap",{"path":1554,"title":1555},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":1557,"title":1558},"\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":1560,"title":1561},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites\u002Fpreloading-fonts-without-double-downloads","Preloading Fonts Without Double Downloads",{"path":1563,"title":1564},"\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":1566,"title":1567},"\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":1569,"title":1570},"\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":1572,"title":1573},"\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":1575,"title":1576},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":1578,"title":1579},"\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":1581,"title":1582},"\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":1584,"title":1585},"\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":1587,"title":1588},"\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":1590,"title":1591},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":1593,"title":1594},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":1596,"title":1597},"\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":1599,"title":1600},"\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":1602,"title":1603},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":1605,"title":1606},"\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":1608,"title":1609},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Freplacing-react-islands-with-web-components","Replacing React Islands with Web Components",{"path":1611,"title":1612},"\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":1614,"title":1615},"\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":1617,"title":1618},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":1620,"title":1621},"\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":1623,"title":1624},"\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":1626,"title":1627},"\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":1629,"title":1630},"\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":1632,"title":1633},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fperformance-budgets-and-lighthouse-ci","Performance Budgets and Lighthouse CI",{"path":1635,"title":1636},"\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":1638,"title":1639},"\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":1641,"title":1642},"\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":1644,"title":1645},"\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":1647,"title":1648},"\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":1650,"title":1651},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fauditing-unused-preloads","Auditing Unused Preloads",{"path":1653,"title":1654},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed","Resource Hints and Navigation Speed",{"path":1656,"title":1657},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Finstant-navigation-with-speculation-rules","Instant Navigation with Speculation Rules",{"path":1659,"title":1660},"\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":1662,"title":1663},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fresource-hints-and-navigation-speed\u002Fprefetching-links-in-astro","Prefetching Links in Astro",{"path":1665,"title":1666},"\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":1668,"title":1669},"\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":1671,"title":1672},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":1674,"title":1675},"\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":1677,"title":1678},"\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":1680,"title":1681},"\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":1683,"title":1684},"\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":1686,"title":1687},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":1689,"title":1690},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fconfiguring-redirects-on-cloudflare-pages","Configuring Redirects on Cloudflare Pages",{"path":1692,"title":1693},"\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":1695,"title":1696},"\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":1698,"title":1699},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":1701,"title":1702},"\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":1704,"title":1705},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fchecking-links-in-pull-requests","Checking Links in Pull Requests",{"path":1707,"title":1708},"\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":1710,"title":1711},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Feditorial-checks-with-vale-in-ci","Editorial Checks with Vale in CI",{"path":1713,"title":1714},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":1716,"title":1717},"\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":1719,"title":1720},"\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":1722,"title":1723},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fbuilding-astro-sites-with-github-actions","Building Astro Sites with GitHub Actions",{"path":1725,"title":1726},"\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":1728,"title":1729},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fdeploying-to-github-pages-with-actions","Deploying to GitHub Pages with Actions",{"path":1731,"title":1732},"\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":1734,"title":1735},"\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":1737,"title":1738},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":1740,"title":1741},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds\u002Fmatrix-builds-for-multi-site-monorepos","Matrix Builds for Multi-Site Monorepos",{"path":1743,"title":1744},"\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":1746,"title":1747},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":1749,"title":1750},"\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":1752,"title":1753},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":1755,"title":1756},"\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":1758,"title":1759},"\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":1761,"title":1762},"\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":1764,"title":1765},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":1767,"title":1768},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Falerting-on-cache-hit-ratio-drops","Alerting on Cache Hit Ratio Drops",{"path":1770,"title":1771},"\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":1773,"title":1774},"\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":1776,"title":1777},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production","Monitoring Static Sites in Production",{"path":1779,"title":1780},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fmonitoring-static-sites-in-production\u002Flogging-404s-at-the-edge","Logging 404s at the Edge",{"path":1782,"title":1783},"\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":1785,"title":1786},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fconfiguring-vercel-for-hugo-and-eleventy","Configuring Vercel for Hugo and Eleventy",{"path":1788,"title":1789},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":1791,"title":1792},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":1794,"title":1795},"\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":1797,"title":1798},"\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":1800,"title":1801},"\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":1803,"title":1804},"\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":1806,"title":1807},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":1809,"title":1810},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":1812,"title":1813},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpassword-protecting-preview-deployments","Password-Protecting Preview Deployments",{"path":1815,"title":1816},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fpreviewing-headless-cms-drafts","Previewing Headless CMS Drafts",{"path":1818,"title":1819},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fvisual-regression-testing-on-preview-deploys","Visual Regression Testing on Preview Deploys",{"path":1821,"title":1822},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":1824,"title":1825},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fcanary-releases-for-static-sites","Canary Releases for Static Sites",{"path":1827,"title":1828},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Ffeature-flags-on-static-sites","Feature Flags on Static Sites",{"path":1830,"title":1831},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":1833,"title":1834},"\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":1836,"title":1837},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Frunning-smoke-tests-against-a-preview-url","Running Smoke Tests Against a Preview URL",{"path":1839,"title":1297},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fauditing-npm-dependencies-in-ssg-pipelines",{"path":1841,"title":1842},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fenabling-hsts-and-preload-safely","Enabling HSTS and Preload Safely",{"path":1844,"title":1845},"\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":1847,"title":1848},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites","Security Headers and Hardening for Static Sites",{"path":1850,"title":1851},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsecuring-deploy-credentials-with-github-oidc","Securing Deploy Credentials with GitHub OIDC",{"path":1853,"title":1854},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fsecurity-headers-for-static-sites\u002Fsubresource-integrity-for-third-party-assets","Subresource Integrity for Third-Party Assets",{"path":1856,"title":1857},"\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":1859,"title":1860},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fclean-urls-and-trailing-slashes-on-s3","Clean URLs and Trailing Slashes on S3",{"path":1862,"title":1863},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fcloudfront-functions-for-redirects","CloudFront Functions for Redirects",{"path":1865,"title":1866},"\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":1868,"title":1869},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites","Self-Hosting Static Sites on S3, Nginx and Caddy",{"path":1871,"title":1872},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-caddy","Serving a Static Site with Caddy",{"path":1874,"title":1875},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fserving-a-static-site-with-nginx","Serving a Static Site with Nginx",{"path":1877,"title":1878},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fself-hosting-static-sites\u002Fzero-downtime-deploys-with-symlink-swaps","Zero-Downtime Deploys with Symlink Swaps",{"path":1880,"title":1881},"\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":1883,"title":1884},"\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":1886,"title":1887},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites","Serverless Functions for Static Sites",{"path":1889,"title":1890},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fnetlify-functions-vs-cloudflare-workers","Netlify Functions vs Cloudflare Workers",{"path":1892,"title":1893},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fserverless-functions-for-static-sites\u002Fprotecting-a-static-site-behind-authentication","Protecting a Static Site Behind Authentication",{"path":1895,"title":1896},"\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",1789722848012]