[{"data":1,"prerenderedAt":2283},["ShallowReactive",2],{"page:\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-hugo-to-astro-without-breaking-urls":3,"all-docs-nav":2043},{"id":4,"title":5,"body":6,"breadcrumb":2019,"dateModified":2028,"datePublished":2028,"description":2029,"extension":766,"faq":2030,"meta":2037,"navigation":354,"path":2038,"seo":2039,"slug":12,"stem":2040,"type":2041,"__hash__":2042},"content\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-hugo-to-astro-without-breaking-urls\u002Findex.md","Migrating From Hugo to Astro Without Breaking URLs",{"type":7,"value":8,"toc":1992},"minimark",[9,13,17,31,36,52,56,215,219,224,227,274,285,289,894,897,901,1120,1123,1127,1224,1231,1235,1390,1394,1397,1445,1456,1467,1564,1568,1571,1593,1599,1613,1620,1624,1627,1630,1633,1637,1644,1739,1827,1834,1838,1845,1848,1852,1893,1897,1902,1906,1910,1913,1917,1924,1928,1931,1935,1938,1942,1945,1949,1988],[10,11,5],"h1",{"id":12},"migrating-from-hugo-to-astro-without-breaking-urls",[14,15,16],"p",{},"Hugo builds a thousand pages in seconds and asks nothing of you at runtime. The reasons to leave it are almost always about the page itself rather than the build: a documentation site that needs interactive components, a design system shared with a product application, a content model you want the build to validate rather than hope about.",[14,18,19,20,24,25,30],{},"The risk in this particular move is URLs. Hugo derives paths from configuration and directory structure; Astro derives them from file locations under ",[21,22,23],"code",{},"src\u002Fpages\u002F",". Left alone, the two produce different URL schemes, and a docs site that quietly changes every path loses its inbound links. This guide keeps them identical. It is one route through ",[26,27,29],"a",{"href":28},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002F","Migrating Between Static Site Generators",".",[32,33,35],"h2",{"id":34},"prerequisites","Prerequisites",[37,38,39,43,49],"ul",{},[40,41,42],"li",{},"Node 20+ and Astro 4 or newer, with the MDX integration if any content uses shortcodes.",[40,44,45,46,30],{},"The Hugo site building locally plus its production sitemap saved as ",[21,47,48],{},"urls-before.txt",[40,50,51],{},"A list of the shortcodes actually used, from the census in the parent guide.",[32,53,55],{"id":54},"decide-the-routing-contract-first","Decide the Routing Contract First",[57,58,59,211],"figure",{},[60,61,68,69,68,73,68,77,68,84,68,196],"svg",{"viewBox":62,"role":63,"ariaLabelledBy":64,"xmlns":67},"0 0 760 300","img",[65,66],"ha-route-title","ha-route-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[70,71,72],"title",{"id":65},"Hugo config-driven routing versus Astro file-driven routing",[74,75,76],"desc",{"id":66},"Hugo derives a URL from a permalink pattern in configuration applied to a content file, so the source path and the URL are independent. Astro derives a URL from the file path under src slash pages. A dynamic route reading an explicit permalink field bridges the two, keeping URLs independent of file layout.",[78,79],"rect",{"x":80,"y":80,"width":81,"height":82,"fill":83},"0","760","300","#ffffff",[85,86,88,89,88,97,88,108,88,114,88,120,88,124,88,130,88,134,88,138,88,143,88,147,88,150,88,155,88,163,88,167,88,171,88,175,88,191,68],"g",{"style":87},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[90,91,96],"text",{"x":92,"y":93,"fill":94,"style":95},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","Two different sources of truth for a URL",[78,98],{"x":99,"y":100,"width":101,"height":102,"rx":103,"fill":104,"opacity":105,"stroke":106,"style":107},"40","56","200","76","10","#ffca3a","0.26","#a97b00","stroke-width:1.5px",[90,109,113],{"x":110,"y":111,"fill":94,"style":112},"140","82","font-size:12px;font-weight:700;text-anchor:middle","Hugo",[90,115,119],{"x":110,"y":116,"fill":117,"style":118},"102","#556071","font-size:11px;text-anchor:middle","content\u002Fdocs\u002Fdeploy.md",[90,121,123],{"x":110,"y":122,"fill":117,"style":118},"120","+ permalinks config",[78,125],{"x":126,"y":100,"width":127,"height":102,"rx":103,"fill":128,"opacity":129,"stroke":117,"style":107},"290","180","#d9e2ef","0.5",[90,131,133],{"x":92,"y":132,"fill":94,"style":112},"90","\u002Fguides\u002Fdeploy\u002F",[90,135,137],{"x":92,"y":136,"fill":117,"style":118},"112","the URL readers have",[78,139],{"x":140,"y":100,"width":101,"height":102,"rx":103,"fill":141,"opacity":142,"stroke":141,"style":107},"520","#1982c4","0.14",[90,144,146],{"x":145,"y":111,"fill":94,"style":112},"620","Astro, naively",[90,148,149],{"x":145,"y":116,"fill":117,"style":118},"src\u002Fpages\u002Fdocs\u002Fdeploy.md",[90,151,154],{"x":145,"y":122,"fill":152,"style":153},"#d83b41","font-size:11px;font-weight:700;text-anchor:middle","→ \u002Fdocs\u002Fdeploy\u002F ✗",[78,156],{"x":101,"y":157,"width":158,"height":159,"rx":103,"fill":160,"opacity":161,"stroke":162,"style":107},"182","360","80","#8ac926","0.16","#5a8a16",[90,164,166],{"x":92,"y":165,"fill":94,"style":112},"208","Astro with an explicit permalink field",[90,168,170],{"x":92,"y":169,"fill":117,"style":118},"228","src\u002Fpages\u002F[...permalink].astro reads front matter",[90,172,174],{"x":92,"y":173,"fill":94,"style":153},"248","→ \u002Fguides\u002Fdeploy\u002F ✓ regardless of file location",[85,176,179,180,179,185,179,188,88],{"stroke":117,"fill":177,"style":178},"none","stroke-width:2px","\n      ",[181,182],"path",{"d":183,"style":184},"M242 94 L288 94","marker-end:url(#ha-arrow)",[181,186],{"d":187,"style":184},"M518 94 L472 94",[181,189],{"d":190,"style":184},"M380 134 L380 180",[90,192,195],{"x":99,"y":193,"fill":117,"style":194},"288","font-size:11px","Writing the old URL into front matter during conversion makes the contract explicit and testable",[197,198,88,199,68],"defs",{},[200,201,179,208,88],"marker",{"id":202,"viewBox":203,"refX":204,"refY":205,"markerWidth":206,"markerHeight":206,"orient":207},"ha-arrow","0 0 10 10","8","5","7","auto-start-reverse",[181,209],{"d":210,"fill":117},"M0 0 L10 5 L0 10 z",[212,213,214],"figcaption",{},"File-based routing is convenient until it disagrees with the URLs you already published. An explicit permalink field decouples the two and turns the contract into data you can assert on.",[32,216,218],{"id":217},"the-recipe","The Recipe",[220,221,223],"h3",{"id":222},"_1-extract-the-real-url-for-every-page","1. Extract the real URL for every page",[14,225,226],{},"Hugo already knows every URL it produces. Ask it, rather than reverse-engineering the config:",[228,229,234],"pre",{"className":230,"code":231,"language":232,"meta":233,"style":233},"language-go-html-template shiki shiki-themes github-light github-dark","{{\u002F* layouts\u002F_default\u002Furlmap.json — output format producing a source→URL map *\u002F}}\n{{- $out := slice -}}\n{{- range .Site.RegularPages -}}\n  {{- $out = $out | append (dict \"file\" .File.Path \"url\" .RelPermalink \"title\" .Title) -}}\n{{- end -}}\n{{- $out | jsonify -}}\n","go-html-template","",[21,235,236,244,250,256,262,268],{"__ignoreMap":233},[237,238,241],"span",{"class":239,"line":240},"line",1,[237,242,243],{},"{{\u002F* layouts\u002F_default\u002Furlmap.json — output format producing a source→URL map *\u002F}}\n",[237,245,247],{"class":239,"line":246},2,[237,248,249],{},"{{- $out := slice -}}\n",[237,251,253],{"class":239,"line":252},3,[237,254,255],{},"{{- range .Site.RegularPages -}}\n",[237,257,259],{"class":239,"line":258},4,[237,260,261],{},"  {{- $out = $out | append (dict \"file\" .File.Path \"url\" .RelPermalink \"title\" .Title) -}}\n",[237,263,265],{"class":239,"line":264},5,[237,266,267],{},"{{- end -}}\n",[237,269,271],{"class":239,"line":270},6,[237,272,273],{},"{{- $out | jsonify -}}\n",[14,275,276,277,280,281,284],{},"Register a custom output format for the home page and run ",[21,278,279],{},"hugo"," once; you now have ",[21,282,283],{},"public\u002Furlmap.json",", an authoritative mapping produced by the generator itself rather than by your reading of its documentation.",[220,286,288],{"id":287},"_2-convert-content-writing-the-url-into-front-matter","2. Convert content, writing the URL into front matter",[228,290,294],{"className":291,"code":292,"language":293,"meta":233,"style":233},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F scripts\u002Fhugo-to-astro.mjs\nimport { readFile, writeFile, mkdir } from 'node:fs\u002Fpromises';\nimport path from 'node:path';\nimport matter from 'gray-matter';\n\nconst map = JSON.parse(await readFile('public\u002Furlmap.json', 'utf8'));\n\nfor (const entry of map) {\n  const src = path.join('content', entry.file);\n  const { data, content } = matter(await readFile(src, 'utf8'));\n\n  const body = content\n    \u002F\u002F {{\u003C note >}}…{{\u003C \u002Fnote >}}  →  \u003CNote>…\u003C\u002FNote>\n    .replace(\u002F\\{\\{\u003C\\s*(\\w+)\\s*>\\}\\}\u002Fg, '\u003C$1>')\n    .replace(\u002F\\{\\{\u003C\\s*\\\u002F(\\w+)\\s*>\\}\\}\u002Fg, '\u003C\u002F$1>')\n    \u002F\u002F {{\u003C figure src=\"x\" caption=\"y\" >}} → \u003CFigure src=\"x\" caption=\"y\" \u002F>\n    .replace(\u002F\\{\\{\u003C\\s*figure\\s+([^>]+?)\\s*\\\u002F?>\\}\\}\u002Fg, '\u003CFigure $1 \u002F>');\n\n  const usesComponents = \u002F\u003C[A-Z]\u002F.test(body);\n  const out = path.join('src\u002Fcontent\u002Fdocs',\n    entry.file.replace(\u002F\\.md$\u002F, usesComponents ? '.mdx' : '.md'));\n\n  await mkdir(path.dirname(out), { recursive: true });\n  await writeFile(out, matter.stringify(body, {\n    title: data.title,\n    description: data.description,\n    permalink: entry.url,          \u002F\u002F ← the contract, carried explicitly\n    weight: data.weight ?? 100,\n    draft: data.draft ?? false,\n  }));\n}\n","javascript",[21,295,296,302,322,336,350,356,400,405,425,450,488,493,506,512,573,622,628,694,699,727,749,788,793,817,834,840,846,855,869,882,888],{"__ignoreMap":233},[237,297,298],{"class":239,"line":240},[237,299,301],{"class":300},"sJ8bj","\u002F\u002F scripts\u002Fhugo-to-astro.mjs\n",[237,303,304,308,312,315,319],{"class":239,"line":246},[237,305,307],{"class":306},"szBVR","import",[237,309,311],{"class":310},"sVt8B"," { readFile, writeFile, mkdir } ",[237,313,314],{"class":306},"from",[237,316,318],{"class":317},"sZZnC"," 'node:fs\u002Fpromises'",[237,320,321],{"class":310},";\n",[237,323,324,326,329,331,334],{"class":239,"line":252},[237,325,307],{"class":306},[237,327,328],{"class":310}," path ",[237,330,314],{"class":306},[237,332,333],{"class":317}," 'node:path'",[237,335,321],{"class":310},[237,337,338,340,343,345,348],{"class":239,"line":258},[237,339,307],{"class":306},[237,341,342],{"class":310}," matter ",[237,344,314],{"class":306},[237,346,347],{"class":317}," 'gray-matter'",[237,349,321],{"class":310},[237,351,352],{"class":239,"line":264},[237,353,355],{"emptyLinePlaceholder":354},true,"\n",[237,357,358,361,365,368,371,373,377,380,383,386,388,391,394,397],{"class":239,"line":270},[237,359,360],{"class":306},"const",[237,362,364],{"class":363},"sj4cs"," map",[237,366,367],{"class":306}," =",[237,369,370],{"class":363}," JSON",[237,372,30],{"class":310},[237,374,376],{"class":375},"sScJk","parse",[237,378,379],{"class":310},"(",[237,381,382],{"class":306},"await",[237,384,385],{"class":375}," readFile",[237,387,379],{"class":310},[237,389,390],{"class":317},"'public\u002Furlmap.json'",[237,392,393],{"class":310},", ",[237,395,396],{"class":317},"'utf8'",[237,398,399],{"class":310},"));\n",[237,401,403],{"class":239,"line":402},7,[237,404,355],{"emptyLinePlaceholder":354},[237,406,408,411,414,416,419,422],{"class":239,"line":407},8,[237,409,410],{"class":306},"for",[237,412,413],{"class":310}," (",[237,415,360],{"class":306},[237,417,418],{"class":363}," entry",[237,420,421],{"class":306}," of",[237,423,424],{"class":310}," map) {\n",[237,426,428,431,434,436,439,442,444,447],{"class":239,"line":427},9,[237,429,430],{"class":306},"  const",[237,432,433],{"class":363}," src",[237,435,367],{"class":306},[237,437,438],{"class":310}," path.",[237,440,441],{"class":375},"join",[237,443,379],{"class":310},[237,445,446],{"class":317},"'content'",[237,448,449],{"class":310},", entry.file);\n",[237,451,453,455,458,461,463,466,469,472,475,477,479,481,484,486],{"class":239,"line":452},10,[237,454,430],{"class":306},[237,456,457],{"class":310}," { ",[237,459,460],{"class":363},"data",[237,462,393],{"class":310},[237,464,465],{"class":363},"content",[237,467,468],{"class":310}," } ",[237,470,471],{"class":306},"=",[237,473,474],{"class":375}," matter",[237,476,379],{"class":310},[237,478,382],{"class":306},[237,480,385],{"class":375},[237,482,483],{"class":310},"(src, ",[237,485,396],{"class":317},[237,487,399],{"class":310},[237,489,491],{"class":239,"line":490},11,[237,492,355],{"emptyLinePlaceholder":354},[237,494,496,498,501,503],{"class":239,"line":495},12,[237,497,430],{"class":306},[237,499,500],{"class":363}," body",[237,502,367],{"class":306},[237,504,505],{"class":310}," content\n",[237,507,509],{"class":239,"line":508},13,[237,510,511],{"class":300},"    \u002F\u002F {{\u003C note >}}…{{\u003C \u002Fnote >}}  →  \u003CNote>…\u003C\u002FNote>\n",[237,513,515,518,521,523,526,530,534,537,540,542,545,548,551,553,555,558,561,563,565,567,570],{"class":239,"line":514},14,[237,516,517],{"class":310},"    .",[237,519,520],{"class":375},"replace",[237,522,379],{"class":310},[237,524,525],{"class":317},"\u002F",[237,527,529],{"class":528},"snhLl","\\{\\{",[237,531,533],{"class":532},"sA_wV","\u003C",[237,535,536],{"class":363},"\\s",[237,538,539],{"class":306},"*",[237,541,379],{"class":532},[237,543,544],{"class":363},"\\w",[237,546,547],{"class":306},"+",[237,549,550],{"class":532},")",[237,552,536],{"class":363},[237,554,539],{"class":306},[237,556,557],{"class":532},">",[237,559,560],{"class":528},"\\}\\}",[237,562,525],{"class":317},[237,564,85],{"class":306},[237,566,393],{"class":310},[237,568,569],{"class":317},"'\u003C$1>'",[237,571,572],{"class":310},")\n",[237,574,576,578,580,582,584,586,588,590,592,595,597,599,601,603,605,607,609,611,613,615,617,620],{"class":239,"line":575},15,[237,577,517],{"class":310},[237,579,520],{"class":375},[237,581,379],{"class":310},[237,583,525],{"class":317},[237,585,529],{"class":528},[237,587,533],{"class":532},[237,589,536],{"class":363},[237,591,539],{"class":306},[237,593,594],{"class":528},"\\\u002F",[237,596,379],{"class":532},[237,598,544],{"class":363},[237,600,547],{"class":306},[237,602,550],{"class":532},[237,604,536],{"class":363},[237,606,539],{"class":306},[237,608,557],{"class":532},[237,610,560],{"class":528},[237,612,525],{"class":317},[237,614,85],{"class":306},[237,616,393],{"class":310},[237,618,619],{"class":317},"'\u003C\u002F$1>'",[237,621,572],{"class":310},[237,623,625],{"class":239,"line":624},16,[237,626,627],{"class":300},"    \u002F\u002F {{\u003C figure src=\"x\" caption=\"y\" >}} → \u003CFigure src=\"x\" caption=\"y\" \u002F>\n",[237,629,631,633,635,637,639,641,643,645,647,649,651,653,655,658,661,664,667,669,671,673,675,678,680,682,684,686,688,691],{"class":239,"line":630},17,[237,632,517],{"class":310},[237,634,520],{"class":375},[237,636,379],{"class":310},[237,638,525],{"class":317},[237,640,529],{"class":528},[237,642,533],{"class":532},[237,644,536],{"class":363},[237,646,539],{"class":306},[237,648,57],{"class":532},[237,650,536],{"class":363},[237,652,547],{"class":306},[237,654,379],{"class":532},[237,656,657],{"class":363},"[",[237,659,660],{"class":306},"^",[237,662,663],{"class":363},">]",[237,665,666],{"class":306},"+?",[237,668,550],{"class":532},[237,670,536],{"class":363},[237,672,539],{"class":306},[237,674,594],{"class":528},[237,676,677],{"class":306},"?",[237,679,557],{"class":532},[237,681,560],{"class":528},[237,683,525],{"class":317},[237,685,85],{"class":306},[237,687,393],{"class":310},[237,689,690],{"class":317},"'\u003CFigure $1 \u002F>'",[237,692,693],{"class":310},");\n",[237,695,697],{"class":239,"line":696},18,[237,698,355],{"emptyLinePlaceholder":354},[237,700,702,704,707,709,712,714,717,719,721,724],{"class":239,"line":701},19,[237,703,430],{"class":306},[237,705,706],{"class":363}," usesComponents",[237,708,367],{"class":306},[237,710,711],{"class":317}," \u002F",[237,713,533],{"class":532},[237,715,716],{"class":363},"[A-Z]",[237,718,525],{"class":317},[237,720,30],{"class":310},[237,722,723],{"class":375},"test",[237,725,726],{"class":310},"(body);\n",[237,728,730,732,735,737,739,741,743,746],{"class":239,"line":729},20,[237,731,430],{"class":306},[237,733,734],{"class":363}," out",[237,736,367],{"class":306},[237,738,438],{"class":310},[237,740,441],{"class":375},[237,742,379],{"class":310},[237,744,745],{"class":317},"'src\u002Fcontent\u002Fdocs'",[237,747,748],{"class":310},",\n",[237,750,752,755,757,759,761,764,767,770,772,775,777,780,783,786],{"class":239,"line":751},21,[237,753,754],{"class":310},"    entry.file.",[237,756,520],{"class":375},[237,758,379],{"class":310},[237,760,525],{"class":317},[237,762,763],{"class":528},"\\.",[237,765,766],{"class":532},"md",[237,768,769],{"class":306},"$",[237,771,525],{"class":317},[237,773,774],{"class":310},", usesComponents ",[237,776,677],{"class":306},[237,778,779],{"class":317}," '.mdx'",[237,781,782],{"class":306}," :",[237,784,785],{"class":317}," '.md'",[237,787,399],{"class":310},[237,789,791],{"class":239,"line":790},22,[237,792,355],{"emptyLinePlaceholder":354},[237,794,796,799,802,805,808,811,814],{"class":239,"line":795},23,[237,797,798],{"class":306},"  await",[237,800,801],{"class":375}," mkdir",[237,803,804],{"class":310},"(path.",[237,806,807],{"class":375},"dirname",[237,809,810],{"class":310},"(out), { recursive: ",[237,812,813],{"class":363},"true",[237,815,816],{"class":310}," });\n",[237,818,820,822,825,828,831],{"class":239,"line":819},24,[237,821,798],{"class":306},[237,823,824],{"class":375}," writeFile",[237,826,827],{"class":310},"(out, matter.",[237,829,830],{"class":375},"stringify",[237,832,833],{"class":310},"(body, {\n",[237,835,837],{"class":239,"line":836},25,[237,838,839],{"class":310},"    title: data.title,\n",[237,841,843],{"class":239,"line":842},26,[237,844,845],{"class":310},"    description: data.description,\n",[237,847,849,852],{"class":239,"line":848},27,[237,850,851],{"class":310},"    permalink: entry.url,          ",[237,853,854],{"class":300},"\u002F\u002F ← the contract, carried explicitly\n",[237,856,858,861,864,867],{"class":239,"line":857},28,[237,859,860],{"class":310},"    weight: data.weight ",[237,862,863],{"class":306},"??",[237,865,866],{"class":363}," 100",[237,868,748],{"class":310},[237,870,872,875,877,880],{"class":239,"line":871},29,[237,873,874],{"class":310},"    draft: data.draft ",[237,876,863],{"class":306},[237,878,879],{"class":363}," false",[237,881,748],{"class":310},[237,883,885],{"class":239,"line":884},30,[237,886,887],{"class":310},"  }));\n",[237,889,891],{"class":239,"line":890},31,[237,892,893],{"class":310},"}\n",[14,895,896],{},"Only pages that actually use components become MDX. Mixed collections are fine, and MDX parses noticeably more slowly, so converting everything costs build time for no benefit.",[220,898,900],{"id":899},"_3-define-the-schema-and-let-the-build-enforce-it","3. Define the schema, and let the build enforce it",[228,902,906],{"className":903,"code":904,"language":905,"meta":233,"style":233},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F src\u002Fcontent\u002Fconfig.ts\nimport { defineCollection, z } from 'astro:content';\n\nconst docs = defineCollection({\n  type: 'content',\n  schema: z.object({\n    title: z.string().max(70),\n    description: z.string().min(80).max(158),\n    permalink: z.string().regex(\u002F^\\\u002F.*\\\u002F$\u002F),   \u002F\u002F must be absolute, trailing slash\n    weight: z.number().default(100),\n    draft: z.boolean().default(false),\n  }),\n});\n\nexport const collections = { docs };\n","typescript",[21,907,908,913,927,931,946,955,965,987,1015,1051,1071,1090,1095,1100,1104],{"__ignoreMap":233},[237,909,910],{"class":239,"line":240},[237,911,912],{"class":300},"\u002F\u002F src\u002Fcontent\u002Fconfig.ts\n",[237,914,915,917,920,922,925],{"class":239,"line":246},[237,916,307],{"class":306},[237,918,919],{"class":310}," { defineCollection, z } ",[237,921,314],{"class":306},[237,923,924],{"class":317}," 'astro:content'",[237,926,321],{"class":310},[237,928,929],{"class":239,"line":252},[237,930,355],{"emptyLinePlaceholder":354},[237,932,933,935,938,940,943],{"class":239,"line":258},[237,934,360],{"class":306},[237,936,937],{"class":363}," docs",[237,939,367],{"class":306},[237,941,942],{"class":375}," defineCollection",[237,944,945],{"class":310},"({\n",[237,947,948,951,953],{"class":239,"line":264},[237,949,950],{"class":310},"  type: ",[237,952,446],{"class":317},[237,954,748],{"class":310},[237,956,957,960,963],{"class":239,"line":270},[237,958,959],{"class":310},"  schema: z.",[237,961,962],{"class":375},"object",[237,964,945],{"class":310},[237,966,967,970,973,976,979,981,984],{"class":239,"line":402},[237,968,969],{"class":310},"    title: z.",[237,971,972],{"class":375},"string",[237,974,975],{"class":310},"().",[237,977,978],{"class":375},"max",[237,980,379],{"class":310},[237,982,983],{"class":363},"70",[237,985,986],{"class":310},"),\n",[237,988,989,992,994,996,999,1001,1003,1006,1008,1010,1013],{"class":239,"line":407},[237,990,991],{"class":310},"    description: z.",[237,993,972],{"class":375},[237,995,975],{"class":310},[237,997,998],{"class":375},"min",[237,1000,379],{"class":310},[237,1002,159],{"class":363},[237,1004,1005],{"class":310},").",[237,1007,978],{"class":375},[237,1009,379],{"class":310},[237,1011,1012],{"class":363},"158",[237,1014,986],{"class":310},[237,1016,1017,1020,1022,1024,1027,1029,1031,1033,1035,1037,1039,1041,1043,1045,1048],{"class":239,"line":427},[237,1018,1019],{"class":310},"    permalink: z.",[237,1021,972],{"class":375},[237,1023,975],{"class":310},[237,1025,1026],{"class":375},"regex",[237,1028,379],{"class":310},[237,1030,525],{"class":317},[237,1032,660],{"class":306},[237,1034,594],{"class":528},[237,1036,30],{"class":363},[237,1038,539],{"class":306},[237,1040,594],{"class":528},[237,1042,769],{"class":306},[237,1044,525],{"class":317},[237,1046,1047],{"class":310},"),   ",[237,1049,1050],{"class":300},"\u002F\u002F must be absolute, trailing slash\n",[237,1052,1053,1056,1059,1061,1064,1066,1069],{"class":239,"line":452},[237,1054,1055],{"class":310},"    weight: z.",[237,1057,1058],{"class":375},"number",[237,1060,975],{"class":310},[237,1062,1063],{"class":375},"default",[237,1065,379],{"class":310},[237,1067,1068],{"class":363},"100",[237,1070,986],{"class":310},[237,1072,1073,1076,1079,1081,1083,1085,1088],{"class":239,"line":490},[237,1074,1075],{"class":310},"    draft: z.",[237,1077,1078],{"class":375},"boolean",[237,1080,975],{"class":310},[237,1082,1063],{"class":375},[237,1084,379],{"class":310},[237,1086,1087],{"class":363},"false",[237,1089,986],{"class":310},[237,1091,1092],{"class":239,"line":495},[237,1093,1094],{"class":310},"  }),\n",[237,1096,1097],{"class":239,"line":508},[237,1098,1099],{"class":310},"});\n",[237,1101,1102],{"class":239,"line":514},[237,1103,355],{"emptyLinePlaceholder":354},[237,1105,1106,1109,1112,1115,1117],{"class":239,"line":575},[237,1107,1108],{"class":306},"export",[237,1110,1111],{"class":306}," const",[237,1113,1114],{"class":363}," collections",[237,1116,367],{"class":306},[237,1118,1119],{"class":310}," { docs };\n",[14,1121,1122],{},"This is the main thing Astro gives you that Hugo does not: a build that fails, with a filename, when a page is missing a description or has a malformed URL. Run it once against the converted corpus and it will find every content defect accumulated over the site's life in a single pass.",[220,1124,1126],{"id":1125},"_4-route-from-the-permalink-field","4. Route from the permalink field",[228,1128,1132],{"className":1129,"code":1130,"language":1131,"meta":233,"style":233},"language-astro shiki shiki-themes github-light github-dark","---\n\u002F\u002F src\u002Fpages\u002F[...permalink].astro\nimport { getCollection } from 'astro:content';\nimport BaseLayout from '..\u002Flayouts\u002FBaseLayout.astro';\n\nexport async function getStaticPaths() {\n  const docs = await getCollection('docs', ({ data }) => !data.draft);\n  return docs.map((entry) => ({\n    params: { permalink: entry.data.permalink.replace(\u002F^\\\u002F|\\\u002F$\u002Fg, '') },\n    props: { entry },\n  }));\n}\n\nconst { entry } = Astro.props;\nconst { Content } = await entry.render();\n---\n\u003CBaseLayout title={entry.data.title} description={entry.data.description}>\n  \u003CContent \u002F>\n\u003C\u002FBaseLayout>\n","astro",[21,1133,1134,1139,1144,1149,1154,1158,1163,1168,1173,1178,1183,1187,1191,1195,1200,1205,1209,1214,1219],{"__ignoreMap":233},[237,1135,1136],{"class":239,"line":240},[237,1137,1138],{},"---\n",[237,1140,1141],{"class":239,"line":246},[237,1142,1143],{},"\u002F\u002F src\u002Fpages\u002F[...permalink].astro\n",[237,1145,1146],{"class":239,"line":252},[237,1147,1148],{},"import { getCollection } from 'astro:content';\n",[237,1150,1151],{"class":239,"line":258},[237,1152,1153],{},"import BaseLayout from '..\u002Flayouts\u002FBaseLayout.astro';\n",[237,1155,1156],{"class":239,"line":264},[237,1157,355],{"emptyLinePlaceholder":354},[237,1159,1160],{"class":239,"line":270},[237,1161,1162],{},"export async function getStaticPaths() {\n",[237,1164,1165],{"class":239,"line":402},[237,1166,1167],{},"  const docs = await getCollection('docs', ({ data }) => !data.draft);\n",[237,1169,1170],{"class":239,"line":407},[237,1171,1172],{},"  return docs.map((entry) => ({\n",[237,1174,1175],{"class":239,"line":427},[237,1176,1177],{},"    params: { permalink: entry.data.permalink.replace(\u002F^\\\u002F|\\\u002F$\u002Fg, '') },\n",[237,1179,1180],{"class":239,"line":452},[237,1181,1182],{},"    props: { entry },\n",[237,1184,1185],{"class":239,"line":490},[237,1186,887],{},[237,1188,1189],{"class":239,"line":495},[237,1190,893],{},[237,1192,1193],{"class":239,"line":508},[237,1194,355],{"emptyLinePlaceholder":354},[237,1196,1197],{"class":239,"line":514},[237,1198,1199],{},"const { entry } = Astro.props;\n",[237,1201,1202],{"class":239,"line":575},[237,1203,1204],{},"const { Content } = await entry.render();\n",[237,1206,1207],{"class":239,"line":624},[237,1208,1138],{},[237,1210,1211],{"class":239,"line":630},[237,1212,1213],{},"\u003CBaseLayout title={entry.data.title} description={entry.data.description}>\n",[237,1215,1216],{"class":239,"line":696},[237,1217,1218],{},"  \u003CContent \u002F>\n",[237,1220,1221],{"class":239,"line":701},[237,1222,1223],{},"\u003C\u002FBaseLayout>\n",[14,1225,1226,1227,1230],{},"Every URL now comes from data, so reorganising ",[21,1228,1229],{},"src\u002Fcontent\u002F"," later cannot move a single page.",[220,1232,1234],{"id":1233},"_5-gate-parity-in-ci","5. Gate parity in CI",[228,1236,1240],{"className":1237,"code":1238,"language":1239,"meta":233,"style":233},"language-bash shiki shiki-themes github-light github-dark","npm run build\nfind dist -name '*.html' | sed 's|dist||; s|\u002Findex.html|\u002F|' | sort > urls-after.txt\ncomm -23 urls-before.txt urls-after.txt > missing.txt\nif [ -s missing.txt ]; then echo \"MISSING URLS:\"; cat missing.txt; exit 1; fi\ncomm -13 urls-before.txt urls-after.txt > added.txt   # informational, not a failure\necho \"URL parity OK ($(wc -l \u003C urls-after.txt) pages)\"\n","bash",[21,1241,1242,1253,1287,1306,1351,1370],{"__ignoreMap":233},[237,1243,1244,1247,1250],{"class":239,"line":240},[237,1245,1246],{"class":375},"npm",[237,1248,1249],{"class":317}," run",[237,1251,1252],{"class":317}," build\n",[237,1254,1255,1258,1261,1264,1267,1270,1273,1276,1278,1281,1284],{"class":239,"line":246},[237,1256,1257],{"class":375},"find",[237,1259,1260],{"class":317}," dist",[237,1262,1263],{"class":363}," -name",[237,1265,1266],{"class":317}," '*.html'",[237,1268,1269],{"class":306}," |",[237,1271,1272],{"class":375}," sed",[237,1274,1275],{"class":317}," 's|dist||; s|\u002Findex.html|\u002F|'",[237,1277,1269],{"class":306},[237,1279,1280],{"class":375}," sort",[237,1282,1283],{"class":306}," >",[237,1285,1286],{"class":317}," urls-after.txt\n",[237,1288,1289,1292,1295,1298,1301,1303],{"class":239,"line":252},[237,1290,1291],{"class":375},"comm",[237,1293,1294],{"class":363}," -23",[237,1296,1297],{"class":317}," urls-before.txt",[237,1299,1300],{"class":317}," urls-after.txt",[237,1302,1283],{"class":306},[237,1304,1305],{"class":317}," missing.txt\n",[237,1307,1308,1311,1314,1317,1320,1323,1326,1329,1332,1335,1338,1340,1343,1346,1348],{"class":239,"line":258},[237,1309,1310],{"class":306},"if",[237,1312,1313],{"class":310}," [ ",[237,1315,1316],{"class":306},"-s",[237,1318,1319],{"class":310}," missing.txt ]; ",[237,1321,1322],{"class":306},"then",[237,1324,1325],{"class":363}," echo",[237,1327,1328],{"class":317}," \"MISSING URLS:\"",[237,1330,1331],{"class":310},"; ",[237,1333,1334],{"class":375},"cat",[237,1336,1337],{"class":317}," missing.txt",[237,1339,1331],{"class":310},[237,1341,1342],{"class":363},"exit",[237,1344,1345],{"class":363}," 1",[237,1347,1331],{"class":310},[237,1349,1350],{"class":306},"fi\n",[237,1352,1353,1355,1358,1360,1362,1364,1367],{"class":239,"line":264},[237,1354,1291],{"class":375},[237,1356,1357],{"class":363}," -13",[237,1359,1297],{"class":317},[237,1361,1300],{"class":317},[237,1363,1283],{"class":306},[237,1365,1366],{"class":317}," added.txt",[237,1368,1369],{"class":300},"   # informational, not a failure\n",[237,1371,1372,1375,1378,1381,1384,1387],{"class":239,"line":270},[237,1373,1374],{"class":363},"echo",[237,1376,1377],{"class":317}," \"URL parity OK ($(",[237,1379,1380],{"class":375},"wc",[237,1382,1383],{"class":363}," -l",[237,1385,1386],{"class":306}," \u003C",[237,1388,1389],{"class":317}," urls-after.txt) pages)\"\n",[220,1391,1393],{"id":1392},"_6-turn-shortcodes-into-components-you-actually-want","6. Turn shortcodes into components you actually want",[14,1395,1396],{},"A Hugo shortcode is a template that renders HTML; an Astro component is a template that renders HTML and can optionally hydrate. The conversion is usually a simplification, because most shortcodes exist to work around the absence of components in the first place.",[228,1398,1400],{"className":1129,"code":1399,"language":1131,"meta":233,"style":233},"---\n\u002F\u002F src\u002Fcomponents\u002FNote.astro — replaces {{\u003C note >}}…{{\u003C \u002Fnote >}}\nconst { type = 'note' } = Astro.props;\nconst label = { note: 'Note', warn: 'Warning', tip: 'Tip' }[type] ?? 'Note';\n---\n\u003Caside class={`callout callout-${type}`}>\n  \u003Cstrong>{label}:\u003C\u002Fstrong>\n  \u003Cslot \u002F>\n\u003C\u002Faside>\n",[21,1401,1402,1406,1411,1416,1421,1425,1430,1435,1440],{"__ignoreMap":233},[237,1403,1404],{"class":239,"line":240},[237,1405,1138],{},[237,1407,1408],{"class":239,"line":246},[237,1409,1410],{},"\u002F\u002F src\u002Fcomponents\u002FNote.astro — replaces {{\u003C note >}}…{{\u003C \u002Fnote >}}\n",[237,1412,1413],{"class":239,"line":252},[237,1414,1415],{},"const { type = 'note' } = Astro.props;\n",[237,1417,1418],{"class":239,"line":258},[237,1419,1420],{},"const label = { note: 'Note', warn: 'Warning', tip: 'Tip' }[type] ?? 'Note';\n",[237,1422,1423],{"class":239,"line":264},[237,1424,1138],{},[237,1426,1427],{"class":239,"line":270},[237,1428,1429],{},"\u003Caside class={`callout callout-${type}`}>\n",[237,1431,1432],{"class":239,"line":402},[237,1433,1434],{},"  \u003Cstrong>{label}:\u003C\u002Fstrong>\n",[237,1436,1437],{"class":239,"line":407},[237,1438,1439],{},"  \u003Cslot \u002F>\n",[237,1441,1442],{"class":239,"line":427},[237,1443,1444],{},"\u003C\u002Faside>\n",[14,1446,1447,1448,1451,1452,1455],{},"Register the components your content uses globally so authors do not have to import them in every file, and keep the invocation names identical to the old shortcode names wherever possible. A writer who has typed ",[21,1449,1450],{},"\u003CNote>"," for three years should not have to learn ",[21,1453,1454],{},"\u003CCallout type=\"note\">"," as part of a migration they did not ask for.",[14,1457,1458,1459,1462,1463,30],{},"This is also the point where a genuinely interactive component becomes possible: an API request builder, a configuration generator, a live example. Add a ",[21,1460,1461],{},"client:visible"," directive and the component hydrates when it scrolls into view, while the rest of the page stays static HTML. That per-component control is the actual reason to be on Astro, and it is worth building one such component during the migration to prove the pipeline works end to end — the trade-offs are laid out in ",[26,1464,1466],{"href":1465},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance\u002F","Astro Islands vs Full Hydration Performance",[57,1468,1469,1561],{},[60,1470,68,1475,68,1478,68,1481,68,1484,68,1554],{"viewBox":1471,"role":63,"ariaLabelledBy":1472,"xmlns":67},"0 0 760 280",[1473,1474],"ha-shortcode-title","ha-shortcode-desc",[70,1476,1477],{"id":1473},"Hugo shortcode compared with an Astro component",[74,1479,1480],{"id":1474},"A Hugo shortcode renders HTML at build time only. An Astro component renders the same HTML at build time and can additionally hydrate on the client when a client directive is present, so the same authoring syntax covers both static and interactive cases.",[78,1482],{"x":80,"y":80,"width":81,"height":1483,"fill":83},"280",[85,1485,88,1486,88,1489,88,1492,88,1495,88,1499,88,1501,88,1505,88,1509,88,1513,88,1517,88,1521,88,1523,88,1525,88,1528,88,1531,88,1535,88,1538,88,1550,68],{"style":87},[90,1487,1488],{"x":92,"y":93,"fill":94,"style":95},"Same call site, one extra capability",[78,1490],{"x":99,"y":100,"width":101,"height":983,"rx":103,"fill":104,"opacity":1491,"stroke":106,"style":107},"0.28",[90,1493,1494],{"x":110,"y":111,"fill":94,"style":112},"Hugo shortcode",[90,1496,1498],{"x":110,"y":1497,"fill":117,"style":118},"104","build-time HTML only",[78,1500],{"x":82,"y":100,"width":127,"height":983,"rx":103,"fill":128,"opacity":129,"stroke":117,"style":107},[90,1502,1504],{"x":1503,"y":132,"fill":94,"style":112},"390","static HTML",[90,1506,1508],{"x":1503,"y":1507,"fill":117,"style":118},"110","0 KB JavaScript",[78,1510],{"x":99,"y":1511,"width":101,"height":983,"rx":103,"fill":160,"opacity":1512,"stroke":162,"style":107},"166","0.18",[90,1514,1516],{"x":110,"y":1515,"fill":94,"style":112},"192","Astro component",[90,1518,1520],{"x":110,"y":1519,"fill":117,"style":118},"214","build-time by default",[78,1522],{"x":82,"y":1511,"width":127,"height":983,"rx":103,"fill":128,"opacity":129,"stroke":117,"style":107},[90,1524,1504],{"x":1503,"y":101,"fill":94,"style":112},[90,1526,1508],{"x":1503,"y":1527,"fill":117,"style":118},"220",[78,1529],{"x":1530,"y":1511,"width":127,"height":983,"rx":103,"fill":141,"opacity":142,"stroke":141,"style":107},"540",[90,1532,1534],{"x":1533,"y":1515,"fill":94,"style":112},"630","+ client:visible",[90,1536,1537],{"x":1533,"y":1519,"fill":117,"style":118},"hydrates on scroll",[85,1539,179,1540,179,1544,179,1547,88],{"stroke":117,"fill":177,"style":178},[181,1541],{"d":1542,"style":1543},"M242 91 L298 91","marker-end:url(#ha-sc-arrow)",[181,1545],{"d":1546,"style":1543},"M242 201 L298 201",[181,1548],{"d":1549,"style":1543},"M482 201 L538 201",[90,1551,1553],{"x":99,"y":1552,"fill":117,"style":194},"266","Pages that need nothing interactive ship exactly what Hugo shipped — the capability is opt-in per component",[197,1555,88,1556,68],{},[200,1557,179,1559,88],{"id":1558,"viewBox":203,"refX":204,"refY":205,"markerWidth":206,"markerHeight":206,"orient":207},"ha-sc-arrow",[181,1560],{"d":210,"fill":117},[212,1562,1563],{},"Nothing hydrates unless you ask it to, which is why a converted docs site still ships zero JavaScript on the pages that were plain documents before.",[220,1565,1567],{"id":1566},"_7-settle-trailing-slashes-case-and-index-pages","7. Settle trailing slashes, case and index pages",[14,1569,1570],{},"Three small details account for most of the \"URL parity passed but production 404s\" reports after this migration.",[14,1572,1573,1577,1578,1581,1582,393,1585,1588,1589,1592],{},[1574,1575,1576],"strong",{},"Trailing slashes."," Hugo emits directory-style URLs ending in a slash by default. Astro's ",[21,1579,1580],{},"trailingSlash"," option accepts ",[21,1583,1584],{},"always",[21,1586,1587],{},"never"," or ",[21,1590,1591],{},"ignore",", and the build output differs accordingly. Set it to match what your old site served, and set the same policy at your host, because a mismatch between the two turns every internal link into a redirect — cheap individually, and a measurable delay on a page with forty of them.",[14,1594,1595,1598],{},[1574,1596,1597],{},"Case sensitivity."," Hugo lowercases path segments derived from titles; a hand-written Astro route does not. If any old URL contains an uppercase letter, it must be reproduced exactly, and a case-insensitive local filesystem will happily hide the problem until it reaches a Linux host.",[14,1600,1601,1604,1605,1608,1609,1612],{},[1574,1602,1603],{},"Index pages."," Hugo's ",[21,1606,1607],{},"_index.md"," becomes a section list page; Astro needs an explicit ",[21,1610,1611],{},"index.astro"," or an entry whose permalink ends at the section root. These are easy to forget precisely because they are the pages nobody links to by filename — and they are usually the most-linked URLs on the site.",[14,1614,1615,1616,1619],{},"Run the parity gate on a case-sensitive filesystem, with the host's trailing-slash policy applied, before believing it. The cheapest way to do that is to run it against a real preview deployment rather than a local ",[21,1617,1618],{},"dist\u002F"," directory, which is one more argument for having per-pull-request previews in place before starting.",[220,1621,1623],{"id":1622},"_8-keep-the-sitemap-and-search-index-honest","8. Keep the sitemap and search index honest",[14,1625,1626],{},"A migration changes two things search engines care about: the pages that exist and the internal links between them. Regenerate the sitemap from the new build rather than copying the old one — a stale sitemap that lists URLs the new build does not produce is a slow, quiet source of crawl errors.",[14,1628,1629],{},"If the site has a client-side search index, rebuild it as part of the same pipeline and check its document count against the page count. An index built from the old content directory will keep returning results that point at URLs the new site no longer serves, and because search results are generated at runtime, no build-time link check will catch it.",[14,1631,1632],{},"Finally, resubmit the sitemap after the last section cuts over and watch coverage for a fortnight. What you are looking for is not a ranking change — those take longer and have many causes — but a spike in \"not found\" or \"redirect\" statuses, which almost always traces to one pattern rather than to individual pages.",[32,1634,1636],{"id":1635},"measured-impact","Measured Impact",[14,1638,1639,1640,1643],{},"A 900-page documentation site, GitHub Actions ",[21,1641,1642],{},"ubuntu-latest",", cold cache, median of five runs:",[1645,1646,1647,1666],"table",{},[1648,1649,1650],"thead",{},[1651,1652,1653,1657,1660,1663],"tr",{},[1654,1655,1656],"th",{},"Metric",[1654,1658,1659],{},"Hugo 0.128",[1654,1661,1662],{},"Astro 4",[1654,1664,1665],{},"Change",[1667,1668,1669,1684,1698,1712,1725],"tbody",{},[1651,1670,1671,1675,1678,1681],{},[1672,1673,1674],"td",{},"Cold build",[1672,1676,1677],{},"4.1 s",[1672,1679,1680],{},"71 s",[1672,1682,1683],{},"17× slower",[1651,1685,1686,1689,1692,1695],{},[1672,1687,1688],{},"Dev server rebuild (one page)",[1672,1690,1691],{},"0.28 s",[1672,1693,1694],{},"0.42 s",[1672,1696,1697],{},"Comparable",[1651,1699,1700,1703,1706,1709],{},[1672,1701,1702],{},"CI wall clock incl. install",[1672,1704,1705],{},"48 s",[1672,1707,1708],{},"2 m 40 s",[1672,1710,1711],{},"+233%",[1651,1713,1714,1717,1720,1722],{},[1672,1715,1716],{},"JS shipped to the reader (median page)",[1672,1718,1719],{},"0 KB",[1672,1721,1719],{},[1672,1723,1724],{},"Unchanged",[1651,1726,1727,1730,1733,1736],{},[1672,1728,1729],{},"Content defects found by the schema",[1672,1731,1732],{},"n\u002Fa",[1672,1734,1735],{},"63 pages",[1672,1737,1738],{},"—",[57,1740,1741,1824],{},[60,1742,68,1747,68,1750,68,1753,68,1755],{"viewBox":1743,"role":63,"ariaLabelledBy":1744,"xmlns":67},"0 0 760 290",[1745,1746],"ha-tradeoff-title","ha-tradeoff-desc",[70,1748,1749],{"id":1745},"What the migration costs and what it buys",[74,1751,1752],{"id":1746},"Two columns. The cost column shows a cold build rising from 4.1 seconds to 71 seconds and CI wall clock rising from 48 seconds to 2 minutes 40. The benefit column shows a typed schema catching 63 content defects, component islands available per page, and a shared design system with the product application.",[78,1754],{"x":80,"y":80,"width":81,"height":126,"fill":83},[85,1756,88,1757,88,1760,88,1768,88,1774,88,1780,88,1784,88,1788,88,1792,88,1796,88,1800,88,1804,88,1808,88,1811,88,1814,88,1817,88,1820,68],{"style":87},[90,1758,1759],{"x":92,"y":93,"fill":94,"style":95},"A deliberate trade, not an upgrade",[78,1761],{"x":99,"y":1762,"width":1763,"height":1764,"rx":1765,"fill":1766,"opacity":1767,"stroke":152,"style":107},"52","330","196","12","#ff595e","0.10",[90,1769,1773],{"x":1770,"y":1771,"fill":94,"style":1772},"205","78","font-size:13px;font-weight:700;text-anchor:middle","Costs",[90,1775,1779],{"x":1776,"y":1777,"fill":94,"style":1778},"64","108","font-size:12px","cold build 4.1 s → 71 s",[90,1781,1783],{"x":1776,"y":1782,"fill":94,"style":1778},"134","CI 48 s → 2 m 40 s",[90,1785,1787],{"x":1776,"y":1786,"fill":94,"style":1778},"160","node_modules to maintain",[90,1789,1791],{"x":1776,"y":1790,"fill":94,"style":1778},"186","MDX parsing on component pages",[90,1793,1795],{"x":1776,"y":1794,"fill":94,"style":1778},"212","a JS toolchain in the deploy path",[78,1797],{"x":1798,"y":1762,"width":1799,"height":1764,"rx":1765,"fill":160,"opacity":142,"stroke":162,"style":107},"400","320",[90,1801,1803],{"x":1802,"y":1771,"fill":94,"style":1772},"560","Benefits",[90,1805,1807],{"x":1806,"y":1777,"fill":94,"style":1778},"424","schema caught 63 content defects",[90,1809,1810],{"x":1806,"y":1782,"fill":94,"style":1778},"islands where interactivity is needed",[90,1812,1813],{"x":1806,"y":1786,"fill":94,"style":1778},"design system shared with the app",[90,1815,1816],{"x":1806,"y":1790,"fill":94,"style":1778},"still 0 KB JS on plain pages",[90,1818,1819],{"x":1806,"y":1794,"fill":94,"style":1778},"typed front matter in the editor",[90,1821,1823],{"x":99,"y":1822,"fill":117,"style":194},"276","If none of the right-hand column applies to your site, this migration is a downgrade — stay on Hugo",[212,1825,1826],{},"Build time is the price. It is worth paying when the site genuinely needs components or content validation, and not otherwise.",[14,1828,1829,1830,30],{},"The last row deserves emphasis. Turning on the schema surfaced 63 pages with missing or over-long descriptions — defects that had been invisible for years and were affecting search snippets. That single pass paid for a meaningful share of the migration effort, and it is the kind of check worth keeping in any content pipeline, as discussed in ",[26,1831,1833],{"href":1832},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fchoosing-an-ssg-for-api-reference-documentation\u002F","Choosing an SSG for API Reference Documentation",[220,1835,1837],{"id":1836},"budgeting-for-the-slower-build","Budgeting for the slower build",[14,1839,1840,1841,1844],{},"A 17× build-time regression is the number that surprises teams after cutover, and it is worth planning for rather than absorbing. Three levers recover most of it in CI. Cache ",[21,1842,1843],{},"node_modules"," and Astro's own build cache between runs, which turns a cold two-minute install into a warm twenty seconds. Split preview builds from production builds, so a pull request only builds what it needs. And avoid MDX where plain Markdown will do, since MDX parsing is the single largest per-page cost in an Astro content build.",[14,1846,1847],{},"For local work the situation is better than the cold-build number suggests: the dev server rebuilds a single page in well under half a second, so an author editing prose sees the same responsiveness they had before. The regression lands almost entirely on CI, which means it is a pipeline problem with pipeline solutions rather than something writers experience daily.",[32,1849,1851],{"id":1850},"pitfalls-rollback","Pitfalls & Rollback",[37,1853,1854,1860,1866,1872,1881,1887],{},[40,1855,1856,1859],{},[1574,1857,1858],{},"Letting file paths define URLs."," The default routing is convenient and wrong for a migration. Route from the permalink field.",[40,1861,1862,1865],{},[1574,1863,1864],{},"Converting everything to MDX."," It slows the build and gains nothing on pages with no components.",[40,1867,1868,1871],{},[1574,1869,1870],{},"Trusting a hand-written URL map."," Generate it from Hugo itself; config patterns interact in ways that are easy to misread.",[40,1873,1874,1877,1878,1880],{},[1574,1875,1876],{},"Forgetting trailing-slash policy."," Astro's ",[21,1879,1580],{}," setting must match what your old site served, or every URL is a redirect.",[40,1882,1883,1886],{},[1574,1884,1885],{},"Ignoring the build-time regression in CI."," A 3× longer pipeline changes how often people deploy; budget for caching before it becomes a complaint.",[40,1888,1889,1892],{},[1574,1890,1891],{},"Rollback:"," keep the Hugo site building in the same repository until the last section is cut over. Reverting is re-pointing the edge route at the Hugo deployment.",[32,1894,1896],{"id":1895},"conclusion","Conclusion",[14,1898,1899,1900,30],{},"The technical work in a Hugo-to-Astro migration is ordinary; the discipline is keeping URLs as data rather than as a side effect of file layout. Generate the URL map from Hugo, carry each permalink into front matter, route from that field, and gate parity in CI. Then decide honestly whether the schema and the components are worth the build time — and if they are not, staying on Hugo is a perfectly good outcome. The wider process is in ",[26,1901,29],{"href":28},[32,1903,1905],{"id":1904},"faq","FAQ",[220,1907,1909],{"id":1908},"why-move-from-hugo-to-astro-at-all","Why move from Hugo to Astro at all?",[14,1911,1912],{},"For component-level interactivity and a typed content model. Hugo is faster to build and perfectly good for pure documents; Astro earns its slower build when pages need real components, islands of client-side behaviour, or a schema that fails the build when content is malformed.",[220,1914,1916],{"id":1915},"how-do-i-reproduce-hugos-permalink-patterns-in-astro","How do I reproduce Hugo's permalink patterns in Astro?",[14,1918,1919,1920,1923],{},"Drive routing from an explicit ",[21,1921,1922],{},"permalink"," field in front matter rather than from file paths. A dynamic route that reads that field and emits the exact path gives you a URL scheme independent of how the source files are organised.",[220,1925,1927],{"id":1926},"will-my-build-get-slower","Will my build get slower?",[14,1929,1930],{},"Yes, substantially. Astro renders through a JavaScript toolchain rather than a Go binary, so expect roughly an order of magnitude more build time on a large corpus. Incremental dev builds stay fast, so writers notice much less than CI does.",[220,1932,1934],{"id":1933},"what-replaces-hugo-shortcodes","What replaces Hugo shortcodes?",[14,1936,1937],{},"MDX components. A shortcode becomes an Astro component imported into the page or registered globally, and the invocation syntax changes from angle-bracket shortcode calls to JSX-style tags.",[220,1939,1941],{"id":1940},"do-i-need-to-convert-all-content-to-mdx","Do I need to convert all content to MDX?",[14,1943,1944],{},"No, and you should not. Keep plain Markdown for pages that only need text, and convert to MDX only those pages that actually use components. Mixed collections are supported and MDX parses more slowly.",[32,1946,1948],{"id":1947},"related","Related",[37,1950,1951,1960,1967,1974,1981],{},[40,1952,1953,1956,1957,1959],{},[1574,1954,1955],{},"Parent:"," ",[26,1958,29],{"href":28}," — the phased process this fits into.",[40,1961,1962,1966],{},[26,1963,1965],{"href":1964},"\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"," — for the URLs that genuinely must change.",[40,1968,1969,1973],{},[26,1970,1972],{"href":1971},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fporting-shortcodes-and-includes-between-generators\u002F","Porting Shortcodes and Includes Between Generators"," — shortcode to component mapping.",[40,1975,1976,1980],{},[26,1977,1979],{"href":1978},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites\u002F","Astro vs Eleventy for Documentation Sites"," — whether Astro is the right destination.",[40,1982,1983,1987],{},[26,1984,1986],{"href":1985},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories\u002F","Hugo Build Times for Large Repositories"," — what you are giving up.",[1989,1990,1991],"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .snhLl, html code.shiki .snhLl{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":233,"searchDepth":246,"depth":246,"links":1993},[1994,1995,1996,2006,2009,2010,2011,2018],{"id":34,"depth":246,"text":35},{"id":54,"depth":246,"text":55},{"id":217,"depth":246,"text":218,"children":1997},[1998,1999,2000,2001,2002,2003,2004,2005],{"id":222,"depth":252,"text":223},{"id":287,"depth":252,"text":288},{"id":899,"depth":252,"text":900},{"id":1125,"depth":252,"text":1126},{"id":1233,"depth":252,"text":1234},{"id":1392,"depth":252,"text":1393},{"id":1566,"depth":252,"text":1567},{"id":1622,"depth":252,"text":1623},{"id":1635,"depth":246,"text":1636,"children":2007},[2008],{"id":1836,"depth":252,"text":1837},{"id":1850,"depth":246,"text":1851},{"id":1895,"depth":246,"text":1896},{"id":1904,"depth":246,"text":1905,"children":2012},[2013,2014,2015,2016,2017],{"id":1908,"depth":252,"text":1909},{"id":1915,"depth":252,"text":1916},{"id":1926,"depth":252,"text":1927},{"id":1933,"depth":252,"text":1934},{"id":1940,"depth":252,"text":1941},{"id":1947,"depth":246,"text":1948},[2020,2022,2025,2026],{"name":2021,"item":525},"Home",{"name":2023,"item":2024},"Choosing the Right Static Site Generator for Production","\u002Fchoosing-the-right-static-site-generator-for-production\u002F",{"name":29,"item":28},{"name":5,"item":2027},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-hugo-to-astro-without-breaking-urls\u002F","2026-08-01","Move a Hugo site to Astro content collections while reproducing every permalink: slug mapping, a typed schema, shortcodes as MDX components, and a URL parity gate in CI.",[2031,2032,2034,2035,2036],{"q":1909,"a":1912},{"q":1916,"a":2033},"Drive routing from an explicit permalink field in front matter rather than from file paths. A dynamic route that reads that field and emits the exact path gives you a URL scheme independent of how the source files are organised.",{"q":1927,"a":1930},{"q":1934,"a":1937},{"q":1941,"a":1944},{},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-hugo-to-astro-without-breaking-urls",{"title":5,"description":2029},"choosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-hugo-to-astro-without-breaking-urls\u002Findex","article","cH7BDZRvCawVkGu_Jzn2Lbs7gFvOh148uPvG7-nCmS0",[2044,2047,2050,2052,2055,2057,2060,2063,2065,2068,2071,2074,2077,2079,2081,2084,2085,2087,2090,2093,2096,2099,2102,2104,2107,2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2166,2169,2172,2175,2178,2181,2184,2187,2190,2193,2196,2199,2202,2205,2208,2211,2214,2217,2220,2223,2226,2229,2232,2235,2238,2241,2244,2247,2250,2253,2256,2259,2262,2265,2268,2271,2274,2277,2280],{"path":2045,"title":2046},"\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":2048,"title":2049},"\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":2051,"title":1979},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites",{"path":2053,"title":2054},"\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":2056,"title":1986},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories",{"path":2058,"title":2059},"\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":2061,"title":2062},"\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":2064,"title":2023},"\u002Fchoosing-the-right-static-site-generator-for-production",{"path":2066,"title":2067},"\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":2069,"title":2070},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":2072,"title":2073},"\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":2075,"title":2076},"\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":2078,"title":29},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators",{"path":2080,"title":1965},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fkeeping-redirects-working-after-an-ssg-migration",{"path":2082,"title":2083},"\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":2038,"title":5},{"path":2086,"title":1972},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fporting-shortcodes-and-includes-between-generators",{"path":2088,"title":2089},"\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":2091,"title":2092},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":2094,"title":2095},"\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":2097,"title":2098},"\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":2100,"title":2101},"\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":2103,"title":1833},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix\u002Fchoosing-an-ssg-for-api-reference-documentation",{"path":2105,"title":2106},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":2108,"title":2109},"\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":2111,"title":2112},"\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":2114,"title":2115},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":2117,"title":2118},"\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":2120,"title":2121},"\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":2123,"title":2124},"\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":2126,"title":2127},"\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":2129,"title":2130},"\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":2132,"title":2133},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":2135,"title":2136},"\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":2138,"title":2139},"\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":2141,"title":2142},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":2144,"title":2145},"\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":2147,"title":2148},"\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":2150,"title":2151},"\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":2153,"title":2154},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":2156,"title":2157},"\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":2159,"title":2160},"\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":2162,"title":2163},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":2165,"title":1466},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance",{"path":2167,"title":2168},"\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":2170,"title":2171},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":2173,"title":2174},"\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":2176,"title":2177},"\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":2179,"title":2180},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":2182,"title":2183},"\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":2185,"title":2186},"\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":2188,"title":2189},"\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":2191,"title":2192},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":2194,"title":2195},"\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":2197,"title":2198},"\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":2200,"title":2201},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":2203,"title":2204},"\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":2206,"title":2207},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":2209,"title":2210},"\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":2212,"title":2213},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":2215,"title":2216},"\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":2218,"title":2219},"\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":2221,"title":2222},"\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":2224,"title":2225},"\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":2227,"title":2228},"\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":2230,"title":2231},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":2233,"title":2234},"\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":2236,"title":2237},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":2239,"title":2240},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":2242,"title":2243},"\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":2245,"title":2246},"\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":2248,"title":2249},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":2251,"title":2252},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":2254,"title":2255},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":2257,"title":2258},"\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":2260,"title":2261},"\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":2263,"title":2264},"\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":2266,"title":2267},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":2269,"title":2270},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":2272,"title":2273},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":2275,"title":2276},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":2278,"title":2279},"\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":2281,"title":2282},"\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",1785611671084]