How the changelog pages work
What the build generates, what stays yours, and how to set it up for a GitHub repository.
Short answer to the most common question: the pages are driven by your
configuration, not by your content. Creating a folder and dropping a page with
<fd-changelog> in it does not make the build produce years and months. The
build reads changelog.monthlyPages and writes the tree itself.
The one thing to remember
<fd-changelog> displays history. changelog.monthlyPages generates
pages. They are independent — you can use either alone.
What you create, and what the build creates
You create the section and, if you want one, a landing page:
docs/
└── changelog/
├── _section.json ← yours: the section's label and position
└── index.md ← yours: whatever you want readers to land onThe build adds everything below that, and keeps it in step with the history:
docs/
└── changelog/
├── _section.json
├── index.md
└── feastdocs/ ← generated: one folder per repository
├── _category.json ← generated: the category label
├── index.md ← generated: that repository's overview
└── 2026/ ← generated: one folder per year
├── _category.json
└── august.md ← generated: one page per monthEach generated page holds a filter and nothing else:
---
title: 'FeastDocs — August 2026'
sidebar_label: 'August'
sidebar_position: -8
---
<!-- AUTO-GENERATED by `npm run docs:build` — edits are overwritten. -->
# FeastDocs — August 2026
<fd-changelog month="2026-08"></fd-changelog>That is why the files are stable: the commits live in a generated data module, never in the page. A new commit changes no file. A new month adds one.
Each repository's index.md becomes its category's own link, and carries that
repository's months plus its five most recent changes. That is deliberate: the
listings live with the repository they belong to, so the section's landing page
can be an introduction and a set of cards
(<fd-changelog-repos>) instead of a third copy of the same commits.
You do not even have to create the section — point monthlyPagesDir at a folder
that does not exist and the build creates it, which then becomes a top-level
section with a label derived from the folder name.
Setting it up for a GitHub repository
// feastdocs.config.mjs
github: {
repo: 'acme/docs', // gives every entry a commit link
branch: 'main',
},
changelog: {
limit: 150, // how many commits to read
monthlyPages: true, // generate the tree
monthlyPagesDir: 'changelog',
selfLabel: 'Acme Docs', // category label for this repository
},npm run docs:buildThe log tells you what happened:
changelog: git history gave 146 commits
changelog pages: 3 written, 0 removed → docs/changelogdocs/changelog/index.md is yours. An index of the generated pages, without
repeating their contents, is one component:
<fd-changelog-months></fd-changelog-months>The generated files are ordinary Markdown and are meant to be committed. They change rarely — once a month — and committing them keeps the repository self-describing and the content manager's file tree honest.
Which branch the history comes from
By default the build reads the checked-out branch, which is what a normal deploy wants: the changelog matches the content being published.
Set a branch explicitly when that is not true — for example when previews build from feature branches but the changelog should always show the release line:
changelog: {
branch: 'main',
},A CI clone often holds only the branch it built, so main may exist solely as
origin/main. Both are tried, and if neither exists the build warns and reads
the checked-out branch rather than failing:
! changelog: branch 'release' is not in this checkout — reading the checked-out branch insteadOther repositories carry their own branch, set per entry in changelog.repos —
see changelogs for several products.
Several repositories
Each source gets its own category, in the order you list them:
Changelog
├── Changelog (your index.md)
├── Acme Docs ← this repository, named by selfLabel
│ └── 2026 › August
└── Checkout API ← from changelog.repos
├── 2026 › August, July
└── 2025 › Decemberchangelog: {
monthlyPages: true,
groupByRepo: true, // 'auto' drops this level while there is one source
selfLabel: 'Acme Docs',
repos: [
{ repo: 'acme/checkout-api', title: 'Checkout API' },
],
},title is the category label and the URL segment (/changelog/checkout-api/…).
Without it the repository name is used.
What is yours and what is the build's
| Owner | Rule | |
|---|---|---|
_section.json, index.md, any page you wrote |
You | Never touched |
<repo>/_category.json, <year>/_category.json |
Build | Carries "generated": true |
<repo>/index.md, <year>/<month>.md |
Build | Carries the AUTO-GENERATED marker |
Two consequences:
- Edits to generated pages are lost on the next build. Put anything hand-written on your own page in the section.
- Pruning only removes what it generated. Months that fall out of
changelog.limit, a renamed product, a repository dropped from the config — their pages go. A page you wrote inside a year folder stays, and so does a_category.jsonyou wrote yourself.
Ordering
Generated categories use deliberately large positions so your own pages come
first: repositories at 1000 + index (config order), years at 10000 - year
(newest first). Months use a negative sidebar_position so December precedes
January within a year.
Give your own pages ordinary positions (10, 20, …) and they stay at the top
of the section.
Turning it off
Remove monthlyPages (or set it to false) and the build stops generating.
The pages already written are not removed — pruning only runs when the
feature is on. Delete the folders yourself, or turn it back on, build once to
let it prune, then turn it off.
Troubleshooting
| Symptom | Cause |
|---|---|
| No pages appear | monthlyPages is not true, or the history is empty |
git history gave 1 commit |
Shallow checkout — the build falls back to the host API, and the log says so |
| A month is missing | No commits landed in it; empty months are skipped |
| An old month page lingers | It is outside changelog.limit; it is pruned on the next build with the feature on |
| The repository level is missing | groupByRepo is 'auto' and there is only one source |
| Pages appear in an unexpected section | monthlyPagesDir is a top-level folder, so it is a section |