minidocs

多个集合

文档之外的 Markdown 页面,比如更新日志、博客、另一套字段不同的文档,各自是一个集合,也都用 minidocs 的 glob 加载。这样全站的文件链接都能互相引用、都会被检查,Markdown 版本和 llms.txt 也能覆盖这些页面。

定义

// src/content.config.ts
const docs = defineCollection({
  loader: glob({ base: './src/content/docs', pattern: '**/*.{md,mdx}', prefix: 'docs' }),
  schema: z.object({ title: z.string() }),
});

const changelog = defineCollection({
  loader: glob({ base: './src/content/changelog', pattern: '**/*.md', prefix: 'changelog' }),
  schema: z.object({ title: z.string(), date: z.coerce.date() }),
});

export const collections = { docs, changelog };

每个集合各写各的 prefix,也就是这个集合的页面所在的路径。其他的都和文档集合一样:多语言站点上,每个集合的第一级都是语言目录,回退规则也是同一套。

作者信息这类没有页面的集合,继续用 Astro 自己的 loader。

地址由文件路径决定

页面的地址固定是语言段、prefix 加页面 id,id 来自文件路径。想用 /blog/2026/10/launch/ 这种按日期生成的地址,或者用 frontmatter 里的 slug 决定地址,minidocs 算出的链接会和你的路由对不上。可以把日期写进文件名,比如 2026-10-31-launch.md。

路由

文件结构就是地址结构,每个集合的路由放在它的 prefix 对应的文件夹里:

src/pages/[...locale]/
├── index.astro                  → /、/en/
├── docs/[...slug].astro         → /docs/…、/en/docs/…
├── changelog/
│   ├── index.astro              → 列表页:/changelog/、/en/changelog/
│   └── [...slug].astro          → /changelog/v0.1.0/…
└── [...path].md.ts              → 所有页面的 Markdown 版本

每个路由都是 pages.map((page) => ({ params: page.params, props: page }))。路由文件和 prefix 对不上时,构建会失败,并列出没有生成的页面。Markdown 版本是全站的产物,一个路由覆盖所有集合,见 Markdown 与 llms.txt。

链接

toHref 的第一个参数是集合名,id 按集合做类型检查:

toHref('docs', 'guides/search', locale);
toHref('changelog', 'v0.1.0', locale);

正文里可以直接链接到另一个集合的文件,两个方向都行:

<!-- src/content/docs/en/guides/search.mdx -->
This was added in [0.1.0](../../../changelog/en/v0.1.0.md).

<!-- src/content/changelog/en/v0.1.0.md -->
See the [search guide](../../docs/en/guides/search.mdx).

规则和集合内部一样:同一种语言之间输出相对地址,所以作为回退显示在中文下的更新日志,其中的链接也会留在中文站;链接到另一种语言的目录,就是明确指定那种语言。目标不存在、或者被它所在集合的 filter 排除时,构建失败。

列表、翻页和 llms.txt

按日期排序的扁平列表也是一棵树,用 defineTree 定义:

// src/lib/changelog.ts
export const entries = await getCollection('changelog');
export const pages = listPages(entries);
export const releases = await defineTree(entries, ({ dir }) =>
  dir('', { compare: (a, b) => b.page!.entry.data.date.valueOf() - a.page!.entry.data.date.valueOf() }),
);
  • 侧边栏和翻页用 navState(releases, id)、neighbors(releases, id),和文档的写法一样
  • 列表页是站点自己的页面,不属于集合,所以单独一个路由 changelog/index.astro。它用 toLocaleParam 为每种语言生成一份,遍历 releases[locale],用 toHref('changelog', page.id, locale) 链接到每一篇:
---
// src/pages/[...locale]/changelog/index.astro
export function getStaticPaths() {
  return locales.map((locale) => ({ params: { locale: toLocaleParam(locale) }, props: { locale } }));
}
---
  • llms.txt 可以把几个集合的树拼在一起,describe 收到的条目类型是各个集合的并集:
toLlmsTxt([...tree[locale], { label: '更新日志', children: releases[locale] }], {
  title: '我的文档',
  describe: (entry) => entry.data.description,
});

分页和 RSS 也是你自己的代码,listPages 给出的 href 就是每一页的地址。

只有一种语言的内容

比如更新日志只用英文写,文件都放在 en/ 目录里,其他语言靠回退显示:

minidocs({ fallback: { en: 'zh', zh: 'en' } })

这样每种语言都有一份更新日志页面:/changelog/v0.1.0/ 是中文界面加英文内容,/en/changelog/v0.1.0/ 是英文。读者从中文文档点进更新日志,页头、侧边栏和回到文档的链接都还是中文。回退对文档没有影响:中文文档是完整的,zh: 'en' 只在中文缺页时才生效。

这些页面的 isFallback 是 true,可以像多语言里那样提示读者。

地址冲突

所有集合的页面地址和 Markdown 版本地址会放在一起检查。比如文档集合没有 prefix,en/ 里又恰好有一个 changelog/v0.1.0.mdx,构建时会报错:

[minidocs] "/en/changelog/v0.1.0/" is the URL of two pages:
  src/content/changelog/en/v0.1.0.md (changelog)
  src/content/docs/en/changelog/v0.1.0.mdx (docs)

所以 prefix 可以嵌套,比如 docs 和 docs/v1,真正撞上同一个地址时才会报错。

不需要多个集合的情况

同一套文档分成几块,比如「指南」和「API 参考」,地址前缀和字段都相同时,用一个集合和多棵树就够了。

本页目录
搜索范围
输入关键词开始搜索