# 多个集合

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

## 定义

```ts
// 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。

> [!NOTE]
> **地址由文件路径决定**
>
> 页面的地址固定是语言段、`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](/docs/guides/markdown.md#%E5%9C%B0%E5%9D%80)。

## 链接

`toHref` 的第一个参数是集合名，id 按集合做类型检查：

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

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

```md
<!-- 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` 定义：

```ts
// 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)` 链接到每一篇：

```astro
---
// src/pages/[...locale]/changelog/index.astro
export function getStaticPaths() {
  return locales.map((locale) => ({ params: { locale: toLocaleParam(locale) }, props: { locale } }));
}
---
```

- llms.txt 可以把几个集合的树拼在一起，`describe` 收到的条目类型是各个集合的并集：

```ts
toLlmsTxt([...tree[locale], { label: '更新日志', children: releases[locale] }], {
  title: '我的文档',
  describe: (entry) => entry.data.description,
});
```

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

## 只有一种语言的内容

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

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

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

这些页面的 `isFallback` 是 `true`，可以像[多语言](/docs/guides/i18n.md#%E6%B2%A1%E6%9C%89%E7%BF%BB%E8%AF%91%E7%9A%84%E9%A1%B5%E9%9D%A2)里那样提示读者。

## 地址冲突

所有集合的页面地址和 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 参考」，地址前缀和字段都相同时，用一个集合和[多棵树](/docs/guides/multiple-trees.md)就够了。
