minidocs

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
On this page
Search in
Type to start searching