快速开始
这一页从一个 Astro 项目开始,搭出一个带侧边栏、本页目录和翻页的文档站。
前置条件
- Node.js 22.22 或更高版本
- 一个 Astro 7 项目
安装
pnpm add @minidocs/astro @astrojs/mdx
在 astro.config.mjs 里加上两个 integration。@astrojs/mdx 让你可以用 MDX 写文档,minidocs 处理正文里的链接和提示框:
import mdx from '@astrojs/mdx';
import { defineConfig } from 'astro/config';
import minidocs from '@minidocs/astro';
export default defineConfig({
integrations: [mdx(), minidocs()],
});
定义内容集合
在 src/content.config.ts 里定义集合,glob 从 @minidocs/astro/loaders 导入。它就是 Astro 的 glob,多了一个 prefix 选项,表示页面放在哪个路径下:
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod';
import { glob } from '@minidocs/astro/loaders';
const docs = defineCollection({
loader: glob({ base: './src/content/docs', pattern: '**/*.{md,mdx}', prefix: 'docs' }),
schema: z.object({
title: z.string(),
}),
});
export const collections = { docs };
minidocs 只要求 schema 里有 title,用作侧边栏里的标题。其他字段随你定义。
写第一篇文档
在 src/content/docs/ 下新建 index.mdx 和 hello.mdx:
---
title: 你好
---
这是第一篇文档。
每个页面有一个 id,就是去掉扩展名的文件路径。这两个页面的 id 是 index 和 hello,地址是 /docs/ 和 /docs/hello/。id 和地址的规则见页面与地址。
定义文档树
集合里的页面没有顺序。用 defineTree 把它们组织成一棵树,侧边栏和翻页都按这棵树来:
// src/lib/docs.ts
import { getCollection } from 'astro:content';
import { defineTree, listPages } from '@minidocs/astro/runtime';
export const entries = await getCollection('docs');
export const tree = await defineTree(entries, ({ page, dir }) => [page('index'), ...dir('')]);
export const pages = listPages(entries);
dir('') 列出根目录下除 index 以外的页面。listPages 列出所有要生成的页面,下一步的路由要用到它。page() 和 dir() 的参数都有补全,写错 id 是类型错误。更多写法见文档树。
渲染页面
在 prefix 对应的文件夹里放一个路由,渲染这个集合的所有页面。pages 里每个页面都带着路由参数 page.params,直接交给 getStaticPaths。render() 返回的 headings 是页面里的标题,留给本页目录用:
---
// src/pages/docs/[...slug].astro
import { render } from 'astro:content';
import Docs from '../../layouts/Docs.astro';
import { pages } from '../../lib/docs';
export async function getStaticPaths() {
return pages.map((page) => ({ params: page.params, props: page }));
}
const { Content, headings } = await render(Astro.props.entry);
---
<Docs {...Astro.props} headings={headings}>
<Content />
</Docs>
搭出页面
最后写页面的布局。Layout、Sidebar、Toc、Pager 和 Prose 都是 minidocs 的组件,引入 @minidocs/astro/styles.css 就有完整的默认外观。布局开头算出组件要用的数据:
navState(tree, id)记下当前页面在文档树里的位置,nav.of(node)给出每一项的链接和是否是当前页面neighbors(tree, id)找出上一页和下一页tocTree(headings)把页面的标题整理成本页目录
---
// src/layouts/Docs.astro
import { Layout, Pager, Prose, Sidebar, Toc } from '@minidocs/astro/components';
import { navState, neighbors, tocTree, toHref } from '@minidocs/astro/runtime';
import '@minidocs/astro/styles.css';
import { tree } from '../lib/docs';
const { id, entry, headings } = Astro.props;
const nav = navState(tree, id);
const toc = tocTree(headings);
const { prev, next } = neighbors(tree, id);
---
<html lang="zh">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{entry.data.title}</title>
</head>
<body>
<Layout.Root>
<Layout.Header>
<Layout.Title href={toHref('docs', 'index')}>我的文档</Layout.Title>
</Layout.Header>
<Sidebar.Trigger target="sidebar" aria-label="菜单">
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
<path d="M2 4h12M2 8h12M2 12h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</Sidebar.Trigger>
<Layout.Sidebar>
<Sidebar.Root id="sidebar" label="文档">
<Sidebar.List>
{tree.map(function item(node) {
return (
<Sidebar.Item>
{node.children.length > 0 ? (
<Sidebar.Group {...nav.of(node)}>
<Fragment slot="label">{node.label}</Fragment>
<Sidebar.List>{node.children.map(item)}</Sidebar.List>
</Sidebar.Group>
) : (
<Sidebar.Link {...nav.of(node)}>{node.label}</Sidebar.Link>
)}
</Sidebar.Item>
);
})}
</Sidebar.List>
</Sidebar.Root>
</Layout.Sidebar>
<Layout.Main>
<Prose>
<h1>{entry.data.title}</h1>
<slot />
</Prose>
{(prev || next) && (
<Pager.Root label="翻页">
{prev && (
<Pager.Link rel="prev" href={nav.of(prev).href}>
<Pager.Label>上一页</Pager.Label>
<Pager.Title>{prev.label}</Pager.Title>
</Pager.Link>
)}
{next && (
<Pager.Link rel="next" href={nav.of(next).href}>
<Pager.Label>下一页</Pager.Label>
<Pager.Title>{next.label}</Pager.Title>
</Pager.Link>
)}
</Pager.Root>
)}
</Layout.Main>
{toc.length > 0 && (
<Layout.Aside title="本页目录">
<Toc.Root label="本页目录">
<Toc.List>
{toc.map(function item(heading) {
return (
<Toc.Item>
<Toc.Link slug={heading.slug}>{heading.text}</Toc.Link>
{heading.children.length > 0 && <Toc.List>{heading.children.map(item)}</Toc.List>}
</Toc.Item>
);
})}
</Toc.List>
</Toc.Root>
</Layout.Aside>
)}
</Layout.Root>
</body>
</html>
运行 pnpm dev,打开 /docs/,就能看到一个完整的文档站:左边是侧边栏,中间是正文,右边是本页目录,底部是上一页和下一页,窄屏下侧边栏会变成抽屉。
这个布局文件是你的代码,结构、顺序、每一部分的样子都可以改。接下来可以看: