# The Document Tree

Pages in a collection have no order. The document tree decides how they are arranged: the sidebar is rendered from it, and the previous and next pages follow its order.

## Defining it

```ts
// src/lib/docs.ts
import { getCollection } from 'astro:content';
import { defineTree } from '@minidocs/astro/runtime';

const entries = await getCollection('docs');

export const tree = await defineTree(entries, ({ page, group, dir, link }) => [
  page('index'),
  page('install'),
  group('Guides', dir('guides')),
  link('GitHub', 'https://github.com/example/repo'),
]);
```

The callback of `defineTree` returns a list of nodes. A node is one item of the tree, such as a page or a group, and this list is the first level of the sidebar.

## Four functions

| Function                  | Returns                                                                          |
| ------------------------- | -------------------------------------------------------------------------------- |
| `page(id, children?)`     | A page, optionally with children                                                 |
| `group(label, children)`  | A group without a page of its own                                                |
| `dir(path, { compare? })` | The pages and subdirectories of a directory, without the directory’s own `index` |
| `link(label, href)`       | An external link                                                                 |

`dir` leaves out the directory’s own `index`, so a directory can be written two ways:

```ts
group('Guides', dir('guides')); // the group is only a heading
page('guides', dir('guides')); // the group is also a page, guides/index.mdx
```

`dir` treats subdirectories the same way: one with an `index` becomes a page with children, one without becomes a group labeled with the directory name.

## Nodes

Every node you get when rendering has these fields:

| Field      | Meaning                                                                                                  |
| ---------- | -------------------------------------------------------------------------------------------------------- |
| `label`    | The text shown: the frontmatter `title` for a page, the label you gave or the directory name for a group |
| `children` | Child nodes, an empty array when there are none                                                          |
| `page`     | Only on pages. `page.entry` is the collection entry                                                      |
| `href`     | Only on external links                                                                                   |

See [Sidebar](/en/docs/guides/sidebar.md) for rendering it as a sidebar.

## Sorting

`dir` sorts by file name. To sort another way, pass `compare`, such as by an `order` field in the frontmatter:

```ts
import { type DocNode } from '@minidocs/astro/runtime';

type Entry = (typeof entries)[number];

const byOrder = (a: DocNode<Entry>, b: DocNode<Entry>) =>
  (a.page?.entry.data.order ?? Infinity) - (b.page?.entry.data.order ?? Infinity);

group('Guides', dir('guides', { compare: byOrder }));
```

`compare` applies to every level `dir` lists. `order` is a field you declare in your own schema; groups have no `page`, so this puts them last.

## Multilingual sites

The callback runs once for each locale, `tree` is an object keyed by locale, and you render `tree[locale]`. The `locale` passed to the callback can translate group labels:

```ts
export const tree = await defineTree(entries, ({ locale, page, group, dir }) => [
  page('index'),
  group(locale === 'zh' ? '指南' : 'Guides', dir('guides')),
]);
```

## Rules

- The tree only decides navigation, not URLs. Reordering pages or moving them into another group leaves every URL as it is
- A page can appear in the tree more than once, but only its first position counts as the current one, and previous and next follow that position
