[{"data":1,"prerenderedAt":1686},["ShallowReactive",2],{"page:\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fporting-shortcodes-and-includes-between-generators":3,"all-docs-nav":1444},{"id":4,"title":5,"body":6,"breadcrumb":1418,"dateModified":1428,"datePublished":1428,"description":1429,"extension":1430,"faq":1431,"meta":1438,"navigation":1001,"path":1439,"seo":1440,"slug":12,"stem":1441,"type":1442,"__hash__":1443},"content\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fporting-shortcodes-and-includes-between-generators\u002Findex.md","Porting Shortcodes and Includes Between Generators",{"type":7,"value":8,"toc":1400},"minimark",[9,13,17,26,31,44,48,51,220,223,259,291,412,460,464,467,470,490,497,611,614,618,621,624,784,899,902,906,914,917,1071,1074,1077,1081,1084,1172,1258,1262,1301,1305,1310,1314,1319,1325,1329,1332,1336,1339,1343,1346,1350,1353,1357,1396],[10,11,5],"h1",{"id":12},"porting-shortcodes-and-includes-between-generators",[14,15,16],"p",{},"Content is the easy part of an SSG migration. The hard part is the code embedded in it: the callouts, figures, tabbed panels, API tables and version badges that authors have been typing for years. Those constructs are the reason a migration takes weeks rather than days, and the reason a naive conversion leaves literal braces scattered through published pages.",[14,18,19,20,25],{},"This guide maps the four common systems onto each other, gives an order of work that keeps the build green throughout, and shows how to verify that a ported component produces the same HTML as the original. It is the template-level companion to ",[21,22,24],"a",{"href":23},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002F","Migrating Between Static Site Generators",".",[27,28,30],"h2",{"id":29},"prerequisites","Prerequisites",[32,33,34,38,41],"ul",{},[35,36,37],"li",{},"The shortcode census from phase 1 of the migration: every construct used in content, with a usage count.",[35,39,40],{},"Both generators building locally, so you can render the same fixture in each.",[35,42,43],{},"A decision about the target's component model — partial, shortcode or MDX component — before you start porting.",[27,45,47],{"id":46},"the-mapping","The Mapping",[14,49,50],{},"Every system offers the same three capabilities: a snippet used in layouts, a snippet used in content, and a snippet that wraps content. Only the spelling changes.",[52,53,54,216],"figure",{},[55,56,63,64,63,68,63,72,63,79],"svg",{"viewBox":57,"role":58,"ariaLabelledBy":59,"xmlns":62},"0 0 760 320","img",[60,61],"port-map-title","port-map-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[65,66,67],"title",{"id":60},"Equivalent constructs across four generators",[69,70,71],"desc",{"id":61},"A comparison matrix with three rows and four columns. For a layout snippet: Jekyll include, Hugo partial, Eleventy include, Astro component. For a content snippet: Jekyll include, Hugo shortcode, Eleventy shortcode, MDX component. For a wrapping snippet: Jekyll capture block, Hugo paired shortcode with dot Inner, Eleventy paired shortcode, MDX component with children.",[73,74],"rect",{"x":75,"y":75,"width":76,"height":77,"fill":78},"0","760","320","#ffffff",[80,81,83,84,83,92,83,99,83,103,83,108,83,113,83,119,83,129,83,134,83,137,83,140,83,143,83,145,83,149,83,152,83,156,83,161,83,165,83,169,83,172,83,176,83,178,83,182,83,185,83,189,83,192,83,196,83,198,83,201,83,203,83,206,83,208,83,211,63],"g",{"style":82},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[85,86,91],"text",{"x":87,"y":88,"fill":89,"style":90},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","One idea, four spellings",[85,93,98],{"x":94,"y":95,"fill":96,"style":97},"230","60","#d83b41","font-size:12px;font-weight:700;text-anchor:middle","Jekyll",[85,100,102],{"x":87,"y":95,"fill":101,"style":97},"#a97b00","Hugo",[85,104,107],{"x":105,"y":95,"fill":106,"style":97},"530","#166a9e","Eleventy",[85,109,112],{"x":110,"y":95,"fill":111,"style":97},"670","#5a8a16","Astro",[85,114,118],{"x":115,"y":116,"fill":89,"style":117},"40","100","font-size:12px;font-weight:700","In a layout",[73,120],{"x":121,"y":122,"width":123,"height":115,"rx":124,"fill":125,"opacity":126,"stroke":127,"style":128},"160","76","140","6","#d9e2ef","0.5","#556071","stroke-width:1.5px",[85,130,133],{"x":94,"y":131,"fill":89,"style":132},"101","font-size:11px;text-anchor:middle","include",[73,135],{"x":136,"y":122,"width":123,"height":115,"rx":124,"fill":125,"opacity":126,"stroke":127,"style":128},"310",[85,138,139],{"x":87,"y":131,"fill":89,"style":132},"partial",[73,141],{"x":142,"y":122,"width":123,"height":115,"rx":124,"fill":125,"opacity":126,"stroke":127,"style":128},"460",[85,144,133],{"x":105,"y":131,"fill":89,"style":132},[73,146],{"x":147,"y":122,"width":148,"height":115,"rx":124,"fill":125,"opacity":126,"stroke":127,"style":128},"610","120",[85,150,151],{"x":110,"y":131,"fill":89,"style":132},"component",[85,153,155],{"x":115,"y":154,"fill":89,"style":117},"172","In content",[73,157],{"x":121,"y":158,"width":123,"height":115,"rx":124,"fill":159,"opacity":160,"stroke":96,"style":128},"148","#ff595e","0.12",[85,162,164],{"x":94,"y":163,"fill":89,"style":132},"173","include (again)",[73,166],{"x":136,"y":158,"width":123,"height":115,"rx":124,"fill":167,"opacity":168,"stroke":101,"style":128},"#ffca3a","0.3",[85,170,171],{"x":87,"y":163,"fill":89,"style":132},"shortcode",[73,173],{"x":142,"y":158,"width":123,"height":115,"rx":124,"fill":174,"opacity":175,"stroke":174,"style":128},"#1982c4","0.14",[85,177,171],{"x":105,"y":163,"fill":89,"style":132},[73,179],{"x":147,"y":158,"width":148,"height":115,"rx":124,"fill":180,"opacity":181,"stroke":111,"style":128},"#8ac926","0.18",[85,183,184],{"x":110,"y":163,"fill":89,"style":132},"MDX component",[85,186,188],{"x":115,"y":187,"fill":89,"style":117},"244","Wrapping content",[73,190],{"x":121,"y":191,"width":123,"height":115,"rx":124,"fill":159,"opacity":160,"stroke":96,"style":128},"220",[85,193,195],{"x":94,"y":194,"fill":89,"style":132},"245","capture block",[73,197],{"x":136,"y":191,"width":123,"height":115,"rx":124,"fill":167,"opacity":168,"stroke":101,"style":128},[85,199,200],{"x":87,"y":194,"fill":89,"style":132},"paired · .Inner",[73,202],{"x":142,"y":191,"width":123,"height":115,"rx":124,"fill":174,"opacity":175,"stroke":174,"style":128},[85,204,205],{"x":105,"y":194,"fill":89,"style":132},"paired shortcode",[73,207],{"x":147,"y":191,"width":148,"height":115,"rx":124,"fill":180,"opacity":181,"stroke":111,"style":128},[85,209,210],{"x":110,"y":194,"fill":89,"style":132},"children \u002F slot",[85,212,215],{"x":115,"y":213,"fill":127,"style":214},"296","font-size:11px","Jekyll's single include tag covering two rows is why its migrations need a manual split",[217,218,219],"figcaption",{},"The mapping is mechanical except for the first column: Jekyll uses one construct for two jobs, so each call site has to be classified before it can be ported.",[14,221,222],{},"The same component in each system, for comparison — a callout that wraps content and takes a type:",[224,225,230],"pre",{"className":226,"code":227,"language":228,"meta":229,"style":229},"language-liquid shiki shiki-themes github-light github-dark","{%- comment -%} Jekyll: _includes\u002Fcallout.html {%- endcomment -%}\n\u003Caside class=\"callout callout-{{ include.type | default: 'note' }}\">\n  \u003Cstrong>{{ include.type | default: 'note' | capitalize }}:\u003C\u002Fstrong> {{ include.content }}\n\u003C\u002Faside>\n","liquid","",[231,232,233,241,247,253],"code",{"__ignoreMap":229},[234,235,238],"span",{"class":236,"line":237},"line",1,[234,239,240],{},"{%- comment -%} Jekyll: _includes\u002Fcallout.html {%- endcomment -%}\n",[234,242,244],{"class":236,"line":243},2,[234,245,246],{},"\u003Caside class=\"callout callout-{{ include.type | default: 'note' }}\">\n",[234,248,250],{"class":236,"line":249},3,[234,251,252],{},"  \u003Cstrong>{{ include.type | default: 'note' | capitalize }}:\u003C\u002Fstrong> {{ include.content }}\n",[234,254,256],{"class":236,"line":255},4,[234,257,258],{},"\u003C\u002Faside>\n",[224,260,264],{"className":261,"code":262,"language":263,"meta":229,"style":229},"language-go-html-template shiki shiki-themes github-light github-dark","{{\u002F* Hugo: layouts\u002Fshortcodes\u002Fcallout.html — {{\u003C callout type=\"warn\" >}}…{{\u003C \u002Fcallout >}} *\u002F}}\n{{ $type := .Get \"type\" | default \"note\" }}\n\u003Caside class=\"callout callout-{{ $type }}\">\n  \u003Cstrong>{{ $type | title }}:\u003C\u002Fstrong> {{ .Inner | markdownify }}\n\u003C\u002Faside>\n","go-html-template",[231,265,266,271,276,281,286],{"__ignoreMap":229},[234,267,268],{"class":236,"line":237},[234,269,270],{},"{{\u002F* Hugo: layouts\u002Fshortcodes\u002Fcallout.html — {{\u003C callout type=\"warn\" >}}…{{\u003C \u002Fcallout >}} *\u002F}}\n",[234,272,273],{"class":236,"line":243},[234,274,275],{},"{{ $type := .Get \"type\" | default \"note\" }}\n",[234,277,278],{"class":236,"line":249},[234,279,280],{},"\u003Caside class=\"callout callout-{{ $type }}\">\n",[234,282,283],{"class":236,"line":255},[234,284,285],{},"  \u003Cstrong>{{ $type | title }}:\u003C\u002Fstrong> {{ .Inner | markdownify }}\n",[234,287,289],{"class":236,"line":288},5,[234,290,258],{},[224,292,296],{"className":293,"code":294,"language":295,"meta":229,"style":229},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F Eleventy: eleventy.config.js — {% callout \"warn\" %}…{% endcallout %}\neleventyConfig.addPairedShortcode('callout', (content, type = 'note') =>\n  `\u003Caside class=\"callout callout-${type}\">` +\n  `\u003Cstrong>${type[0].toUpperCase() + type.slice(1)}:\u003C\u002Fstrong> ${content}\u003C\u002Faside>`);\n","javascript",[231,297,298,304,347,360],{"__ignoreMap":229},[234,299,300],{"class":236,"line":237},[234,301,303],{"class":302},"sJ8bj","\u002F\u002F Eleventy: eleventy.config.js — {% callout \"warn\" %}…{% endcallout %}\n",[234,305,306,310,314,317,321,324,328,331,334,338,341,344],{"class":236,"line":243},[234,307,309],{"class":308},"sVt8B","eleventyConfig.",[234,311,313],{"class":312},"sScJk","addPairedShortcode",[234,315,316],{"class":308},"(",[234,318,320],{"class":319},"sZZnC","'callout'",[234,322,323],{"class":308},", (",[234,325,327],{"class":326},"s4XuR","content",[234,329,330],{"class":308},", ",[234,332,333],{"class":326},"type",[234,335,337],{"class":336},"szBVR"," =",[234,339,340],{"class":319}," 'note'",[234,342,343],{"class":308},") ",[234,345,346],{"class":336},"=>\n",[234,348,349,352,354,357],{"class":236,"line":249},[234,350,351],{"class":319},"  `\u003Caside class=\"callout callout-${",[234,353,333],{"class":308},[234,355,356],{"class":319},"}\">`",[234,358,359],{"class":336}," +\n",[234,361,362,365,367,370,373,376,379,382,385,388,390,393,395,398,401,404,406,409],{"class":236,"line":255},[234,363,364],{"class":319},"  `\u003Cstrong>${",[234,366,333],{"class":308},[234,368,369],{"class":319},"[",[234,371,75],{"class":372},"sj4cs",[234,374,375],{"class":319},"].",[234,377,378],{"class":312},"toUpperCase",[234,380,381],{"class":319},"() ",[234,383,384],{"class":336},"+",[234,386,387],{"class":308}," type",[234,389,25],{"class":319},[234,391,392],{"class":312},"slice",[234,394,316],{"class":319},[234,396,397],{"class":372},"1",[234,399,400],{"class":319},")",[234,402,403],{"class":319},"}:\u003C\u002Fstrong> ${",[234,405,327],{"class":308},[234,407,408],{"class":319},"}\u003C\u002Faside>`",[234,410,411],{"class":308},");\n",[224,413,417],{"className":414,"code":415,"language":416,"meta":229,"style":229},"language-astro shiki shiki-themes github-light github-dark","---\n\u002F\u002F Astro: src\u002Fcomponents\u002FCallout.astro — \u003CCallout type=\"warn\">…\u003C\u002FCallout>\nconst { type = 'note' } = Astro.props;\n---\n\u003Caside class={`callout callout-${type}`}>\n  \u003Cstrong>{type[0].toUpperCase() + type.slice(1)}:\u003C\u002Fstrong>\n  \u003Cslot \u002F>\n\u003C\u002Faside>\n","astro",[231,418,419,424,429,434,438,443,449,455],{"__ignoreMap":229},[234,420,421],{"class":236,"line":237},[234,422,423],{},"---\n",[234,425,426],{"class":236,"line":243},[234,427,428],{},"\u002F\u002F Astro: src\u002Fcomponents\u002FCallout.astro — \u003CCallout type=\"warn\">…\u003C\u002FCallout>\n",[234,430,431],{"class":236,"line":249},[234,432,433],{},"const { type = 'note' } = Astro.props;\n",[234,435,436],{"class":236,"line":255},[234,437,423],{},[234,439,440],{"class":236,"line":288},[234,441,442],{},"\u003Caside class={`callout callout-${type}`}>\n",[234,444,446],{"class":236,"line":445},6,[234,447,448],{},"  \u003Cstrong>{type[0].toUpperCase() + type.slice(1)}:\u003C\u002Fstrong>\n",[234,450,452],{"class":236,"line":451},7,[234,453,454],{},"  \u003Cslot \u002F>\n",[234,456,458],{"class":236,"line":457},8,[234,459,258],{},[27,461,463],{"id":462},"port-in-usage-order-stub-the-tail","Port in Usage Order, Stub the Tail",[14,465,466],{},"The census tells you the order. Usage on real documentation sites is heavily skewed — five constructs typically cover 85-95% of call sites — so port those first and stub everything else so the build stays green.",[14,468,469],{},"A stub that renders its inner content is far more useful than a build error:",[224,471,473],{"className":261,"code":472,"language":263,"meta":229,"style":229},"{{\u002F* layouts\u002Fshortcodes\u002F_stub.html — copy for each unported shortcode *\u002F}}\n{{ warnf \"unported shortcode %q on %s\" .Name .Page.RelPermalink }}\n{{ .Inner | markdownify }}\n",[231,474,475,480,485],{"__ignoreMap":229},[234,476,477],{"class":236,"line":237},[234,478,479],{},"{{\u002F* layouts\u002Fshortcodes\u002F_stub.html — copy for each unported shortcode *\u002F}}\n",[234,481,482],{"class":236,"line":243},[234,483,484],{},"{{ warnf \"unported shortcode %q on %s\" .Name .Page.RelPermalink }}\n",[234,486,487],{"class":236,"line":249},[234,488,489],{},"{{ .Inner | markdownify }}\n",[14,491,492,493,496],{},"Now every build prints a list of what remains, the page still reads correctly, and you can work through the tail in priority order instead of being blocked by it. The equivalent in Eleventy is a shortcode that returns its content and logs; in Astro, a component that renders ",[231,494,495],{},"\u003Cslot \u002F>"," and warns during the build.",[498,499,500,516],"table",{},[501,502,503],"thead",{},[504,505,506,510,513],"tr",{},[507,508,509],"th",{},"Shortcode",[507,511,512],{},"Call sites",[507,514,515],{},"Decision",[517,518,519,544,557,574,587,600],"tbody",{},[504,520,521,526,529],{},[522,523,524],"td",{},[231,525,52],{},[522,527,528],{},"412",[522,530,531,532,330,535,330,538,330,541],{},"Port fully — carries ",[231,533,534],{},"src",[231,536,537],{},"alt",[231,539,540],{},"caption",[231,542,543],{},"width",[504,545,546,551,554],{},[522,547,548],{},[231,549,550],{},"callout",[522,552,553],{},"268",[522,555,556],{},"Port fully — three types in use",[504,558,559,568,571],{},[522,560,561,564,565],{},[231,562,563],{},"tabs"," \u002F ",[231,566,567],{},"tab",[522,569,570],{},"96",[522,572,573],{},"Port fully — paired, nested",[504,575,576,581,584],{},[522,577,578],{},[231,579,580],{},"version-badge",[522,582,583],{},"44",[522,585,586],{},"Port, simplify to one parameter",[504,588,589,594,597],{},[522,590,591],{},[231,592,593],{},"api-table",[522,595,596],{},"18",[522,598,599],{},"Rewrite as a data-driven component",[504,601,602,605,608],{},[522,603,604],{},"23 others",[522,606,607],{},"61 total",[522,609,610],{},"Inline as Markdown, delete the shortcode",[14,612,613],{},"That last row is the highest-value decision in the table. Replacing 61 one-off invocations with plain Markdown removes 23 constructs from the corpus permanently, which makes this migration smaller and the next one smaller still.",[27,615,617],{"id":616},"handle-the-data-fetching-ones-separately","Handle the Data-Fetching Ones Separately",[14,619,620],{},"Some shortcodes are not presentation — they fetch data at build time. A version badge that reads the latest release from an API, a table generated from an OpenAPI document, a list built from a data file. These are the expensive ports, because the data access API differs completely between generators.",[14,622,623],{},"The fix is to stop fetching inside the component. Move the fetch into a build step that writes a plain JSON file, and have the component read that file:",[224,625,627],{"className":293,"code":626,"language":295,"meta":229,"style":229},"\u002F\u002F scripts\u002Ffetch-versions.mjs — runs before the build, in every generator\nimport { writeFile } from 'node:fs\u002Fpromises';\nconst res = await fetch('https:\u002F\u002Fapi.example.com\u002Freleases\u002Flatest');\nif (!res.ok) throw new Error(`release fetch failed: ${res.status}`);\nconst { tag_name, published_at } = await res.json();\nawait writeFile('data\u002Fversions.json', JSON.stringify({ latest: tag_name, published_at }, null, 2));\n",[231,628,629,634,651,674,715,747],{"__ignoreMap":229},[234,630,631],{"class":236,"line":237},[234,632,633],{"class":302},"\u002F\u002F scripts\u002Ffetch-versions.mjs — runs before the build, in every generator\n",[234,635,636,639,642,645,648],{"class":236,"line":243},[234,637,638],{"class":336},"import",[234,640,641],{"class":308}," { writeFile } ",[234,643,644],{"class":336},"from",[234,646,647],{"class":319}," 'node:fs\u002Fpromises'",[234,649,650],{"class":308},";\n",[234,652,653,656,659,661,664,667,669,672],{"class":236,"line":249},[234,654,655],{"class":336},"const",[234,657,658],{"class":372}," res",[234,660,337],{"class":336},[234,662,663],{"class":336}," await",[234,665,666],{"class":312}," fetch",[234,668,316],{"class":308},[234,670,671],{"class":319},"'https:\u002F\u002Fapi.example.com\u002Freleases\u002Flatest'",[234,673,411],{"class":308},[234,675,676,679,682,685,688,691,694,697,699,702,705,707,710,713],{"class":236,"line":255},[234,677,678],{"class":336},"if",[234,680,681],{"class":308}," (",[234,683,684],{"class":336},"!",[234,686,687],{"class":308},"res.ok) ",[234,689,690],{"class":336},"throw",[234,692,693],{"class":336}," new",[234,695,696],{"class":312}," Error",[234,698,316],{"class":308},[234,700,701],{"class":319},"`release fetch failed: ${",[234,703,704],{"class":308},"res",[234,706,25],{"class":319},[234,708,709],{"class":308},"status",[234,711,712],{"class":319},"}`",[234,714,411],{"class":308},[234,716,717,719,722,725,727,730,733,736,738,741,744],{"class":236,"line":288},[234,718,655],{"class":336},[234,720,721],{"class":308}," { ",[234,723,724],{"class":372},"tag_name",[234,726,330],{"class":308},[234,728,729],{"class":372},"published_at",[234,731,732],{"class":308}," } ",[234,734,735],{"class":336},"=",[234,737,663],{"class":336},[234,739,740],{"class":308}," res.",[234,742,743],{"class":312},"json",[234,745,746],{"class":308},"();\n",[234,748,749,752,755,757,760,762,765,767,770,773,776,778,781],{"class":236,"line":445},[234,750,751],{"class":336},"await",[234,753,754],{"class":312}," writeFile",[234,756,316],{"class":308},[234,758,759],{"class":319},"'data\u002Fversions.json'",[234,761,330],{"class":308},[234,763,764],{"class":372},"JSON",[234,766,25],{"class":308},[234,768,769],{"class":312},"stringify",[234,771,772],{"class":308},"({ latest: tag_name, published_at }, ",[234,774,775],{"class":372},"null",[234,777,330],{"class":308},[234,779,780],{"class":372},"2",[234,782,783],{"class":308},"));\n",[52,785,786,896],{},[55,787,63,792,63,795,63,798,63,801,63,882],{"viewBox":788,"role":58,"ariaLabelledBy":789,"xmlns":62},"0 0 760 280",[790,791],"port-data-title","port-data-desc",[65,793,794],{"id":790},"Moving the fetch out of the component",[69,796,797],{"id":791},"Before: a shortcode fetches an API at render time, so the generator's data API is part of the component and the build needs network access. After: a build step fetches once and writes a JSON file, and the component in any generator reads that file, so only the fetch script is generator-independent code.",[73,799],{"x":75,"y":75,"width":76,"height":800,"fill":78},"280",[80,802,83,803,83,807,83,811,83,818,83,823,83,827,83,831,83,835,83,839,83,843,83,846,83,849,83,851,83,854,83,856,83,859,83,878,63],{"style":82},[85,804,806],{"x":87,"y":805,"fill":89,"style":90},"26","Fetch once in a script, not once per render",[85,808,810],{"x":115,"y":809,"fill":96,"style":117},"66","Before",[73,812],{"x":813,"y":814,"width":815,"height":816,"rx":817,"fill":159,"opacity":175,"stroke":96,"style":128},"130","52","180","46","8",[85,819,822],{"x":191,"y":820,"fill":89,"style":821},"80","font-size:12px;text-anchor:middle","shortcode fetches API",[73,824],{"x":825,"y":814,"width":815,"height":816,"rx":817,"fill":159,"opacity":826,"stroke":96,"style":128},"350","0.1",[85,828,830],{"x":829,"y":820,"fill":89,"style":821},"440","build needs network",[73,832],{"x":833,"y":814,"width":834,"height":816,"rx":817,"fill":159,"opacity":826,"stroke":96,"style":128},"570","150",[85,836,838],{"x":837,"y":820,"fill":89,"style":821},"645","flaky, per-generator",[85,840,842],{"x":115,"y":841,"fill":111,"style":117},"166","After",[73,844],{"x":813,"y":845,"width":815,"height":816,"rx":817,"fill":180,"opacity":181,"stroke":111,"style":128},"152",[85,847,848],{"x":191,"y":815,"fill":89,"style":821},"build step writes JSON",[73,850],{"x":825,"y":845,"width":815,"height":816,"rx":817,"fill":180,"opacity":175,"stroke":111,"style":128},[85,852,853],{"x":829,"y":815,"fill":89,"style":821},"component reads file",[73,855],{"x":833,"y":845,"width":834,"height":816,"rx":817,"fill":180,"opacity":175,"stroke":111,"style":128},[85,857,858],{"x":837,"y":815,"fill":89,"style":821},"offline, portable",[80,860,863,864,863,869,863,872,863,875,83],{"stroke":127,"fill":861,"style":862},"none","stroke-width:2px","\n      ",[865,866],"path",{"d":867,"style":868},"M312 75 L348 75","marker-end:url(#port-data-arrow)",[865,870],{"d":871,"style":868},"M532 75 L568 75",[865,873],{"d":874,"style":868},"M312 175 L348 175",[865,876],{"d":877,"style":868},"M532 175 L568 175",[85,879,881],{"x":115,"y":880,"fill":127,"style":214},"242","The JSON file can be committed, so a build with no network still produces identical output",[883,884,83,885,63],"defs",{},[886,887,863,893,83],"marker",{"id":888,"viewBox":889,"refX":817,"refY":890,"markerWidth":891,"markerHeight":891,"orient":892},"port-data-arrow","0 0 10 10","5","7","auto-start-reverse",[865,894],{"d":895,"fill":127},"M0 0 L10 5 L0 10 z",[217,897,898],{},"Splitting fetch from render makes the component trivially portable and the build reproducible — two problems solved by one refactor you were going to do anyway.",[14,900,901],{},"Now the component is pure presentation in every generator, the fetch is one Node script you keep as is, and the build is reproducible offline because the JSON file can be committed. This is worth doing even when you are not migrating: it removes network access from the render path, which is one of the most common causes of a flaky documentation build.",[27,903,905],{"id":904},"verify-with-a-fixture-diff","Verify With a Fixture Diff",[14,907,908,909,913],{},"A ported component that renders ",[910,911,912],"em",{},"almost"," the same HTML is a subtle defect that shows up as broken CSS on a handful of pages. Prove equivalence instead of assuming it.",[14,915,916],{},"Write one fixture page that exercises every parameter combination, render it in both generators, and diff the normalised output:",[224,918,922],{"className":919,"code":920,"language":921,"meta":229,"style":229},"language-bash shiki shiki-themes github-light github-dark","# Render the fixture in both, strip whitespace differences, diff\nrender_old() { (cd old && hugo --quiet && cat public\u002Ffixtures\u002Findex.html); }\nrender_new() { (cd new && npm run build --silent && cat dist\u002Ffixtures\u002Findex.html); }\n\ndiff \u003C(render_old | tr -s ' \\n' ' ' | sed 's\u002F> \u003C\u002F>\u003C\u002Fg') \\\n     \u003C(render_new | tr -s ' \\n' ' ' | sed 's\u002F> \u003C\u002F>\u003C\u002Fg') \\\n  && echo \"fixture parity: OK\"\n","bash",[231,923,924,929,963,997,1003,1037,1060],{"__ignoreMap":229},[234,925,926],{"class":236,"line":237},[234,927,928],{"class":302},"# Render the fixture in both, strip whitespace differences, diff\n",[234,930,931,934,937,940,943,946,949,952,954,957,960],{"class":236,"line":243},[234,932,933],{"class":312},"render_old",[234,935,936],{"class":308},"() { (",[234,938,939],{"class":372},"cd",[234,941,942],{"class":319}," old",[234,944,945],{"class":308}," && ",[234,947,948],{"class":312},"hugo",[234,950,951],{"class":372}," --quiet",[234,953,945],{"class":308},[234,955,956],{"class":312},"cat",[234,958,959],{"class":319}," public\u002Ffixtures\u002Findex.html",[234,961,962],{"class":308},"); }\n",[234,964,965,968,970,972,974,976,979,982,985,988,990,992,995],{"class":236,"line":249},[234,966,967],{"class":312},"render_new",[234,969,936],{"class":308},[234,971,939],{"class":372},[234,973,693],{"class":319},[234,975,945],{"class":308},[234,977,978],{"class":312},"npm",[234,980,981],{"class":319}," run",[234,983,984],{"class":319}," build",[234,986,987],{"class":372}," --silent",[234,989,945],{"class":308},[234,991,956],{"class":312},[234,993,994],{"class":319}," dist\u002Ffixtures\u002Findex.html",[234,996,962],{"class":308},[234,998,999],{"class":236,"line":255},[234,1000,1002],{"emptyLinePlaceholder":1001},true,"\n",[234,1004,1005,1008,1011,1013,1016,1019,1022,1025,1028,1031,1034],{"class":236,"line":288},[234,1006,1007],{"class":312},"diff",[234,1009,1010],{"class":319}," \u003C(",[234,1012,933],{"class":312},[234,1014,1015],{"class":336}," |",[234,1017,1018],{"class":312}," tr",[234,1020,1021],{"class":372}," -s",[234,1023,1024],{"class":319}," ' \\n' ' ' ",[234,1026,1027],{"class":336},"|",[234,1029,1030],{"class":312}," sed",[234,1032,1033],{"class":319}," 's\u002F> \u003C\u002F>\u003C\u002Fg')",[234,1035,1036],{"class":372}," \\\n",[234,1038,1039,1042,1044,1046,1048,1050,1052,1054,1056,1058],{"class":236,"line":445},[234,1040,1041],{"class":319},"     \u003C(",[234,1043,967],{"class":312},[234,1045,1015],{"class":336},[234,1047,1018],{"class":312},[234,1049,1021],{"class":372},[234,1051,1024],{"class":319},[234,1053,1027],{"class":336},[234,1055,1030],{"class":312},[234,1057,1033],{"class":319},[234,1059,1036],{"class":372},[234,1061,1062,1065,1068],{"class":236,"line":451},[234,1063,1064],{"class":308},"  && ",[234,1066,1067],{"class":372},"echo",[234,1069,1070],{"class":319}," \"fixture parity: OK\"\n",[14,1072,1073],{},"One detail makes the diff usable: normalise whitespace but nothing else. Collapsing attribute order or lowercasing tags hides real differences, and the two generators emit essentially the same HTML anyway once you ignore indentation. If a diff is noisy, the usual cause is a template that emits a wrapper element in one generator and not the other — which is a genuine difference worth resolving rather than normalising away.",[14,1075,1076],{},"Keep the fixture page in the repository afterwards. It becomes the regression test for every future change to those components, and it is the fastest way to answer \"did this refactor change any rendered output?\" — the same principle as the output diffing used during cutover in the parent guide.",[27,1078,1080],{"id":1079},"measured-impact","Measured Impact",[14,1082,1083],{},"From a 1,400-page documentation migration with 28 distinct shortcodes:",[498,1085,1086,1102],{},[501,1087,1088],{},[504,1089,1090,1093,1096,1099],{},[507,1091,1092],{},"Stage",[507,1094,1095],{},"Constructs remaining",[507,1097,1098],{},"Pages rendering correctly",[507,1100,1101],{},"Effort",[517,1103,1104,1118,1131,1145,1159],{},[504,1105,1106,1109,1112,1115],{},[522,1107,1108],{},"Conversion script only",[522,1110,1111],{},"28 unported",[522,1113,1114],{},"41%",[522,1116,1117],{},"0.5 day",[504,1119,1120,1123,1126,1129],{},[522,1121,1122],{},"Stubs added for all",[522,1124,1125],{},"28 stubbed",[522,1127,1128],{},"100% (degraded)",[522,1130,1117],{},[504,1132,1133,1136,1139,1142],{},[522,1134,1135],{},"Top 4 ported",[522,1137,1138],{},"24 stubbed",[522,1140,1141],{},"92% (fully correct)",[522,1143,1144],{},"2 days",[504,1146,1147,1150,1153,1156],{},[522,1148,1149],{},"Data-fetching ones moved to build step",[522,1151,1152],{},"22 stubbed",[522,1154,1155],{},"96%",[522,1157,1158],{},"1 day",[504,1160,1161,1164,1166,1169],{},[522,1162,1163],{},"Tail inlined or deleted",[522,1165,75],{},[522,1167,1168],{},"100%",[522,1170,1171],{},"1.5 days",[52,1173,1174,1255],{},[55,1175,63,1179,63,1182,63,1185,63,1187],{"viewBox":788,"role":58,"ariaLabelledBy":1176,"xmlns":62},[1177,1178],"port-burn-title","port-burn-desc",[65,1180,1181],{"id":1177},"Pages rendering correctly as porting progresses",[69,1183,1184],{"id":1178},"A line chart over five stages. After the conversion script only 41 percent of pages render correctly. Adding stubs takes every page to a readable state. Porting the top four shortcodes reaches 92 percent fully correct, moving data-fetching shortcodes to a build step reaches 96 percent, and inlining the tail reaches 100 percent.",[73,1186],{"x":75,"y":75,"width":76,"height":800,"fill":78},[80,1188,83,1189,83,1192,83,1196,83,1199,83,1202,83,1207,83,1212,83,1217,83,1222,83,1225,83,1229,83,1232,83,1236,83,1240,83,1244,83,1248,83,1251,63],{"style":82},[85,1190,1191],{"x":87,"y":805,"fill":89,"style":90},"Stubs first: never let the build be the blocker",[236,1193],{"x1":1194,"y1":814,"x2":1194,"y2":1195,"stroke":127,"style":128},"90","196",[236,1197],{"x1":1194,"y1":1195,"x2":1198,"y2":1195,"stroke":127,"style":128},"700",[85,1200,1168],{"x":816,"y":1201,"fill":127,"style":214},"58",[85,1203,1206],{"x":1204,"y":1205,"fill":127,"style":214},"56","200","0%",[1208,1209],"polyline",{"points":1210,"fill":861,"stroke":111,"style":1211},"130,111 270,62 410,64 550,58 670,52","stroke-width:2.5px",[85,1213,1114],{"x":1214,"y":1215,"fill":96,"style":1216},"132","102","font-size:11px;font-weight:700",[85,1218,1221],{"x":1219,"y":1220,"fill":101,"style":1216},"272","54","readable",[85,1223,1224],{"x":528,"y":1204,"fill":111,"style":1216},"92%",[85,1226,1155],{"x":1227,"y":1228,"fill":111,"style":1216},"552","50",[85,1230,1168],{"x":1231,"y":583,"fill":111,"style":1216},"640",[85,1233,1235],{"x":813,"y":1234,"fill":127,"style":132},"216","converted",[85,1237,1239],{"x":1238,"y":1234,"fill":127,"style":132},"270","stubbed",[85,1241,1243],{"x":1242,"y":1234,"fill":127,"style":132},"410","top 4 ported",[85,1245,1247],{"x":1246,"y":1234,"fill":127,"style":132},"550","data extracted",[85,1249,1250],{"x":110,"y":1234,"fill":127,"style":132},"tail inlined",[85,1252,1254],{"x":1194,"y":1253,"fill":127,"style":214},"252","1,400 pages, 28 shortcodes · the stub stage costs half a day and removes every blocked page",[217,1256,1257],{},"The second stage is the one teams skip. Stubbing everything means the rest of the migration proceeds against a fully building site, which is what lets template work and content review happen in parallel.",[27,1259,1261],{"id":1260},"pitfalls-rollback","Pitfalls & Rollback",[32,1263,1264,1271,1277,1283,1289,1295],{},[35,1265,1266,1270],{},[1267,1268,1269],"strong",{},"Porting alphabetically."," Port by usage count; the distribution is steep and the first five constructs decide when the site becomes usable.",[35,1272,1273,1276],{},[1267,1274,1275],{},"Not splitting Jekyll includes."," Layout includes become partials, content includes become shortcodes. Classify each call site before porting.",[35,1278,1279,1282],{},[1267,1280,1281],{},"Changing author-facing names."," A migration should be invisible in the authoring vocabulary; keep names and parameters even if they are not idiomatic in the target.",[35,1284,1285,1288],{},[1267,1286,1287],{},"Fetching data inside components."," It makes the port generator-specific and the build network-dependent. Extract to a build step first.",[35,1290,1291,1294],{},[1267,1292,1293],{},"Trusting a visual check."," Render a fixture and diff HTML; CSS hooks differ in ways that look fine on the one page you inspected.",[35,1296,1297,1300],{},[1267,1298,1299],{},"Rollback:"," each ported component is a single file. Reverting one to its stub restores a readable page immediately, so a bad port never blocks a deploy.",[27,1302,1304],{"id":1303},"conclusion","Conclusion",[14,1306,1307,1308,25],{},"Shortcodes are where migration effort actually goes, and the strategy that works is always the same: count usage, stub everything so the build stays green, port in usage order, extract data fetching into build steps, and prove equivalence with a fixture diff. Do that and the remaining handful becomes a deliberate cleanup rather than an obstacle. The surrounding process is in ",[21,1309,24],{"href":23},[27,1311,1313],{"id":1312},"faq","FAQ",[1315,1316,1318],"h3",{"id":1317},"what-is-the-difference-between-an-include-and-a-shortcode","What is the difference between an include and a shortcode?",[14,1320,1321,1322,1324],{},"An include is used inside a template and renders as part of the layout; a shortcode is used inside content and renders where an author placed it. Jekyll uses the same ",[231,1323,133],{}," tag for both jobs, which is why a Jekyll migration always involves splitting them into two categories in the target generator.",[1315,1326,1328],{"id":1327},"should-i-port-every-shortcode","Should I port every shortcode?",[14,1330,1331],{},"No. Port the ones that carry most of the usage, then decide case by case on the tail. A shortcode used twice is usually cheaper to inline as plain Markdown than to reimplement, and it makes the corpus more portable next time.",[1315,1333,1335],{"id":1334},"how-do-i-keep-authors-from-having-to-relearn-syntax","How do I keep authors from having to relearn syntax?",[14,1337,1338],{},"Keep the component names identical and accept the same parameter names, even when the target generator would idiomatically use different ones. The invocation syntax will change; the vocabulary does not have to.",[1315,1340,1342],{"id":1341},"what-about-shortcodes-that-fetch-data-at-build-time","What about shortcodes that fetch data at build time?",[14,1344,1345],{},"Those are the genuinely expensive ones, because the data access pattern differs per generator. Isolate the fetch into a build step that writes a data file, then have the component read the file — that way only the fetch has to be rewritten, not the component.",[1315,1347,1349],{"id":1348},"how-do-i-test-that-a-ported-component-matches-the-original","How do I test that a ported component matches the original?",[14,1351,1352],{},"Render a fixture page that exercises every parameter combination in both generators and diff the HTML output, ignoring whitespace. Any difference is either a bug or a decision you should make deliberately.",[27,1354,1356],{"id":1355},"related","Related",[32,1358,1359,1368,1375,1382,1389],{},[35,1360,1361,1364,1365,1367],{},[1267,1362,1363],{},"Parent:"," ",[21,1366,24],{"href":23}," — where this sits in the phased process.",[35,1369,1370,1374],{},[21,1371,1373],{"href":1372},"\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"," — the include split in practice.",[35,1376,1377,1381],{},[21,1378,1380],{"href":1379},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-hugo-to-astro-without-breaking-urls\u002F","Migrating From Hugo to Astro Without Breaking URLs"," — shortcodes as components.",[35,1383,1384,1388],{},[21,1385,1387],{"href":1386},"\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"," — the plugin-side equivalent.",[35,1390,1391,1395],{},[21,1392,1394],{"href":1393},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002F","Jekyll Plugin Ecosystem"," — what the includes were compensating for.",[1397,1398,1399],"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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":229,"searchDepth":243,"depth":243,"links":1401},[1402,1403,1404,1405,1406,1407,1408,1409,1410,1417],{"id":29,"depth":243,"text":30},{"id":46,"depth":243,"text":47},{"id":462,"depth":243,"text":463},{"id":616,"depth":243,"text":617},{"id":904,"depth":243,"text":905},{"id":1079,"depth":243,"text":1080},{"id":1260,"depth":243,"text":1261},{"id":1303,"depth":243,"text":1304},{"id":1312,"depth":243,"text":1313,"children":1411},[1412,1413,1414,1415,1416],{"id":1317,"depth":249,"text":1318},{"id":1327,"depth":249,"text":1328},{"id":1334,"depth":249,"text":1335},{"id":1341,"depth":249,"text":1342},{"id":1348,"depth":249,"text":1349},{"id":1355,"depth":243,"text":1356},[1419,1422,1425,1426],{"name":1420,"item":1421},"Home","\u002F",{"name":1423,"item":1424},"Choosing the Right Static Site Generator for Production","\u002Fchoosing-the-right-static-site-generator-for-production\u002F",{"name":24,"item":23},{"name":5,"item":1427},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fporting-shortcodes-and-includes-between-generators\u002F","2026-08-01","Map Jekyll includes, Hugo shortcodes, Eleventy shortcodes and MDX components onto each other — with a census-first strategy, stub-then-port order, and a rendering test per component.","md",[1432,1434,1435,1436,1437],{"q":1318,"a":1433},"An include is used inside a template and renders as part of the layout; a shortcode is used inside content and renders where an author placed it. Jekyll uses the same include tag for both jobs, which is why a Jekyll migration always involves splitting them into two categories in the target generator.",{"q":1328,"a":1331},{"q":1335,"a":1338},{"q":1342,"a":1345},{"q":1349,"a":1352},{},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fporting-shortcodes-and-includes-between-generators",{"title":5,"description":1429},"choosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fporting-shortcodes-and-includes-between-generators\u002Findex","article","2UuXlnzOQaz4YayZP3InueZ8TzjkFgn8CYI-061c3RI",[1445,1448,1451,1454,1457,1460,1463,1466,1468,1471,1473,1475,1478,1480,1483,1485,1487,1488,1491,1494,1497,1500,1503,1506,1509,1512,1515,1518,1521,1524,1527,1530,1533,1536,1539,1542,1545,1548,1551,1554,1557,1560,1563,1566,1569,1572,1575,1578,1581,1584,1587,1590,1593,1596,1599,1602,1605,1608,1611,1614,1617,1620,1623,1626,1629,1632,1635,1638,1641,1644,1647,1650,1653,1656,1659,1662,1665,1668,1671,1674,1677,1680,1683],{"path":1446,"title":1447},"\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":1449,"title":1450},"\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":1452,"title":1453},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1455,"title":1456},"\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":1458,"title":1459},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1461,"title":1462},"\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":1464,"title":1465},"\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":1467,"title":1423},"\u002Fchoosing-the-right-static-site-generator-for-production",{"path":1469,"title":1470},"\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":1472,"title":1394},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem",{"path":1474,"title":1387},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem\u002Freplacing-jekyll-plugins-when-migrating-to-eleventy",{"path":1476,"title":1477},"\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":1479,"title":24},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators",{"path":1481,"title":1482},"\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":1484,"title":1373},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-a-docs-site-from-jekyll-to-hugo",{"path":1486,"title":1380},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-hugo-to-astro-without-breaking-urls",{"path":1439,"title":5},{"path":1489,"title":1490},"\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":1492,"title":1493},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1495,"title":1496},"\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":1498,"title":1499},"\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":1501,"title":1502},"\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":1504,"title":1505},"\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":1507,"title":1508},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":1510,"title":1511},"\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":1513,"title":1514},"\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":1516,"title":1517},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":1519,"title":1520},"\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":1522,"title":1523},"\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":1525,"title":1526},"\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":1528,"title":1529},"\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":1531,"title":1532},"\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":1534,"title":1535},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":1537,"title":1538},"\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":1540,"title":1541},"\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":1543,"title":1544},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":1546,"title":1547},"\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":1549,"title":1550},"\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":1552,"title":1553},"\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":1555,"title":1556},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro","Image Optimization Pipelines in Astro",{"path":1558,"title":1559},"\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":1561,"title":1562},"\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":1564,"title":1565},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":1567,"title":1568},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":1570,"title":1571},"\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":1573,"title":1574},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":1576,"title":1577},"\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":1579,"title":1580},"\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":1582,"title":1583},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":1585,"title":1586},"\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":1588,"title":1589},"\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":1591,"title":1592},"\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":1594,"title":1595},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":1597,"title":1598},"\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":1600,"title":1601},"\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":1603,"title":1604},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":1606,"title":1607},"\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":1609,"title":1610},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":1612,"title":1613},"\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":1615,"title":1616},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams","Content Workflows for Documentation Teams",{"path":1618,"title":1619},"\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":1621,"title":1622},"\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":1624,"title":1625},"\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":1627,"title":1628},"\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":1630,"title":1631},"\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":1633,"title":1634},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":1636,"title":1637},"\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":1639,"title":1640},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":1642,"title":1643},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":1645,"title":1646},"\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":1648,"title":1649},"\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":1651,"title":1652},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":1654,"title":1655},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":1657,"title":1658},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates","Netlify Build Hooks for Content Updates",{"path":1660,"title":1661},"\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":1663,"title":1664},"\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":1666,"title":1667},"\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":1669,"title":1670},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":1672,"title":1673},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":1675,"title":1676},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":1678,"title":1679},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":1681,"title":1682},"\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":1684,"title":1685},"\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]