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
// 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:
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 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:
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:
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