[{"data":1,"prerenderedAt":2154},["ShallowReactive",2],{"page:\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fwiring-a-headless-cms-to-a-static-build":3,"all-docs-nav":1911},{"id":4,"title":5,"body":6,"breadcrumb":1886,"dateModified":1896,"datePublished":1896,"description":1897,"extension":1898,"faq":1899,"meta":1905,"navigation":370,"path":1906,"seo":1907,"slug":12,"stem":1908,"type":1909,"__hash__":1910},"content\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fwiring-a-headless-cms-to-a-static-build\u002Findex.md","Wiring a Headless CMS to a Static Build",{"type":7,"value":8,"toc":1867},"minimark",[9,13,17,26,31,44,48,236,240,243,666,676,679,683,686,1032,1035,1039,1042,1351,1359,1468,1472,1475,1483,1486,1578,1582,1585,1662,1732,1736,1775,1779,1784,1788,1793,1796,1800,1803,1807,1810,1814,1817,1821,1824,1828,1863],[10,11,5],"h1",{"id":12},"wiring-a-headless-cms-to-a-static-build",[14,15,16],"p",{},"A CMS earns its place on a static site when the people who should be writing will not use a code host, or when the front matter has grown structured enough that hand-editing YAML produces errors. It does not earn its place merely because a site has content — plenty of documentation teams are better served by Markdown in a repository and a good review flow.",[14,18,19,20,25],{},"This guide covers both shapes: a Git-backed CMS that commits Markdown to your repository, and an API-backed CMS the build fetches from. It shows the content model, the authentication, the rebuild trigger and the media path, and it keeps the repository authoritative wherever possible. It is part of ",[21,22,24],"a",{"href":23},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002F","Content Workflows for Documentation Teams",".",[27,28,30],"h2",{"id":29},"prerequisites","Prerequisites",[32,33,34,38,41],"ul",{},[35,36,37],"li",{},"A static site whose content already has consistent front matter — the CMS describes what exists, so consistency comes first.",[35,39,40],{},"A deploy pipeline triggered by pushes to the default branch.",[35,42,43],{},"For an API-backed CMS: somewhere to store an access token that the build can read but the repository cannot leak.",[27,45,47],{"id":46},"two-architectures-two-trade-offs","Two Architectures, Two Trade-offs",[49,50,51,232],"figure",{},[52,53,60,61,60,65,60,69,60,76,60,218],"svg",{"viewBox":54,"role":55,"ariaLabelledBy":56,"xmlns":59},"0 0 760 320","img",[57,58],"cms-arch-title","cms-arch-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[62,63,64],"title",{"id":57},"Git-backed versus API-backed CMS architecture",[66,67,68],"desc",{"id":58},"Two architectures. In the Git-backed model the editor commits Markdown to the repository, which triggers the normal build and deploy pipeline, so the repository stays the source of truth. In the API-backed model the editor writes to a hosted content store, a webhook triggers a build, and the build fetches content over the network at build time.",[70,71],"rect",{"x":72,"y":72,"width":73,"height":74,"fill":75},"0","760","320","#ffffff",[77,78,80,81,80,89,80,96,80,106,80,112,80,118,80,123,80,128,80,133,80,137,80,144,80,148,80,153,80,156,80,159,80,163,80,167,80,171,80,173,80,176,80,180,80,182,80,184,80,209,80,214,60],"g",{"style":79},"font-family:system-ui, sans-serif;font-size:12px","\n    ",[82,83,88],"text",{"x":84,"y":85,"fill":86,"style":87},"380","28","#1f2937","font-size:16px;font-weight:700;text-anchor:middle","Where the content actually lives",[82,90,95],{"x":91,"y":92,"fill":93,"style":94},"40","64","#5a8a16","font-size:12px;font-weight:700","Git-backed",[70,97],{"x":98,"y":99,"width":100,"height":101,"rx":102,"fill":103,"opacity":104,"stroke":103,"style":105},"140","52","130","46","8","#6a4c93","0.14","stroke-width:1.5px",[82,107,111],{"x":108,"y":109,"fill":86,"style":110},"205","80","font-size:12px;text-anchor:middle","CMS editor UI",[70,113],{"x":114,"y":99,"width":115,"height":101,"rx":102,"fill":116,"opacity":117,"stroke":93,"style":105},"300","150","#8ac926","0.18",[82,119,122],{"x":120,"y":121,"fill":86,"style":110},"375","72","commit Markdown",[82,124,127],{"x":120,"y":125,"fill":93,"style":126},"90","font-size:11px;text-anchor:middle","repo is authoritative",[70,129],{"x":130,"y":99,"width":131,"height":101,"rx":102,"fill":132,"opacity":104,"stroke":132,"style":105},"480","120","#1982c4",[82,134,136],{"x":135,"y":109,"fill":86,"style":110},"540","normal CI",[70,138],{"x":139,"y":99,"width":140,"height":101,"rx":102,"fill":141,"opacity":142,"stroke":143,"style":105},"630","100","#d9e2ef","0.55","#556071",[82,145,147],{"x":146,"y":109,"fill":86,"style":110},"680","deploy",[82,149,152],{"x":91,"y":150,"fill":151,"style":94},"188","#a97b00","API-backed",[70,154],{"x":98,"y":155,"width":100,"height":101,"rx":102,"fill":103,"opacity":104,"stroke":103,"style":105},"176",[82,157,111],{"x":108,"y":158,"fill":86,"style":110},"204",[70,160],{"x":114,"y":155,"width":115,"height":101,"rx":102,"fill":161,"opacity":162,"stroke":151,"style":105},"#ffca3a","0.3",[82,164,166],{"x":120,"y":165,"fill":86,"style":110},"196","hosted content store",[82,168,170],{"x":120,"y":169,"fill":151,"style":126},"214","vendor is authoritative",[70,172],{"x":130,"y":155,"width":131,"height":101,"rx":102,"fill":132,"opacity":104,"stroke":132,"style":105},[82,174,175],{"x":135,"y":165,"fill":86,"style":110},"webhook →",[82,177,179],{"x":135,"y":178,"fill":86,"style":110},"212","build fetches",[70,181],{"x":139,"y":155,"width":140,"height":101,"rx":102,"fill":141,"opacity":142,"stroke":143,"style":105},[82,183,147],{"x":146,"y":158,"fill":86,"style":110},[77,185,188,189,188,194,188,197,188,200,188,203,188,206,80],{"stroke":143,"fill":186,"style":187},"none","stroke-width:2px","\n      ",[190,191],"path",{"d":192,"style":193},"M272 75 L298 75","marker-end:url(#cms-arrow)",[190,195],{"d":196,"style":193},"M452 75 L478 75",[190,198],{"d":199,"style":193},"M602 75 L628 75",[190,201],{"d":202,"style":193},"M272 199 L298 199",[190,204],{"d":205,"style":193},"M452 199 L478 199",[190,207],{"d":208,"style":193},"M602 199 L628 199",[82,210,213],{"x":91,"y":211,"fill":143,"style":212},"268","font-size:11px","Git-backed: branch previews, review, history and offline builds all keep working unchanged",[82,215,217],{"x":91,"y":216,"fill":143,"style":212},"290","API-backed: richer editorial features, but the build now depends on a network service being up",[219,220,80,221,60],"defs",{},[222,223,188,229,80],"marker",{"id":224,"viewBox":225,"refX":102,"refY":226,"markerWidth":227,"markerHeight":227,"orient":228},"cms-arrow","0 0 10 10","5","7","auto-start-reverse",[190,230],{"d":231,"fill":143},"M0 0 L10 5 L0 10 z",[233,234,235],"figcaption",{},"The architectural question is not which UI is nicer — it is whether a build six months from now can be reproduced from the repository alone.",[27,237,239],{"id":238},"git-backed-describe-the-content-you-already-have","Git-Backed: Describe the Content You Already Have",[14,241,242],{},"A Git-backed CMS is configuration, not migration. You describe the front matter that exists, and it edits the files that exist.",[244,245,250],"pre",{"className":246,"code":247,"language":248,"meta":249,"style":249},"language-yaml shiki shiki-themes github-light github-dark","# public\u002Fadmin\u002Fconfig.yml — Decap-style configuration\nbackend:\n  name: github\n  repo: acme\u002Fdocs-site\n  branch: main\n  # Editorial workflow creates a branch + pull request per entry instead of\n  # committing straight to main — this is what keeps review in the loop.\n  open_authoring: false\npublish_mode: editorial_workflow\nmedia_folder: content\u002Fuploads\npublic_folder: \u002Fuploads\n\ncollections:\n  - name: guides\n    label: Guides\n    folder: content\u002Fguides\n    create: true\n    slug: '{{slug}}'\n    path: '{{slug}}\u002Findex'\n    fields:\n      - { name: title, label: Title, widget: string, pattern: ['^.{10,70}$', '10–70 characters'] }\n      - { name: description, label: Description, widget: text,\n          pattern: ['^.{80,158}$', '80–158 characters for search snippets'] }\n      - { name: date, label: Publish date, widget: datetime }\n      - { name: draft, label: Draft, widget: boolean, default: true }\n      - { name: body, label: Body, widget: markdown }\n","yaml","",[251,252,253,262,273,286,297,308,314,320,332,343,354,365,372,380,394,405,416,427,438,449,457,509,541,559,592,634],"code",{"__ignoreMap":249},[254,255,258],"span",{"class":256,"line":257},"line",1,[254,259,261],{"class":260},"sJ8bj","# public\u002Fadmin\u002Fconfig.yml — Decap-style configuration\n",[254,263,265,269],{"class":256,"line":264},2,[254,266,268],{"class":267},"s9eBZ","backend",[254,270,272],{"class":271},"sVt8B",":\n",[254,274,276,279,282],{"class":256,"line":275},3,[254,277,278],{"class":267},"  name",[254,280,281],{"class":271},": ",[254,283,285],{"class":284},"sZZnC","github\n",[254,287,289,292,294],{"class":256,"line":288},4,[254,290,291],{"class":267},"  repo",[254,293,281],{"class":271},[254,295,296],{"class":284},"acme\u002Fdocs-site\n",[254,298,300,303,305],{"class":256,"line":299},5,[254,301,302],{"class":267},"  branch",[254,304,281],{"class":271},[254,306,307],{"class":284},"main\n",[254,309,311],{"class":256,"line":310},6,[254,312,313],{"class":260},"  # Editorial workflow creates a branch + pull request per entry instead of\n",[254,315,317],{"class":256,"line":316},7,[254,318,319],{"class":260},"  # committing straight to main — this is what keeps review in the loop.\n",[254,321,323,326,328],{"class":256,"line":322},8,[254,324,325],{"class":267},"  open_authoring",[254,327,281],{"class":271},[254,329,331],{"class":330},"sj4cs","false\n",[254,333,335,338,340],{"class":256,"line":334},9,[254,336,337],{"class":267},"publish_mode",[254,339,281],{"class":271},[254,341,342],{"class":284},"editorial_workflow\n",[254,344,346,349,351],{"class":256,"line":345},10,[254,347,348],{"class":267},"media_folder",[254,350,281],{"class":271},[254,352,353],{"class":284},"content\u002Fuploads\n",[254,355,357,360,362],{"class":256,"line":356},11,[254,358,359],{"class":267},"public_folder",[254,361,281],{"class":271},[254,363,364],{"class":284},"\u002Fuploads\n",[254,366,368],{"class":256,"line":367},12,[254,369,371],{"emptyLinePlaceholder":370},true,"\n",[254,373,375,378],{"class":256,"line":374},13,[254,376,377],{"class":267},"collections",[254,379,272],{"class":271},[254,381,383,386,389,391],{"class":256,"line":382},14,[254,384,385],{"class":271},"  - ",[254,387,388],{"class":267},"name",[254,390,281],{"class":271},[254,392,393],{"class":284},"guides\n",[254,395,397,400,402],{"class":256,"line":396},15,[254,398,399],{"class":267},"    label",[254,401,281],{"class":271},[254,403,404],{"class":284},"Guides\n",[254,406,408,411,413],{"class":256,"line":407},16,[254,409,410],{"class":267},"    folder",[254,412,281],{"class":271},[254,414,415],{"class":284},"content\u002Fguides\n",[254,417,419,422,424],{"class":256,"line":418},17,[254,420,421],{"class":267},"    create",[254,423,281],{"class":271},[254,425,426],{"class":330},"true\n",[254,428,430,433,435],{"class":256,"line":429},18,[254,431,432],{"class":267},"    slug",[254,434,281],{"class":271},[254,436,437],{"class":284},"'{{slug}}'\n",[254,439,441,444,446],{"class":256,"line":440},19,[254,442,443],{"class":267},"    path",[254,445,281],{"class":271},[254,447,448],{"class":284},"'{{slug}}\u002Findex'\n",[254,450,452,455],{"class":256,"line":451},20,[254,453,454],{"class":267},"    fields",[254,456,272],{"class":271},[254,458,460,463,465,467,469,472,475,477,480,482,485,487,490,492,495,498,501,503,506],{"class":256,"line":459},21,[254,461,462],{"class":271},"      - { ",[254,464,388],{"class":267},[254,466,281],{"class":271},[254,468,62],{"class":284},[254,470,471],{"class":271},", ",[254,473,474],{"class":267},"label",[254,476,281],{"class":271},[254,478,479],{"class":284},"Title",[254,481,471],{"class":271},[254,483,484],{"class":267},"widget",[254,486,281],{"class":271},[254,488,489],{"class":284},"string",[254,491,471],{"class":271},[254,493,494],{"class":267},"pattern",[254,496,497],{"class":271},": [",[254,499,500],{"class":284},"'^.{10,70}$'",[254,502,471],{"class":271},[254,504,505],{"class":284},"'10–70 characters'",[254,507,508],{"class":271},"] }\n",[254,510,512,514,516,518,521,523,525,527,530,532,534,536,538],{"class":256,"line":511},22,[254,513,462],{"class":271},[254,515,388],{"class":267},[254,517,281],{"class":271},[254,519,520],{"class":284},"description",[254,522,471],{"class":271},[254,524,474],{"class":267},[254,526,281],{"class":271},[254,528,529],{"class":284},"Description",[254,531,471],{"class":271},[254,533,484],{"class":267},[254,535,281],{"class":271},[254,537,82],{"class":284},[254,539,540],{"class":271},",\n",[254,542,544,547,549,552,554,557],{"class":256,"line":543},23,[254,545,546],{"class":267},"          pattern",[254,548,497],{"class":271},[254,550,551],{"class":284},"'^.{80,158}$'",[254,553,471],{"class":271},[254,555,556],{"class":284},"'80–158 characters for search snippets'",[254,558,508],{"class":271},[254,560,562,564,566,568,571,573,575,577,580,582,584,586,589],{"class":256,"line":561},24,[254,563,462],{"class":271},[254,565,388],{"class":267},[254,567,281],{"class":271},[254,569,570],{"class":284},"date",[254,572,471],{"class":271},[254,574,474],{"class":267},[254,576,281],{"class":271},[254,578,579],{"class":284},"Publish date",[254,581,471],{"class":271},[254,583,484],{"class":267},[254,585,281],{"class":271},[254,587,588],{"class":284},"datetime",[254,590,591],{"class":271}," }\n",[254,593,595,597,599,601,604,606,608,610,613,615,617,619,622,624,627,629,632],{"class":256,"line":594},25,[254,596,462],{"class":271},[254,598,388],{"class":267},[254,600,281],{"class":271},[254,602,603],{"class":284},"draft",[254,605,471],{"class":271},[254,607,474],{"class":267},[254,609,281],{"class":271},[254,611,612],{"class":284},"Draft",[254,614,471],{"class":271},[254,616,484],{"class":267},[254,618,281],{"class":271},[254,620,621],{"class":284},"boolean",[254,623,471],{"class":271},[254,625,626],{"class":267},"default",[254,628,281],{"class":271},[254,630,631],{"class":330},"true",[254,633,591],{"class":271},[254,635,637,639,641,643,646,648,650,652,655,657,659,661,664],{"class":256,"line":636},26,[254,638,462],{"class":271},[254,640,388],{"class":267},[254,642,281],{"class":271},[254,644,645],{"class":284},"body",[254,647,471],{"class":271},[254,649,474],{"class":267},[254,651,281],{"class":271},[254,653,654],{"class":284},"Body",[254,656,471],{"class":271},[254,658,484],{"class":267},[254,660,281],{"class":271},[254,662,663],{"class":284},"markdown",[254,665,591],{"class":271},[14,667,668,669,672,673,675],{},"Two settings carry most of the value. ",[251,670,671],{},"publish_mode: editorial_workflow"," makes each entry a branch and a pull request, so the preview and review process in the parent guide applies unchanged. And the field ",[251,674,494],{}," validations put the description-length rule in front of the author at the moment they write it, rather than in a build failure later.",[14,677,678],{},"Authentication is the part that surprises people: a browser-based CMS cannot hold a repository token, so it uses an OAuth flow through a small backend. Most hosts provide one; on Cloudflare or Netlify it is a few lines of Worker or function code, and it is worth confirming who can log in before pointing anyone at the URL.",[27,680,682],{"id":681},"api-backed-fetch-at-build-time-cache-deliberately","API-Backed: Fetch at Build Time, Cache Deliberately",[14,684,685],{},"When content lives in a hosted store, the build fetches it. Write that fetch as a discrete step that produces files on disk, rather than scattering API calls through templates:",[244,687,691],{"className":688,"code":689,"language":690,"meta":249,"style":249},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F scripts\u002Ffetch-content.mjs — run before the build\nimport { mkdir, writeFile } from 'node:fs\u002Fpromises';\nimport matter from 'gray-matter';\n\nconst res = await fetch(`${process.env.CMS_URL}\u002Fapi\u002Fguides?limit=500`, {\n  headers: { authorization: `Bearer ${process.env.CMS_TOKEN}` },\n});\nif (!res.ok) throw new Error(`content fetch failed: ${res.status} ${res.statusText}`);\nconst { items } = await res.json();\n\nawait mkdir('content\u002Fguides', { recursive: true });\nfor (const item of items) {\n  const front = {\n    title: item.title,\n    description: item.description,\n    date: item.publishedAt,\n    cmsId: item.id,            \u002F\u002F keeps the round trip traceable\n  };\n  await writeFile(`content\u002Fguides\u002F${item.slug}.md`, matter.stringify(item.body, front));\n}\nconsole.log(`fetched ${items.length} entries`);\n","javascript",[251,692,693,698,716,730,734,777,802,807,858,885,889,910,928,941,946,951,956,964,969,1002,1007],{"__ignoreMap":249},[254,694,695],{"class":256,"line":257},[254,696,697],{"class":260},"\u002F\u002F scripts\u002Ffetch-content.mjs — run before the build\n",[254,699,700,704,707,710,713],{"class":256,"line":264},[254,701,703],{"class":702},"szBVR","import",[254,705,706],{"class":271}," { mkdir, writeFile } ",[254,708,709],{"class":702},"from",[254,711,712],{"class":284}," 'node:fs\u002Fpromises'",[254,714,715],{"class":271},";\n",[254,717,718,720,723,725,728],{"class":256,"line":275},[254,719,703],{"class":702},[254,721,722],{"class":271}," matter ",[254,724,709],{"class":702},[254,726,727],{"class":284}," 'gray-matter'",[254,729,715],{"class":271},[254,731,732],{"class":256,"line":288},[254,733,371],{"emptyLinePlaceholder":370},[254,735,736,739,742,745,748,752,755,758,761,763,766,768,771,774],{"class":256,"line":299},[254,737,738],{"class":702},"const",[254,740,741],{"class":330}," res",[254,743,744],{"class":702}," =",[254,746,747],{"class":702}," await",[254,749,751],{"class":750},"sScJk"," fetch",[254,753,754],{"class":271},"(",[254,756,757],{"class":284},"`${",[254,759,760],{"class":271},"process",[254,762,25],{"class":284},[254,764,765],{"class":271},"env",[254,767,25],{"class":284},[254,769,770],{"class":330},"CMS_URL",[254,772,773],{"class":284},"}\u002Fapi\u002Fguides?limit=500`",[254,775,776],{"class":271},", {\n",[254,778,779,782,785,787,789,791,793,796,799],{"class":256,"line":310},[254,780,781],{"class":271},"  headers: { authorization: ",[254,783,784],{"class":284},"`Bearer ${",[254,786,760],{"class":271},[254,788,25],{"class":284},[254,790,765],{"class":271},[254,792,25],{"class":284},[254,794,795],{"class":330},"CMS_TOKEN",[254,797,798],{"class":284},"}`",[254,800,801],{"class":271}," },\n",[254,803,804],{"class":256,"line":316},[254,805,806],{"class":271},"});\n",[254,808,809,812,815,818,821,824,827,830,832,835,838,840,843,846,848,850,853,855],{"class":256,"line":322},[254,810,811],{"class":702},"if",[254,813,814],{"class":271}," (",[254,816,817],{"class":702},"!",[254,819,820],{"class":271},"res.ok) ",[254,822,823],{"class":702},"throw",[254,825,826],{"class":702}," new",[254,828,829],{"class":750}," Error",[254,831,754],{"class":271},[254,833,834],{"class":284},"`content fetch failed: ${",[254,836,837],{"class":271},"res",[254,839,25],{"class":284},[254,841,842],{"class":271},"status",[254,844,845],{"class":284},"} ${",[254,847,837],{"class":271},[254,849,25],{"class":284},[254,851,852],{"class":271},"statusText",[254,854,798],{"class":284},[254,856,857],{"class":271},");\n",[254,859,860,862,865,868,871,874,876,879,882],{"class":256,"line":334},[254,861,738],{"class":702},[254,863,864],{"class":271}," { ",[254,866,867],{"class":330},"items",[254,869,870],{"class":271}," } ",[254,872,873],{"class":702},"=",[254,875,747],{"class":702},[254,877,878],{"class":271}," res.",[254,880,881],{"class":750},"json",[254,883,884],{"class":271},"();\n",[254,886,887],{"class":256,"line":345},[254,888,371],{"emptyLinePlaceholder":370},[254,890,891,894,897,899,902,905,907],{"class":256,"line":356},[254,892,893],{"class":702},"await",[254,895,896],{"class":750}," mkdir",[254,898,754],{"class":271},[254,900,901],{"class":284},"'content\u002Fguides'",[254,903,904],{"class":271},", { recursive: ",[254,906,631],{"class":330},[254,908,909],{"class":271}," });\n",[254,911,912,915,917,919,922,925],{"class":256,"line":367},[254,913,914],{"class":702},"for",[254,916,814],{"class":271},[254,918,738],{"class":702},[254,920,921],{"class":330}," item",[254,923,924],{"class":702}," of",[254,926,927],{"class":271}," items) {\n",[254,929,930,933,936,938],{"class":256,"line":374},[254,931,932],{"class":702},"  const",[254,934,935],{"class":330}," front",[254,937,744],{"class":702},[254,939,940],{"class":271}," {\n",[254,942,943],{"class":256,"line":382},[254,944,945],{"class":271},"    title: item.title,\n",[254,947,948],{"class":256,"line":396},[254,949,950],{"class":271},"    description: item.description,\n",[254,952,953],{"class":256,"line":407},[254,954,955],{"class":271},"    date: item.publishedAt,\n",[254,957,958,961],{"class":256,"line":418},[254,959,960],{"class":271},"    cmsId: item.id,            ",[254,962,963],{"class":260},"\u002F\u002F keeps the round trip traceable\n",[254,965,966],{"class":256,"line":429},[254,967,968],{"class":271},"  };\n",[254,970,971,974,977,979,982,985,987,990,993,996,999],{"class":256,"line":440},[254,972,973],{"class":702},"  await",[254,975,976],{"class":750}," writeFile",[254,978,754],{"class":271},[254,980,981],{"class":284},"`content\u002Fguides\u002F${",[254,983,984],{"class":271},"item",[254,986,25],{"class":284},[254,988,989],{"class":271},"slug",[254,991,992],{"class":284},"}.md`",[254,994,995],{"class":271},", matter.",[254,997,998],{"class":750},"stringify",[254,1000,1001],{"class":271},"(item.body, front));\n",[254,1003,1004],{"class":256,"line":451},[254,1005,1006],{"class":271},"}\n",[254,1008,1009,1012,1015,1017,1020,1022,1024,1027,1030],{"class":256,"line":459},[254,1010,1011],{"class":271},"console.",[254,1013,1014],{"class":750},"log",[254,1016,754],{"class":271},[254,1018,1019],{"class":284},"`fetched ${",[254,1021,867],{"class":271},[254,1023,25],{"class":284},[254,1025,1026],{"class":330},"length",[254,1028,1029],{"class":284},"} entries`",[254,1031,857],{"class":271},[14,1033,1034],{},"Three properties make this worth the extra file. The build fails loudly if the API is unavailable, instead of silently producing a site with missing pages. The fetched Markdown can be committed to a cache branch so a build is reproducible without the vendor. And every downstream tool — link checks, spell checks, the generator itself — sees ordinary Markdown, so nothing else in the pipeline has to know a CMS exists.",[27,1036,1038],{"id":1037},"trigger-rebuilds-without-thrashing","Trigger Rebuilds Without Thrashing",[14,1040,1041],{},"A publish event should produce exactly one build. Without debouncing, an editor fixing five typos produces five builds, each cancelling or queueing behind the last.",[244,1043,1045],{"className":688,"code":1044,"language":690,"meta":249,"style":249},"\u002F\u002F functions\u002Fcms-hook.js — debounced deploy trigger at the edge\nexport async function onRequestPost({ request, env }) {\n  const sig = request.headers.get('x-cms-signature');\n  if (sig !== env.CMS_WEBHOOK_SECRET) return new Response('forbidden', { status: 403 });\n\n  const now = Date.now();\n  const last = Number((await env.KV.get('last-build')) || 0);\n  if (now - last \u003C 120_000) {                 \u002F\u002F 2-minute debounce window\n    await env.KV.put('pending', '1');\n    return new Response('debounced', { status: 202 });\n  }\n  await env.KV.put('last-build', String(now));\n  await fetch(env.DEPLOY_HOOK_URL, { method: 'POST' });\n  return new Response('triggered', { status: 202 });\n}\n",[251,1046,1047,1052,1080,1102,1143,1147,1164,1206,1231,1257,1278,1283,1307,1327,1347],{"__ignoreMap":249},[254,1048,1049],{"class":256,"line":257},[254,1050,1051],{"class":260},"\u002F\u002F functions\u002Fcms-hook.js — debounced deploy trigger at the edge\n",[254,1053,1054,1057,1060,1063,1066,1069,1073,1075,1077],{"class":256,"line":264},[254,1055,1056],{"class":702},"export",[254,1058,1059],{"class":702}," async",[254,1061,1062],{"class":702}," function",[254,1064,1065],{"class":750}," onRequestPost",[254,1067,1068],{"class":271},"({ ",[254,1070,1072],{"class":1071},"s4XuR","request",[254,1074,471],{"class":271},[254,1076,765],{"class":1071},[254,1078,1079],{"class":271}," }) {\n",[254,1081,1082,1084,1087,1089,1092,1095,1097,1100],{"class":256,"line":275},[254,1083,932],{"class":702},[254,1085,1086],{"class":330}," sig",[254,1088,744],{"class":702},[254,1090,1091],{"class":271}," request.headers.",[254,1093,1094],{"class":750},"get",[254,1096,754],{"class":271},[254,1098,1099],{"class":284},"'x-cms-signature'",[254,1101,857],{"class":271},[254,1103,1104,1107,1110,1113,1116,1119,1122,1125,1127,1130,1132,1135,1138,1141],{"class":256,"line":288},[254,1105,1106],{"class":702},"  if",[254,1108,1109],{"class":271}," (sig ",[254,1111,1112],{"class":702},"!==",[254,1114,1115],{"class":271}," env.",[254,1117,1118],{"class":330},"CMS_WEBHOOK_SECRET",[254,1120,1121],{"class":271},") ",[254,1123,1124],{"class":702},"return",[254,1126,826],{"class":702},[254,1128,1129],{"class":750}," Response",[254,1131,754],{"class":271},[254,1133,1134],{"class":284},"'forbidden'",[254,1136,1137],{"class":271},", { status: ",[254,1139,1140],{"class":330},"403",[254,1142,909],{"class":271},[254,1144,1145],{"class":256,"line":299},[254,1146,371],{"emptyLinePlaceholder":370},[254,1148,1149,1151,1154,1156,1159,1162],{"class":256,"line":310},[254,1150,932],{"class":702},[254,1152,1153],{"class":330}," now",[254,1155,744],{"class":702},[254,1157,1158],{"class":271}," Date.",[254,1160,1161],{"class":750},"now",[254,1163,884],{"class":271},[254,1165,1166,1168,1171,1173,1176,1179,1181,1183,1186,1188,1190,1192,1195,1198,1201,1204],{"class":256,"line":316},[254,1167,932],{"class":702},[254,1169,1170],{"class":330}," last",[254,1172,744],{"class":702},[254,1174,1175],{"class":750}," Number",[254,1177,1178],{"class":271},"((",[254,1180,893],{"class":702},[254,1182,1115],{"class":271},[254,1184,1185],{"class":330},"KV",[254,1187,25],{"class":271},[254,1189,1094],{"class":750},[254,1191,754],{"class":271},[254,1193,1194],{"class":284},"'last-build'",[254,1196,1197],{"class":271},")) ",[254,1199,1200],{"class":702},"||",[254,1202,1203],{"class":330}," 0",[254,1205,857],{"class":271},[254,1207,1208,1210,1213,1216,1219,1222,1225,1228],{"class":256,"line":322},[254,1209,1106],{"class":702},[254,1211,1212],{"class":271}," (now ",[254,1214,1215],{"class":702},"-",[254,1217,1218],{"class":271}," last ",[254,1220,1221],{"class":702},"\u003C",[254,1223,1224],{"class":330}," 120_000",[254,1226,1227],{"class":271},") {                 ",[254,1229,1230],{"class":260},"\u002F\u002F 2-minute debounce window\n",[254,1232,1233,1236,1238,1240,1242,1245,1247,1250,1252,1255],{"class":256,"line":334},[254,1234,1235],{"class":702},"    await",[254,1237,1115],{"class":271},[254,1239,1185],{"class":330},[254,1241,25],{"class":271},[254,1243,1244],{"class":750},"put",[254,1246,754],{"class":271},[254,1248,1249],{"class":284},"'pending'",[254,1251,471],{"class":271},[254,1253,1254],{"class":284},"'1'",[254,1256,857],{"class":271},[254,1258,1259,1262,1264,1266,1268,1271,1273,1276],{"class":256,"line":345},[254,1260,1261],{"class":702},"    return",[254,1263,826],{"class":702},[254,1265,1129],{"class":750},[254,1267,754],{"class":271},[254,1269,1270],{"class":284},"'debounced'",[254,1272,1137],{"class":271},[254,1274,1275],{"class":330},"202",[254,1277,909],{"class":271},[254,1279,1280],{"class":256,"line":356},[254,1281,1282],{"class":271},"  }\n",[254,1284,1285,1287,1289,1291,1293,1295,1297,1299,1301,1304],{"class":256,"line":367},[254,1286,973],{"class":702},[254,1288,1115],{"class":271},[254,1290,1185],{"class":330},[254,1292,25],{"class":271},[254,1294,1244],{"class":750},[254,1296,754],{"class":271},[254,1298,1194],{"class":284},[254,1300,471],{"class":271},[254,1302,1303],{"class":750},"String",[254,1305,1306],{"class":271},"(now));\n",[254,1308,1309,1311,1313,1316,1319,1322,1325],{"class":256,"line":374},[254,1310,973],{"class":702},[254,1312,751],{"class":750},[254,1314,1315],{"class":271},"(env.",[254,1317,1318],{"class":330},"DEPLOY_HOOK_URL",[254,1320,1321],{"class":271},", { method: ",[254,1323,1324],{"class":284},"'POST'",[254,1326,909],{"class":271},[254,1328,1329,1332,1334,1336,1338,1341,1343,1345],{"class":256,"line":382},[254,1330,1331],{"class":702},"  return",[254,1333,826],{"class":702},[254,1335,1129],{"class":750},[254,1337,754],{"class":271},[254,1339,1340],{"class":284},"'triggered'",[254,1342,1137],{"class":271},[254,1344,1275],{"class":330},[254,1346,909],{"class":271},[254,1348,1349],{"class":256,"line":396},[254,1350,1006],{"class":271},[14,1352,1353,1354,1358],{},"Verify the signature: a deploy hook URL that anyone can call is a free denial-of-wallet on your build minutes. The scheduled-rebuild pattern in ",[21,1355,1357],{"href":1356},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fscheduling-content-publication-with-cron-triggered-builds\u002F","Scheduling Content Publication With Cron-Triggered Builds"," pairs naturally with the debounce, since the cron sweeps up anything the window swallowed.",[49,1360,1361,1465],{},[52,1362,60,1367,60,1370,60,1373,60,1376],{"viewBox":1363,"role":55,"ariaLabelledBy":1364,"xmlns":59},"0 0 760 280",[1365,1366],"cms-debounce-title","cms-debounce-desc",[62,1368,1369],{"id":1365},"Publish events with and without debouncing",[66,1371,1372],{"id":1366},"Two timelines over ten minutes. Without debouncing, six publish events produce six builds, several overlapping and cancelling each other. With a two-minute debounce, the same six events produce two builds, and a scheduled sweep catches anything published inside the final window.",[70,1374],{"x":72,"y":72,"width":73,"height":1375,"fill":75},"280",[77,1377,80,1378,80,1382,80,1387,80,1393,80,1398,80,1401,80,1404,80,1407,80,1410,80,1413,80,1416,80,1419,80,1422,80,1425,80,1427,80,1431,80,1436,80,1441,80,1443,80,1446,80,1449,80,1453,80,1457,80,1461,60],{"style":79},[82,1379,1381],{"x":84,"y":1380,"fill":86,"style":87},"26","Six edits, two builds",[82,1383,1386],{"x":91,"y":1384,"fill":1385,"style":94},"70","#d83b41","No debounce",[70,1388],{"x":115,"y":1389,"width":109,"height":1390,"rx":226,"fill":1391,"opacity":1392,"stroke":1385,"style":105},"56","30","#ff595e","0.16",[82,1394,1397],{"x":1395,"y":1396,"fill":86,"style":126},"190","76","build",[70,1399],{"x":1400,"y":1389,"width":109,"height":1390,"rx":226,"fill":1391,"opacity":1392,"stroke":1385,"style":105},"238",[82,1402,1397],{"x":1403,"y":1396,"fill":86,"style":126},"278",[70,1405],{"x":1406,"y":1389,"width":109,"height":1390,"rx":226,"fill":1391,"opacity":1392,"stroke":1385,"style":105},"326",[82,1408,1397],{"x":1409,"y":1396,"fill":86,"style":126},"366",[70,1411],{"x":1412,"y":1389,"width":109,"height":1390,"rx":226,"fill":1391,"opacity":1392,"stroke":1385,"style":105},"414",[82,1414,1397],{"x":1415,"y":1396,"fill":86,"style":126},"454",[70,1417],{"x":1418,"y":1389,"width":109,"height":1390,"rx":226,"fill":1391,"opacity":1392,"stroke":1385,"style":105},"502",[82,1420,1397],{"x":1421,"y":1396,"fill":86,"style":126},"542",[70,1423],{"x":1424,"y":1389,"width":109,"height":1390,"rx":226,"fill":1391,"opacity":1392,"stroke":1385,"style":105},"590",[82,1426,1397],{"x":139,"y":1396,"fill":86,"style":126},[82,1428,1430],{"x":91,"y":1429,"fill":93,"style":94},"160","2-minute debounce",[70,1432],{"x":115,"y":1433,"width":1434,"height":1390,"rx":226,"fill":116,"opacity":1435,"stroke":93,"style":105},"146","180","0.2",[82,1437,1440],{"x":1438,"y":1439,"fill":86,"style":126},"240","166","build · covers 3 edits",[70,1442],{"x":1412,"y":1433,"width":1434,"height":1390,"rx":226,"fill":116,"opacity":1435,"stroke":93,"style":105},[82,1444,1440],{"x":1445,"y":1439,"fill":86,"style":126},"504",[256,1447],{"x1":115,"y1":158,"x2":1448,"y2":158,"stroke":143,"style":105},"700",[82,1450,1452],{"x":115,"y":1451,"fill":143,"style":212},"224","0 min",[82,1454,1456],{"x":1455,"y":1451,"fill":143,"style":212},"410","5 min",[82,1458,1460],{"x":1459,"y":1451,"fill":143,"style":212},"660","10 min",[82,1462,1464],{"x":91,"y":1463,"fill":143,"style":212},"256","The hourly scheduled build is the backstop for an edit that lands in the last debounce window",[233,1466,1467],{},"Debouncing costs a couple of minutes of publish latency and removes most of the build minutes — and, on hosts that cancel in-flight builds, it removes a class of confusing half-deploys.",[27,1469,1471],{"id":1470},"media-constrain-at-the-source","Media: Constrain at the Source",[14,1473,1474],{},"Uploads are where a CMS most often damages a static site. A phone photo is 4-8 MB, and an editor with no guidance will happily insert one at full size.",[14,1476,1477,1478,1482],{},"Constrain in the CMS configuration (maximum dimensions and file size on the image widget), store the original in the repository or the CMS media store, and let the build produce the responsive variants. Never serve the upload directly — the pipeline described in ",[21,1479,1481],{"href":1480},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro\u002F","Image Optimization Pipelines in Astro"," exists for exactly this, and it also stamps the dimensions that keep layout stable.",[14,1484,1485],{},"Add a build check as the backstop:",[244,1487,1491],{"className":1488,"code":1489,"language":1490,"meta":249,"style":249},"language-bash shiki shiki-themes github-light github-dark","# Fail the build if any committed upload exceeds 1.5 MB\nfind content\u002Fuploads -type f -size +1500k -printf '%s\\t%p\\n' \\\n  | tee \u002Fdev\u002Fstderr | grep -q . && { echo \"oversized upload(s) — resize before merging\"; exit 1; }\nexit 0\n","bash",[251,1492,1493,1498,1527,1571],{"__ignoreMap":249},[254,1494,1495],{"class":256,"line":257},[254,1496,1497],{"class":260},"# Fail the build if any committed upload exceeds 1.5 MB\n",[254,1499,1500,1503,1506,1509,1512,1515,1518,1521,1524],{"class":256,"line":264},[254,1501,1502],{"class":750},"find",[254,1504,1505],{"class":284}," content\u002Fuploads",[254,1507,1508],{"class":330}," -type",[254,1510,1511],{"class":284}," f",[254,1513,1514],{"class":330}," -size",[254,1516,1517],{"class":284}," +1500k",[254,1519,1520],{"class":330}," -printf",[254,1522,1523],{"class":284}," '%s\\t%p\\n'",[254,1525,1526],{"class":330}," \\\n",[254,1528,1529,1532,1535,1538,1541,1544,1547,1550,1553,1556,1559,1562,1565,1568],{"class":256,"line":275},[254,1530,1531],{"class":702},"  |",[254,1533,1534],{"class":750}," tee",[254,1536,1537],{"class":284}," \u002Fdev\u002Fstderr",[254,1539,1540],{"class":702}," |",[254,1542,1543],{"class":750}," grep",[254,1545,1546],{"class":330}," -q",[254,1548,1549],{"class":284}," .",[254,1551,1552],{"class":271}," && { ",[254,1554,1555],{"class":330},"echo",[254,1557,1558],{"class":284}," \"oversized upload(s) — resize before merging\"",[254,1560,1561],{"class":271},"; ",[254,1563,1564],{"class":330},"exit",[254,1566,1567],{"class":330}," 1",[254,1569,1570],{"class":271},"; }\n",[254,1572,1573,1575],{"class":256,"line":288},[254,1574,1564],{"class":330},[254,1576,1577],{"class":330}," 0\n",[27,1579,1581],{"id":1580},"measured-impact","Measured Impact",[14,1583,1584],{},"A documentation team of eleven, of whom three were comfortable with a repository, over one quarter before and after adding a Git-backed CMS with editorial workflow:",[1586,1587,1588,1604],"table",{},[1589,1590,1591],"thead",{},[1592,1593,1594,1598,1601],"tr",{},[1595,1596,1597],"th",{},"Measure",[1595,1599,1600],{},"Before",[1595,1602,1603],{},"After",[1605,1606,1607,1619,1630,1641,1652],"tbody",{},[1592,1608,1609,1613,1616],{},[1610,1611,1612],"td",{},"Contributors publishing at least once",[1610,1614,1615],{},"3",[1610,1617,1618],{},"9",[1592,1620,1621,1624,1627],{},[1610,1622,1623],{},"Median time from draft to published",[1610,1625,1626],{},"6.2 days",[1610,1628,1629],{},"0.6 days",[1592,1631,1632,1635,1638],{},[1610,1633,1634],{},"Front-matter errors reaching review",[1610,1636,1637],{},"14",[1610,1639,1640],{},"1",[1592,1642,1643,1646,1649],{},[1610,1644,1645],{},"Builds triggered per publishing day",[1610,1647,1648],{},"4.1",[1610,1650,1651],{},"5.8",[1592,1653,1654,1657,1659],{},[1610,1655,1656],{},"Oversized images committed",[1610,1658,227],{},[1610,1660,1661],{},"0 (blocked at upload)",[49,1663,1664,1729],{},[52,1665,60,1669,60,1672,60,1675,60,1677],{"viewBox":1363,"role":55,"ariaLabelledBy":1666,"xmlns":59},[1667,1668],"cms-impact-title","cms-impact-desc",[62,1670,1671],{"id":1667},"Contributors and errors before and after adding the CMS",[66,1673,1674],{"id":1668},"Two paired bars. Contributors publishing at least once rose from three to nine out of eleven team members. Front-matter errors reaching review fell from fourteen to one, because the CMS validated fields at entry time.",[70,1676],{"x":72,"y":72,"width":73,"height":1375,"fill":75},[77,1678,80,1679,80,1682,80,1686,80,1691,80,1697,80,1702,80,1707,80,1711,80,1713,80,1718,80,1722,80,1725,60],{"style":79},[82,1680,1681],{"x":84,"y":1380,"fill":86,"style":87},"Same team, same repository, different front door",[82,1683,1685],{"x":1684,"y":109,"fill":86,"style":94},"60","Publishing contributors",[70,1687],{"x":216,"y":1688,"width":1689,"height":1390,"rx":226,"fill":161,"opacity":1690,"stroke":151,"style":105},"62","110","0.34",[82,1692,1696],{"x":1693,"y":1694,"fill":86,"style":1695},"345","82","font-size:12px;font-weight:700;text-anchor:middle","3 of 11",[70,1698],{"x":216,"y":1699,"width":1700,"height":1390,"rx":226,"fill":116,"opacity":1701,"stroke":93,"style":105},"98","330","0.24",[82,1703,1706],{"x":1704,"y":1705,"fill":86,"style":1695},"455","118","9 of 11",[82,1708,1710],{"x":1684,"y":1709,"fill":86,"style":94},"184","Front-matter errors",[70,1712],{"x":216,"y":1439,"width":1375,"height":1390,"rx":226,"fill":1391,"opacity":1392,"stroke":1385,"style":105},[82,1714,1717],{"x":1715,"y":1716,"fill":1385,"style":1695},"430","186","14 reached review",[70,1719],{"x":216,"y":1275,"width":1720,"height":1390,"rx":226,"fill":116,"opacity":1721,"stroke":93,"style":105},"20","0.28",[82,1723,1640],{"x":1700,"y":1724,"fill":93,"style":94},"222",[82,1726,1728],{"x":1684,"y":1727,"fill":143,"style":212},"262","One quarter before, one quarter after · validation at entry time is what removed the errors",[233,1730,1731],{},"The error reduction is the underrated half: the same validation rules existed as build gates before, but catching them at the moment of writing is far cheaper than catching them in CI.",[27,1733,1735],{"id":1734},"pitfalls-rollback","Pitfalls & Rollback",[32,1737,1738,1745,1751,1757,1763,1769],{},[35,1739,1740,1744],{},[1741,1742,1743],"strong",{},"Letting the CMS become the source of truth."," With a Git-backed system the repository must stay authoritative; with an API-backed one, cache the fetched content so a build is reproducible without the vendor.",[35,1746,1747,1750],{},[1741,1748,1749],{},"Skipping the editorial workflow."," Committing straight to the default branch skips preview and review, which is most of what made the pipeline trustworthy.",[35,1752,1753,1756],{},[1741,1754,1755],{},"Unsigned deploy hooks."," A public trigger URL is an invitation to burn your build minutes.",[35,1758,1759,1762],{},[1741,1760,1761],{},"Modelling content around the CMS's widgets."," Model it around the pages you publish; a schema that only makes sense inside one vendor's UI is a migration cost later.",[35,1764,1765,1768],{},[1741,1766,1767],{},"Unconstrained uploads."," Set limits in the CMS and add a build check; an editor should not need to know what an AVIF is.",[35,1770,1771,1774],{},[1741,1772,1773],{},"Rollback:"," a Git-backed CMS is a configuration file and an auth backend. Deleting the admin route leaves every piece of content exactly where it was, because it was always just Markdown in the repository.",[27,1776,1778],{"id":1777},"conclusion","Conclusion",[14,1780,1781,1782,25],{},"For documentation, a Git-backed CMS is usually the right answer: it adds an editing interface without moving the source of truth, so previews, review, history and offline builds all keep working. Reach for an API-backed system only when the content genuinely belongs to more than one product, and then write the fetch as an explicit build step that produces Markdown you could commit. Either way, validate fields at entry, debounce the rebuild, and constrain media before it reaches the repository. The surrounding workflow is in ",[21,1783,24],{"href":23},[27,1785,1787],{"id":1786},"faq","FAQ",[1789,1790,1792],"h3",{"id":1791},"git-backed-or-api-backed-cms-which-should-i-choose","Git-backed or API-backed CMS — which should I choose?",[14,1794,1795],{},"Git-backed for documentation, where the repository should stay authoritative and content benefits from branching, review and history. API-backed when content is shared across several products, needs fine-grained editorial permissions, or is edited by people who will never see a pull request.",[1789,1797,1799],{"id":1798},"how-does-the-site-rebuild-when-someone-publishes","How does the site rebuild when someone publishes?",[14,1801,1802],{},"A webhook from the CMS triggers a build. With a Git-backed CMS the commit itself triggers your normal pipeline; with an API-backed one you add a deploy hook the CMS calls on publish, usually with a short debounce so ten edits do not queue ten builds.",[1789,1804,1806],{"id":1805},"what-happens-to-preview-when-content-lives-outside-git","What happens to preview when content lives outside Git?",[14,1808,1809],{},"You lose per-branch previews unless the CMS supports draft states that the build can query. Most API-backed systems expose a preview token that renders unpublished entries, which you point a separate preview deployment at.",[1789,1811,1813],{"id":1812},"where-should-uploaded-images-go","Where should uploaded images go?",[14,1815,1816],{},"Into the repository with a Git-backed CMS, and into the CMS's own media store with an API-backed one. Either way, run them through the build's image pipeline rather than serving originals, and set a size limit in the CMS so a 12 megapixel phone photo never reaches the build.",[1789,1818,1820],{"id":1819},"can-i-add-a-cms-to-an-existing-static-site-without-changing-the-content","Can I add a CMS to an existing static site without changing the content?",[14,1822,1823],{},"Usually yes for a Git-backed CMS — you describe the existing front matter as a collection schema and it edits the files you already have. That is a strong argument for keeping front matter simple and consistent from the start.",[27,1825,1827],{"id":1826},"related","Related",[32,1829,1830,1839,1844,1851,1858],{},[35,1831,1832,1835,1836,1838],{},[1741,1833,1834],{},"Parent:"," ",[21,1837,24],{"href":23}," — where the CMS fits among the entry points.",[35,1840,1841,1843],{},[21,1842,1357],{"href":1356}," — the backstop for debounced webhooks.",[35,1845,1846,1850],{},[21,1847,1849],{"href":1848},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fdocs-as-code-review-workflow-for-writers\u002F","Docs-as-Code Review Workflow for Writers"," — the review process the editorial workflow feeds.",[35,1852,1853,1857],{},[21,1854,1856],{"href":1855},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates\u002F","Netlify Build Hooks for Content Updates"," — the same trigger mechanism on another host.",[35,1859,1860,1862],{},[21,1861,1481],{"href":1480}," — what should happen to every upload.",[1864,1865,1866],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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 .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":249,"searchDepth":264,"depth":264,"links":1868},[1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1885],{"id":29,"depth":264,"text":30},{"id":46,"depth":264,"text":47},{"id":238,"depth":264,"text":239},{"id":681,"depth":264,"text":682},{"id":1037,"depth":264,"text":1038},{"id":1470,"depth":264,"text":1471},{"id":1580,"depth":264,"text":1581},{"id":1734,"depth":264,"text":1735},{"id":1777,"depth":264,"text":1778},{"id":1786,"depth":264,"text":1787,"children":1879},[1880,1881,1882,1883,1884],{"id":1791,"depth":275,"text":1792},{"id":1798,"depth":275,"text":1799},{"id":1805,"depth":275,"text":1806},{"id":1812,"depth":275,"text":1813},{"id":1819,"depth":275,"text":1820},{"id":1826,"depth":264,"text":1827},[1887,1890,1893,1894],{"name":1888,"item":1889},"Home","\u002F",{"name":1891,"item":1892},"Production-Ready Deployment & CI\u002FCD Workflows","\u002Fproduction-ready-deployment-cicd-workflows\u002F",{"name":24,"item":23},{"name":5,"item":1895},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fwiring-a-headless-cms-to-a-static-build\u002F","2026-08-01","Connect a Git-backed or API-backed CMS to a static generator: content model, auth, webhook rebuilds, media handling, and keeping the repository the source of truth.","md",[1900,1901,1902,1903,1904],{"q":1792,"a":1795},{"q":1799,"a":1802},{"q":1806,"a":1809},{"q":1813,"a":1816},{"q":1820,"a":1823},{},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fwiring-a-headless-cms-to-a-static-build",{"title":5,"description":1897},"production-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fwiring-a-headless-cms-to-a-static-build\u002Findex","article","sVATQvDuFMGRn_2Od6AwwqVsLoRk_BO-Mu3xDqpNHC8",[1912,1915,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2031,2034,2037,2040,2043,2046,2049,2052,2055,2058,2061,2064,2067,2070,2073,2076,2079,2082,2085,2087,2089,2091,2092,2095,2098,2101,2104,2107,2110,2113,2116,2119,2122,2125,2127,2130,2133,2136,2139,2142,2145,2148,2151],{"path":1913,"title":1914},"\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":1916,"title":1917},"\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":1919,"title":1920},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fastro-vs-eleventy-for-documentation-sites","Astro vs Eleventy for Documentation Sites",{"path":1922,"title":1923},"\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":1925,"title":1926},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fhugo-build-times-for-large-repositories","Hugo Build Times for Large Repositories",{"path":1928,"title":1929},"\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":1931,"title":1932},"\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":1934,"title":1935},"\u002Fchoosing-the-right-static-site-generator-for-production","Choosing the Right Static Site Generator for Production",{"path":1937,"title":1938},"\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":1940,"title":1941},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fjekyll-plugin-ecosystem","Jekyll Plugin Ecosystem",{"path":1943,"title":1944},"\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":1946,"title":1947},"\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":1949,"title":1950},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators","Migrating Between Static Site Generators",{"path":1952,"title":1953},"\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":1955,"title":1956},"\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":1958,"title":1959},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fmigrating-from-hugo-to-astro-without-breaking-urls","Migrating From Hugo to Astro Without Breaking URLs",{"path":1961,"title":1962},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fmigrating-between-static-site-generators\u002Fporting-shortcodes-and-includes-between-generators","Porting Shortcodes and Includes Between Generators",{"path":1964,"title":1965},"\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":1967,"title":1968},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fnextjs-static-export-for-content-sites","Next.js Static Export for Content Sites",{"path":1970,"title":1971},"\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":1973,"title":1974},"\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":1976,"title":1977},"\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":1979,"title":1980},"\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":1982,"title":1983},"\u002Fchoosing-the-right-static-site-generator-for-production\u002Fssg-framework-selection-matrix","SSG Framework Selection Matrix",{"path":1985,"title":1986},"\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":1988,"title":1989},"\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":1991,"title":1992},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcdn-caching-rules-for-ssgs","CDN Caching Rules for SSGs",{"path":1994,"title":1995},"\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":1997,"title":1998},"\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":2000,"title":2001},"\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":2003,"title":2004},"\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":2006,"title":2007},"\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":2009,"title":2010},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fcumulative-layout-shift-fixes-for-static-sites","Cumulative Layout Shift Fixes for Static Sites",{"path":2012,"title":2013},"\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":2015,"title":2016},"\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":2018,"title":2019},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Ffont-loading-strategies-for-static-sites","Font Loading Strategies for Static Sites",{"path":2021,"title":2022},"\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":2024,"title":2025},"\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":2027,"title":2028},"\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":2030,"title":1481},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fimage-optimization-pipelines-in-astro",{"path":2032,"title":2033},"\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":2035,"title":2036},"\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":2038,"title":2039},"\u002Fperformance-optimization-core-web-vitals-for-ssgs","Core Web Vitals Optimization for SSGs",{"path":2041,"title":2042},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering\u002Fastro-islands-vs-full-hydration-performance","Astro Islands vs Full Hydration Performance",{"path":2044,"title":2045},"\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":2047,"title":2048},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fjavascript-hydration-partial-rendering","JavaScript Hydration & Partial Rendering",{"path":2050,"title":2051},"\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":2053,"title":2054},"\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":2056,"title":2057},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Flargest-contentful-paint-optimization-for-static-sites","Largest Contentful Paint Optimization for Static Sites",{"path":2059,"title":2060},"\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":2062,"title":2063},"\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":2065,"title":2066},"\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":2068,"title":2069},"\u002Fperformance-optimization-core-web-vitals-for-ssgs\u002Fthird-party-script-performance-on-static-sites","Third-Party Script Performance on Static Sites",{"path":2071,"title":2072},"\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":2074,"title":2075},"\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":2077,"title":2078},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup\u002Fautomating-eleventy-deployments-with-cloudflare-pages","Automating Eleventy Deployments on Cloudflare Pages",{"path":2080,"title":2081},"\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":2083,"title":2084},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcloudflare-pages-edge-caching-setup","Cloudflare Pages Edge Caching Setup",{"path":2086,"title":1849},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fdocs-as-code-review-workflow-for-writers",{"path":2088,"title":24},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams",{"path":2090,"title":1357},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fcontent-workflows-for-documentation-teams\u002Fscheduling-content-publication-with-cron-triggered-builds",{"path":1906,"title":5},{"path":2093,"title":2094},"\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":2096,"title":2097},"\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":2099,"title":2100},"\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":2102,"title":2103},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fgithub-actions-for-automated-ssg-builds","GitHub Actions for Automated SSG Builds",{"path":2105,"title":2106},"\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":2108,"title":2109},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs\u002Fenabling-incremental-builds-in-eleventy","Enabling Incremental Builds in Eleventy",{"path":2111,"title":2112},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fincremental-builds-and-build-caching-for-ssgs","Incremental Builds and Build Caching for SSGs",{"path":2114,"title":2115},"\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":2117,"title":2118},"\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":2120,"title":2121},"\u002Fproduction-ready-deployment-cicd-workflows","Production-Ready Deployment & CI\u002FCD for SSGs",{"path":2123,"title":2124},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies","Netlify vs Vercel Deployment Strategies",{"path":2126,"title":1856},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fnetlify-vs-vercel-deployment-strategies\u002Fnetlify-build-hooks-for-content-updates",{"path":2128,"title":2129},"\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":2131,"title":2132},"\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":2134,"title":2135},"\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":2137,"title":2138},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests\u002Fcleaning-up-stale-preview-deployments","Cleaning Up Stale Preview Deployments",{"path":2140,"title":2141},"\u002Fproduction-ready-deployment-cicd-workflows\u002Fpreview-environments-for-pull-requests","Preview Environments for Pull Requests",{"path":2143,"title":2144},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites\u002Fatomic-deploys-vs-incremental-uploads","Atomic Deploys vs Incremental Uploads",{"path":2146,"title":2147},"\u002Fproduction-ready-deployment-cicd-workflows\u002Frollbacks-and-deploy-safety-for-static-sites","Rollbacks and Deploy Safety for Static Sites",{"path":2149,"title":2150},"\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":2152,"title":2153},"\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",1785611671058]