# document0
> document0: documentation framework for building custom docs sites
---
# Angular
URL: https://document0.dev/docs/angular
# Angular
**Coming soon.** This guide is under development. In the meantime, see the [Quickstart](/docs/quickstart) for the framework-agnostic basics — `DocsSource` and `processMdx` work in any Node.js environment.
This guide will cover:
- Setting up document0 with Angular
- Routing with Angular Router
- Rendering MDX content in Angular components
- Adding search and navigation
---
# Astro
URL: https://document0.dev/docs/astro
# Astro
**Coming soon.** This guide is under development. In the meantime, see the [Quickstart](/docs/quickstart) for the framework-agnostic basics — `DocsSource` and `processMdx` work in any Node.js environment.
This guide will cover:
- Setting up document0 with Astro
- Content collections vs document0 source scanning
- Rendering MDX content in Astro pages
- Adding search and navigation
---
# @document0/cli
URL: https://document0.dev/docs/cli
# @document0/cli
The CLI is a plugin manager for document0. It installs components and plugins from the registry directly into your project and tracks installed versions in a lock file for upgrades.
## Install
```bash
npm install -D @document0/cli
```
Or run commands directly with `npx`:
```bash
npx @document0/cli add document0/sidebar
```
## Commands
### add
Install one or more plugins or components from the registry.
```bash
document0 add document0/sidebar
document0 add document0/sidebar document0/breadcrumbs document0/toc
```
Files are copied into your project at the path defined by the registry item (e.g. `components/document0/sidebar/`). A `document0.lock.json` file is created or updated to track installed versions.
Dependencies declared by the plugin are auto-installed with your detected package manager (npm, pnpm, yarn, or bun).
### update
Update installed components and plugins to the latest registry versions.
```bash
document0 update # update all installed items
document0 update document0/sidebar # update a specific item
```
The command compares versions in `document0.lock.json` against the registry. If a newer version exists, the files are re-fetched and the lock file is updated.
### list
List all available items in the registry.
```bash
document0 list
```
### search
Search the registry by name, tag, or description.
```bash
document0 search sidebar
document0 search navigation
```
## Lock file
When you run `document0 add`, a `document0.lock.json` file is created in your project root:
```json
{
"version": 1,
"items": {
"document0/sidebar": {
"namespace": "document0",
"name": "sidebar",
"version": "0.1.0",
"installPath": "components/document0/sidebar",
"installedAt": "2026-03-29T00:00:00.000Z"
}
}
}
```
This file should be committed to your repository. It enables `document0 update` to detect when newer versions are available.
## Registry
The CLI fetches from the public document0 registry on GitHub by default. Override the registry URL with the `DOCUMENT0_REGISTRY` environment variable:
```bash
DOCUMENT0_REGISTRY=https://my-registry.example.com document0 add my-org/my-plugin
```
### Registry categories
| Category | Install path | Description |
|---|---|---|
| `ui` | `components///` | React components (sidebar, TOC, search dialog, etc.) |
| `core` | `plugins///` | Framework-agnostic utilities (reading time, content graph) |
| `mdx` | `plugins///` | Remark/rehype plugins (admonitions, etc.) |
## Available plugins
| Name | Category | Description |
|---|---|---|
| `document0/admonitions` | mdx | GitHub-style blockquote callouts |
| `document0/reading-time` | core | Adds reading time and word count |
| `document0/content-graph` | core | Internal link graph with backlinks and broken link detection |
| `document0/sidebar` | ui | Collapsible navigation sidebar |
| `document0/toc` | ui | Table of contents with scroll-spy |
| `document0/breadcrumbs` | ui | Breadcrumb navigation |
| `document0/page-navigation` | ui | Previous/Next page links |
| `document0/search-dialog` | ui | Command-palette search with keyboard navigation |
---
# @document0/core
URL: https://document0.dev/docs/core
# @document0/core
The core package handles everything related to reading your content from disk, building data structures for your UI, serving search and LLM endpoints, and processing OpenAPI specs.
## DocsSource
The main entry point. Scans a directory for `.md` and `.mdx` files and produces typed `PageData` objects. All results are cached on the instance — subsequent calls return instantly.
```ts
import { DocsSource } from "@document0/core";
const source = new DocsSource({
rootDir: "./content/docs", // required: absolute or relative path
baseUrl: "/docs", // default: "/docs"
extensions: [".md", ".mdx"], // default
});
```
### Methods
#### `getPages()`
Returns all pages as a flat array of `PageData`. Cached after first call.
```ts
const pages = await source.getPages();
// PageData[]
```
#### `getPageTree()`
Returns a nested `TreeNode[]` for sidebar rendering. Respects `_meta.json` ordering. Cached after first call.
```ts
const tree = await source.getPageTree();
// TreeNode[]
```
#### `getPage(slug)`
O(1) lookup of a single page by its slug string (e.g. `"guides/installation"`).
```ts
const page = await source.getPage("installation");
// PageData | undefined
```
#### `getPageByUrl(url)`
O(1) lookup of a single page by its full URL (e.g. `"/docs/guides/installation"`).
```ts
const page = await source.getPageByUrl("/docs/installation");
// PageData | undefined
```
#### `invalidate()`
Clears all cached data (pages, tree, search index, lookup maps). Call this when content files change to force a reload.
```ts
source.invalidate();
```
#### Content watching (dev)
**Next.js:** use **`@document0/next-dev`** (`withDocument0` + one `content-stamp` import) so the dev bundler invalidates your `DocsSource` module when `content/` changes (Fumadocs-style). See the **[Next.js guide](/docs/nextjs#content-hot-reload-in-development)**.
**Other runtimes:** **`watchDocsSource`** from **`@document0/core/watch`** (Node-only; separate entry so **chokidar** is not pulled into unrelated graphs).
### PageData
```ts
interface PageData {
slug: string; // e.g. "guides/installation"
slugs: string[]; // e.g. ["guides", "installation"]
url: string; // e.g. "/docs/guides/installation"
filePath: string; // absolute path to the .mdx file
content: string; // raw markdown body (frontmatter stripped)
frontmatter: {
title: string;
description?: string;
icon?: string;
full?: boolean;
[key: string]: unknown;
};
}
```
---
## Page tree
### TreeNode
```ts
type TreeNode = PageNode | FolderNode | SeparatorNode;
interface PageNode {
type: "page";
name: string;
url: string;
slug: string;
icon?: string;
}
interface FolderNode {
type: "folder";
name: string;
icon?: string;
defaultOpen?: boolean;
index?: PageNode; // index.mdx inside this folder
children: TreeNode[];
}
interface SeparatorNode {
type: "separator";
name: string; // label, or empty string for an unlabelled divider
}
```
### \_meta.json
Place a `_meta.json` file in any content directory to control ordering and labels.
```json
{
"title": "Guides",
"pages": [
"index",
"--- Getting Started",
"installation",
"---",
"advanced"
],
"defaultOpen": true,
"icon": "📖"
}
```
- Strings starting with `"--- "` become labelled separators
- `"---"` alone becomes an unlabelled divider
- Pages not listed are appended alphabetically
---
## Navigation utilities
### getBreadcrumbs
Returns the breadcrumb trail from the root to the current page.
```ts
import { getBreadcrumbs } from "@document0/core";
const crumbs = getBreadcrumbs(tree, "/docs/guides/installation");
// BreadcrumbItem[]
```
```ts
interface BreadcrumbItem {
name: string;
url?: string; // undefined for the current (last) item
}
```
### getPageNeighbours
Returns the previous and next pages in document order.
```ts
import { getPageNeighbours } from "@document0/core";
const { previous, next } = getPageNeighbours(tree, "/docs/installation");
// { previous: PageNode | null, next: PageNode | null }
```
### isActiveOrAncestor
Returns `true` if a tree node is the active page or an ancestor of it, useful for expanding folders in a sidebar.
```ts
import { isActiveOrAncestor } from "@document0/core";
const open = isActiveOrAncestor(folderNode, currentUrl);
```
---
## Search
### createSearchRoute
Creates an API route handler backed by [Orama](https://orama.com) with full-text search, fuzzy matching, and relevance ranking. The search index is cached per `DocsSource` instance and cleared on `invalidate()`.
```ts
import { createSearchRoute } from "@document0/core/search";
// app/internal/search/route.ts
export const { GET } = createSearchRoute(source);
```
The returned `GET` handler accepts a `?q=` query parameter and responds with `SearchResult[]`.
```ts
interface SearchResult {
title: string;
description?: string;
url: string;
score: number;
}
```
---
## llms.txt
Generate [llms.txt](https://llmstxt.org/) files so LLMs and AI tools can ingest your documentation.
### createLlmsTxtRoute
Serves a concise index of all pages as `llms.txt`.
```ts
import { createLlmsTxtRoute } from "@document0/core/llms";
// app/llms.txt/route.ts
export const { GET } = createLlmsTxtRoute(source, {
title: "My Docs",
description: "Documentation for my project",
baseUrl: "https://docs.example.com",
});
```
### createLlmsFullTxtRoute
Serves the complete content of every page concatenated into a single text file.
```ts
import { createLlmsFullTxtRoute } from "@document0/core/llms";
// app/llms-full.txt/route.ts
export const { GET } = createLlmsFullTxtRoute(source, {
title: "My Docs",
description: "Documentation for my project",
baseUrl: "https://docs.example.com",
});
```
### createMdxPageRoute
Serves raw markdown content for a single page by slug.
```ts
import { createMdxPageRoute } from "@document0/core/llms";
// app/api/page/[...slug]/route.ts
export const { GET } = createMdxPageRoute(source);
```
---
## OpenAPI
### createOpenAPISource
Parses an OpenAPI 3.x spec and generates `OpenAPIPageData` for each operation.
```ts
import { createOpenAPISource } from "@document0/core/openapi";
const operations = createOpenAPISource(specObject, { baseUrl: "/docs/api" });
```
### buildOpenAPITree
Builds a `TreeNode[]` from OpenAPI operations, grouped by tag.
```ts
import { buildOpenAPITree } from "@document0/core/openapi";
const tree = buildOpenAPITree(operations);
```
### buildOpenAPISearchIndex
Returns `SearchResult[]` from OpenAPI operations for use with search.
```ts
import { buildOpenAPISearchIndex } from "@document0/core/openapi";
const results = buildOpenAPISearchIndex(operations);
```
---
# create-document0
URL: https://document0.dev/docs/create
# create-document0
The `create-document0` CLI scaffolds a complete, working docs site based on the document0 template.
## Usage
```bash
npx create-document0 my-docs
```
```bash
pnpm dlx create-document0 my-docs
```
```bash
bunx create-document0 my-docs
```
If you omit the project name, the CLI will prompt you for one.
## What gets scaffolded
```
my-docs/
├── app/
│ ├── layout.tsx
│ ├── page.tsx ← redirects to /docs
│ └── docs/
│ ├── layout.tsx ← sidebar + main layout
│ └── [[...slug]]/
│ └── page.tsx ← MDX rendering page
├── components/
│ ├── sidebar.tsx
│ ├── breadcrumbs.tsx
│ ├── page-navigation.tsx
│ ├── table-of-contents.tsx
│ └── mdx-components.tsx
├── content/
│ └── docs/
│ ├── _meta.json
│ ├── index.mdx
│ ├── installation.mdx
│ ├── configuration.mdx
│ └── guides/
│ └── index.mdx
├── lib/
│ ├── source.ts
│ ├── highlighter.ts
│ └── utils.ts
├── next.config.ts
├── tsconfig.json
└── package.json
```
The scaffolded site is intentionally unstyled: it uses inline styles as a baseline so you can see the structure and replace everything with your own design system.
## Options
| Flag | Description |
|---|---|
| `--no-install` | Skip installing dependencies |
## Package manager detection
The CLI detects your package manager from `npm_config_user_agent` and uses it automatically:
```bash
pnpm dlx create-document0 my-docs # uses pnpm install
yarn dlx create-document0 my-docs # uses yarn
bunx create-document0 my-docs # uses bun install
npx create-document0 my-docs # uses npm install
```
## After scaffolding
```bash
cd my-docs
pnpm dev
```
Open [http://localhost:3000/docs](http://localhost:3000/docs) to see your site.
From there:
1. Replace the inline styles in `components/` with your design system
2. Edit `content/docs/` with your actual documentation
3. Update `_meta.json` files to control page ordering
---
# Custom Components
URL: https://document0.dev/docs/custom-components
# Custom Components
Replace any HTML element rendered from MDX with your own component by passing a `components` map to the compiled `MDXContent`:
```tsx
const components = {
h2: ({ children, id }) => (
{children}
),
a: ({ children, href }) => (
{children}
),
};
```
You can override any valid HTML tag name: `h1`–`h6`, `p`, `a`, `ul`, `ol`, `li`, `blockquote`, `pre`, `code`, `table`, `thead`, `tbody`, `tr`, `th`, `td`, `hr`, `strong`, `em`, and more.
## Building a sidebar
Use the `TreeNode` type from `@document0/core` to build a recursive sidebar component:
```tsx
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import type { TreeNode } from "@document0/core";
function SidebarNode({ node }: { node: TreeNode }) {
const pathname = usePathname();
if (node.type === "separator") {
return
{node.name}
;
}
if (node.type === "page") {
return (
{node.name}
);
}
if (node.type === "folder") {
return (
{node.name}
{node.children.map((child, i) => (
))}
);
}
}
```
Or install pre-built components from the registry:
```bash
document0 add document0/sidebar document0/toc document0/breadcrumbs
```
## Table of contents
Build an active-heading TOC using the `toc` array returned by `processMdx`:
```tsx
"use client";
import { useEffect, useState } from "react";
import type { TocEntry } from "@document0/mdx";
export function Toc({ toc }: { toc: TocEntry[] }) {
const [activeId, setActiveId] = useState("");
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) setActiveId(entry.target.id);
}
},
{ rootMargin: "0px 0px -70% 0px" }
);
toc.forEach(({ id }) => {
const el = document.getElementById(id);
if (el) observer.observe(el);
});
return () => observer.disconnect();
}, [toc]);
return (
);
}
```
## Previous / next navigation
```ts
import { getPageNeighbours } from "@document0/core";
const { previous, next } = getPageNeighbours(tree, page.url);
```
Both `previous` and `next` are `PageNode | null`. Or install from the registry:
```bash
document0 add document0/page-navigation
```
---
# Introduction
URL: https://document0.dev/docs
# Introduction
**document0** is an open-source, headless documentation framework that gives you all the hard parts — file-system scanning, page trees, MDX processing, full-text search, llms.txt generation, and a plugin registry — while making zero decisions about your UI.
Build your docs site with any styling system you like. Ship exactly the design you want.
## Why document0?
Most documentation frameworks force you into their component library, their design tokens, and their CSS. Customising them means fighting the framework. document0 takes the opposite approach: it handles the data and processing layer completely, and hands you typed data structures you can render any way you want.
## Packages
| Package | Description |
|---|---|
| `@document0/core` | File-system source, page trees, navigation, Orama-backed search, llms.txt generation, OpenAPI support |
| `@document0/mdx` | MDX compilation, frontmatter extraction, remark/rehype plugins, Shiki highlighting |
| `@document0/mdc` | MDC processor for Vue/Svelte/non-React — markdown to HTML or JSON AST with Shiki highlighting |
| `@document0/cli` | Plugin manager — install and update components and plugins from the registry |
| `create-document0` | Scaffold a complete docs site in seconds |
## Quick example
```ts
import { DocsSource } from "@document0/core";
import { createSearchRoute } from "@document0/core/search";
import { processMdx } from "@document0/mdx";
import { createHighlighter } from "shiki";
const source = new DocsSource({ rootDir: "./content/docs", baseUrl: "/docs" });
const pages = await source.getPages();
const tree = await source.getPageTree();
// Full-text search API route (Orama-backed)
export const { GET } = createSearchRoute(source);
// MDX processing with syntax highlighting
const highlighter = await createHighlighter({ themes: ["github-dark"], langs: ["typescript"] });
const { code, frontmatter, toc } = await processMdx(rawMdx, { highlighter });
```
## Next steps
- [Installation](/docs/installation): install the packages
- [Quickstart](/docs/quickstart): build your first docs site in 5 minutes
- [CLI](/docs/cli): install plugins and components from the registry
- [Next.js guide](/docs/nextjs): App Router walkthrough, including content hot reload in dev
- [Changelog](/changelog): release history for packages and docs
---
# Installation
URL: https://document0.dev/docs/installation
# Installation
**Zero UI.** document0 is a headless documentation engine: it handles content sourcing, MDX processing, search, and navigation but ships with no UI components. You bring your own design system and have complete control over every pixel.
## Requirements
- Node.js 18 or later
- React 18+ (for React/Next.js) or Vue 3+ (for Vue)
## Install the packages
### React / Next.js
```bash
npm install @document0/core @document0/mdx
```
```bash
pnpm add @document0/core @document0/mdx
```
```bash
yarn add @document0/core @document0/mdx
```
```bash
bun add @document0/core @document0/mdx
```
You'll also need these peer dependencies:
```bash
npm install shiki @mdx-js/mdx react react-dom
```
```bash
pnpm add shiki @mdx-js/mdx react react-dom
```
```bash
yarn add shiki @mdx-js/mdx react react-dom
```
```bash
bun add shiki @mdx-js/mdx react react-dom
```
| Package | Purpose |
|---|---|
| `shiki` | Syntax highlighting for code blocks |
| `@mdx-js/mdx` | Compiling MDX at runtime |
| `react` / `react-dom` | Rendering compiled MDX |
### Vue / Svelte / Other frameworks
```bash
npm install @document0/core @document0/mdc shiki
```
```bash
pnpm add @document0/core @document0/mdc shiki
```
```bash
yarn add @document0/core @document0/mdc shiki
```
```bash
bun add @document0/core @document0/mdc shiki
```
`@document0/mdc` processes markdown to HTML or JSON AST — no React dependencies needed. See the [Vue guide](/docs/vue) or [@document0/mdc reference](/docs/mdc).
## CLI (optional)
Install the CLI to add plugins and components from the registry:
```bash
npm install -D @document0/cli
```
```bash
pnpm add -D @document0/cli
```
```bash
yarn add -D @document0/cli
```
```bash
bun add -D @document0/cli
```
Then install components:
```bash
npx document0 add document0/sidebar document0/search-dialog
```
See the [CLI reference](/docs/cli) for all commands.
## Framework setup
document0 works with any Node.js framework. The [Quickstart](/docs/quickstart) guide walks through a complete Next.js setup.
For React frameworks, use `DocsSource` and `processMdx` directly. For Vue and other frameworks, use `DocsSource` with `processMdcToHtml` from `@document0/mdc`.
---
# llms.txt
URL: https://document0.dev/docs/llms-txt
# llms.txt
Serve [llms.txt](https://llmstxt.org/) files so LLMs and AI tools can discover and ingest your documentation.
## llms.txt (concise index)
```ts
// app/llms.txt/route.ts
import { createLlmsTxtRoute } from "@document0/core/llms";
import { source } from "@/lib/source";
export const { GET } = createLlmsTxtRoute(source, {
title: "My Project",
description: "Documentation for My Project",
baseUrl: "https://docs.example.com",
});
```
## llms-full.txt (complete content)
```ts
// app/llms-full.txt/route.ts
import { createLlmsFullTxtRoute } from "@document0/core/llms";
import { source } from "@/lib/source";
export const { GET } = createLlmsFullTxtRoute(source, {
title: "My Project",
description: "Documentation for My Project",
baseUrl: "https://docs.example.com",
});
```
## Raw page content
Serve raw markdown for individual pages:
```ts
// app/api/page/[...slug]/route.ts
import { createMdxPageRoute } from "@document0/core/llms";
import { source } from "@/lib/source";
export const { GET } = createMdxPageRoute(source);
```
---
# @document0/mdc
URL: https://document0.dev/docs/mdc
# @document0/mdc
The MDC package processes `.mdx` / `.md` files into either an HTML string or a JSON AST, with frontmatter extraction, Shiki syntax highlighting, and table-of-contents generation — all without React.
This is the recommended content processor for **Vue**, **Svelte**, **Astro**, and any non-React framework. For React/Next.js, see [@document0/mdx](/docs/mdx).
## Install
```bash
npm install @document0/mdc shiki
```
## processMdcToHtml
Parses markdown source directly into an HTML string. The simplest path — use this when you want to render with `v-html` or inject into a template.
```ts
import { processMdcToHtml } from "@document0/mdc";
const { html, frontmatter, toc } = await processMdcToHtml(source, options);
```
### Parameters
| Parameter | Type | Description |
|---|---|---|
| `source` | `string` | Raw markdown/MDX source content |
| `options` | `MdcProcessorOptions` | Processing options (see below) |
### Return value
```ts
interface ProcessedMdcHtml {
html: string;
frontmatter: Record;
toc: TocEntry[];
}
```
---
## processMdc
Parses markdown source into a JSON AST (`MdcRoot`). Use this when you want programmatic control over rendering — for example, mapping AST nodes to Vue or Svelte components.
```ts
import { processMdc } from "@document0/mdc";
const { body, frontmatter, toc } = await processMdc(source, options);
```
### Return value
```ts
interface ProcessedMdc {
body: MdcRoot;
frontmatter: Record;
toc: TocEntry[];
}
```
The `body` is a tree of `MdcNode` objects (see [types](#types) below) that you can walk and render however you like.
---
## Options
Both `processMdc` and `processMdcToHtml` accept the same options:
```ts
interface MdcProcessorOptions {
highlighter?: HighlighterGeneric;
defaultLanguage?: string;
themes?: RehypeShikiThemes;
remarkPlugins?: unknown[];
rehypePlugins?: unknown[];
}
```
| Option | Default | Description |
|---|---|---|
| `highlighter` | — | Shiki highlighter instance. Omit to skip syntax highlighting |
| `defaultLanguage` | `"plaintext"` | Language for code blocks with no language specified |
| `themes` | `{ light: "github-light", dark: "github-dark" }` | Shiki dual-theme config |
| `remarkPlugins` | `[]` | Additional remark plugins |
| `rehypePlugins` | `[]` | Additional rehype plugins |
---
## Setting up Shiki
Create the highlighter once and reuse it (initialisation is expensive):
```ts
import { createHighlighter } from "shiki";
let highlighter: Awaited> | null = null;
export async function getHighlighter() {
if (highlighter) return highlighter;
highlighter = await createHighlighter({
themes: ["github-dark", "github-light"],
langs: ["typescript", "javascript", "bash", "json", "vue", "css"],
});
return highlighter;
}
```
Then pass it to either processor:
```ts
const highlighter = await getHighlighter();
const { html, toc } = await processMdcToHtml(raw, { highlighter });
```
---
## Types
### TocEntry
```ts
interface TocEntry {
id: string; // heading anchor id
text: string; // heading text content
depth: number; // 1–6
}
```
### MdcNode
The JSON AST node returned by `processMdc`:
```ts
interface MdcNode {
type: "element" | "text";
tag?: string; // HTML tag name (e.g. "h2", "p", "pre")
props?: Record; // element attributes
children?: MdcNode[];
value?: string; // text content (for type: "text")
}
```
### MdcRoot
```ts
interface MdcRoot {
type: "root";
children: MdcNode[];
}
```
---
## Built-in plugins
### rehypeShiki
Applied automatically when you pass a `highlighter`. Replaces fenced code blocks with Shiki-highlighted HTML output.
### rehypeStripShikiStyle
Also applied automatically. Strips inline `style` attributes from Shiki output so your CSS themes take over.
Both are re-exported if you need them in a custom unified pipeline:
```ts
import { rehypeShiki, rehypeStripShikiStyle } from "@document0/mdc";
```
---
## MDC vs MDX
| | `@document0/mdc` | `@document0/mdx` |
|---|---|---|
| Output | HTML string or JSON AST | Compiled JS module |
| Peer deps | None | `react`, `react-dom`, `@mdx-js/mdx` |
| Best for | Vue, Svelte, any non-React framework | React / Next.js |
| Custom components | Style with CSS or render JSON AST | JSX component map via `run()` |
Both packages share the same Shiki integration, frontmatter extraction, and TOC generation. Choose based on your framework.
---
# @document0/mdx
URL: https://document0.dev/docs/mdx
# @document0/mdx
The MDX package compiles your `.mdx` files into executable JavaScript, extracts frontmatter, and runs Shiki over code blocks, all in one call.
## processMdx
```ts
import { processMdx } from "@document0/mdx";
const { code, frontmatter, toc } = await processMdx(source, options);
```
### Parameters
| Parameter | Type | Description |
|---|---|---|
| `source` | `string` | Raw MDX/Markdown source content |
| `options` | `ProcessMdxOptions` | Processing options (see below) |
### Options
```ts
interface ProcessMdxOptions {
highlighter?: HighlighterGeneric;
defaultLanguage?: string; // default: "plaintext"
theme?: string; // default: "github-dark"
remarkPlugins?: PluggableList;
rehypePlugins?: PluggableList;
jsxRuntime?: "automatic" | "classic"; // default: "automatic"
}
```
| Option | Default | Description |
|---|---|---|
| `highlighter` | - | Shiki highlighter instance. Omit to skip syntax highlighting |
| `defaultLanguage` | `"plaintext"` | Language used for code blocks with no language specified |
| `theme` | `"github-dark"` | Shiki theme name |
| `remarkPlugins` | `[]` | Additional remark plugins to run |
| `rehypePlugins` | `[]` | Additional rehype plugins to run |
| `jsxRuntime` | `"automatic"` | JSX runtime mode passed to `@mdx-js/mdx` |
### Return value
```ts
interface ProcessMdxResult {
code: string; // compiled JS; pass to @mdx-js/mdx run()
frontmatter: Record;
toc: TocEntry[];
}
```
### TocEntry
```ts
interface TocEntry {
id: string; // heading anchor id (auto-generated from text)
text: string; // heading text content
depth: number; // 1–6
}
```
---
## Built-in plugins
### remarkToc
Extracts headings into `toc` without modifying the document tree.
### rehypeShiki
Replaces fenced code blocks with Shiki-highlighted hast output. Uses `codeToHast` internally so the result is proper hast nodes, compatible with `@mdx-js/mdx`'s JSX runtime.
Language is read from the code fence tag:
````md
```typescript
const x: number = 1;
```
````
---
## Setting up Shiki
Create the highlighter once and reuse it across requests (it's expensive to initialise):
```ts
import { createHighlighter } from "shiki";
let highlighter: Awaited> | null = null;
export async function getHighlighter() {
if (highlighter) return highlighter;
highlighter = await createHighlighter({
themes: ["github-dark", "github-light"],
langs: ["typescript", "javascript", "bash", "json", "css"],
});
return highlighter;
}
```
Then pass it to `processMdx`:
```ts
const highlighter = await getHighlighter();
const { code, toc } = await processMdx(raw, { highlighter, theme: "github-dark" });
```
---
## Running compiled MDX
The `code` returned by `processMdx` is a compiled JS module. Use `@mdx-js/mdx`'s `run` to execute it:
```ts
import { run } from "@mdx-js/mdx";
import * as runtime from "react/jsx-runtime";
const { default: MDXContent } = await run(code, {
...(runtime as object),
baseUrl: import.meta.url,
});
// Render it; pass your component overrides
```
---
## Custom MDX components
Pass a `components` object to override any HTML element:
```tsx
const components = {
h2: ({ children, id }) => (
{children}
),
pre: ({ children }) => (
{children}
),
};
```
---
# Sidebar Ordering
URL: https://document0.dev/docs/meta-json
# Sidebar Ordering
Place a `_meta.json` file in any content directory to control the order pages appear in the sidebar tree.
## Example
```json
{
"title": "API Reference",
"pages": [
"index",
"--- Core",
"dosource",
"buildpagetree",
"---",
"--- Navigation",
"getbreadcrumbs",
"getpageneighbours"
],
"defaultOpen": true,
"icon": "📖"
}
```
## Rules
- Strings starting with `"--- "` become labelled separators
- `"---"` alone becomes an unlabelled divider
- Pages not listed in `pages` are appended at the end in alphabetical order
- `defaultOpen` controls whether the folder starts expanded in the sidebar
- `icon` adds an emoji or string icon to the folder
## Static generation
Use `generateStaticParams` to pre-render all docs pages at build time:
```ts
export async function generateStaticParams() {
return (await source.getPages()).map((page) => ({
slug: page.slugs.filter(Boolean),
}));
}
```
This works with Next.js App Router's `[[...slug]]` catch-all route.
## Hot reload
During development, call `source.invalidate()` when content files change to clear all cached data:
```ts
source.invalidate();
```
This forces the next `getPages()`, `getPageTree()`, `getPage()`, or search query to re-read from disk.
---
# Next.js
URL: https://document0.dev/docs/nextjs
# Next.js
This guide builds a working docs site from scratch using Next.js App Router and document0.
## 1. Create a Next.js app
```bash
npx create-next-app@latest my-docs --typescript --tailwind --app --no-src-dir
cd my-docs
```
Or scaffold instantly with `create-document0`:
```bash
npx create-document0 my-docs
```
## 2. Install document0
```bash
npm install @document0/core @document0/mdx @document0/next-dev shiki @mdx-js/mdx
```
## 3. Configure Next.js
Add `serverExternalPackages` and wrap the config with **`withDocument0`** from **`@document0/next-dev`**:
```ts
// next.config.ts
import type { NextConfig } from "next";
import { withDocument0 } from "@document0/next-dev";
const nextConfig: NextConfig = {
serverExternalPackages: ["@document0/core", "@document0/mdx", "shiki"],
};
export default withDocument0({ contentDir: "content/docs" })(nextConfig);
```
`contentDir` is relative to your Next project root and must match the folder you pass to `DocsSource`.
## 4. Create your source loader
Import the **content stamp** once in the same file as `DocsSource` so the dev bundler treats your docs tree as a dependency (same idea as Fumadocs: content on the webpack graph). When anything under `content/docs` changes, this module re-runs and you get a fresh `DocsSource`.
```ts
// lib/source.ts
import "@document0/next-dev/content-stamp";
import path from "node:path";
import { DocsSource } from "@document0/core";
const rootDir = path.join(process.cwd(), "content/docs");
export const source = new DocsSource({ rootDir, baseUrl: "/docs" });
```
## 5. Create a Shiki highlighter
```ts
// lib/highlighter.ts
import { createHighlighter } from "shiki";
let highlighter: Awaited> | null = null;
export async function getHighlighter() {
if (highlighter) return highlighter;
highlighter = await createHighlighter({
themes: ["github-dark"],
langs: ["typescript", "javascript", "bash", "json"],
});
return highlighter;
}
```
## 6. Create your docs page
```tsx
// app/docs/[[...slug]]/page.tsx
import { notFound } from "next/navigation";
import { run } from "@mdx-js/mdx";
import * as runtime from "react/jsx-runtime";
import { source } from "@/lib/source";
import { getHighlighter } from "@/lib/highlighter";
import { processMdx } from "@document0/mdx";
export async function generateStaticParams() {
return (await source.getPages()).map((page) => ({
slug: page.slugs.filter(Boolean),
}));
}
export default async function DocPage({
params,
}: {
params: Promise<{ slug?: string[] }>;
}) {
const { slug } = await params;
const page = await source.getPage(slug ? slug.join("/") : "");
if (!page) notFound();
const highlighter = await getHighlighter();
const { code } = await processMdx(page.content, { highlighter });
const { default: MDXContent } = await run(code, {
...(runtime as object),
baseUrl: import.meta.url,
} as Parameters[1]);
return (
{page.frontmatter.title}
);
}
```
## 7. Add a search route
```ts
// app/internal/search/route.ts
import { createSearchRoute } from "@document0/core/search";
import { source } from "@/lib/source";
export const { GET } = createSearchRoute(source);
```
## 8. Add llms.txt routes (optional)
```ts
// app/llms.txt/route.ts
import { createLlmsTxtRoute } from "@document0/core/llms";
import { source } from "@/lib/source";
export const { GET } = createLlmsTxtRoute(source, {
title: "My Docs",
description: "Documentation for my project",
baseUrl: "https://docs.example.com",
});
```
## 9. Add some content
```bash
mkdir -p content/docs
```
Create `content/docs/index.mdx`:
```mdx
---
title: My Docs
description: Welcome to my documentation.
---
# My Docs
Hello world!
```
## Content hot reload in development
`DocsSource` caches pages after the first read. Editing markdown or `_meta.json` does not change your `app/**/*.tsx` modules, so by default the dev server would keep serving cached data.
**`@document0/next-dev`** fixes that in development by registering your **`contentDir`** as a **webpack context dependency** (via a small loader on `content-stamp`). Any file change under that directory invalidates the module that imports **`@document0/next-dev/content-stamp`**, which should be the same module that constructs **`DocsSource`** — so that file re-executes and reads from disk again.
Requirements:
- Run **`next dev`** with **webpack** (the default in many setups). Custom webpack is **not** used when you pass **`--turbo`**; use plain **`next dev`** or **`next dev --webpack`** if Turbopack is your default.
- Keep the **`content-stamp`** import in the same file as `new DocsSource(...)`.
Scaffolds from **`create-document0`** apply **`withDocument0`** and the content-stamp import by default.
**Vite** does not use this package. For manual file watching there, use **`watchDocsSource`** from **`@document0/core/watch`** in a dev plugin (for example **`server.ws.send({ type: "full-reload" })`** in `onInvalidate`). See the [@document0/core](/docs/core) package README and the [React + Vite](/docs/react-vite) guide.
## 10. Run the dev server
```bash
npm run dev
```
Open [http://localhost:3000/docs](http://localhost:3000/docs). Your docs site is live.
## Next steps
- Install UI components: `npx document0 add document0/sidebar document0/toc document0/search-dialog`
- [Core package reference](/docs/core): all APIs for source, navigation, search, and llms.txt
---
# Quickstart
URL: https://document0.dev/docs/quickstart
# Quickstart
document0 is framework-agnostic. The core packages (`@document0/core` and `@document0/mdx`) work anywhere Node.js runs. Pick your framework to get started:
## Framework guides
adding a test here for HMR
| Framework | Status |
|---|---|
| [Next.js](/docs/nextjs) | Available |
| [React (Vite)](/docs/react-vite) | Coming soon |
| [Vue](/docs/vue) | Coming soon |
| [Svelte](/docs/svelte) | Coming soon |
| [Astro](/docs/astro) | Coming soon |
| [Angular](/docs/angular) | Coming soon |
## Scaffold with create-document0
The fastest way to get started with Next.js:
```bash
npx create-document0 my-docs
cd my-docs
npm run dev
```
This scaffolds a complete working docs site. See [create-document0](/docs/create) for details.
## The basics (any framework)
Regardless of framework, the core workflow is the same:
### 1. Install
```bash
npm install @document0/core @document0/mdx shiki @mdx-js/mdx
```
### 2. Create a source
```ts
import { DocsSource } from "@document0/core";
const source = new DocsSource({
rootDir: "./content/docs",
baseUrl: "/docs",
});
```
### 3. Get pages and render
```ts
import { processMdx } from "@document0/mdx";
const page = await source.getPage("installation");
const { code, frontmatter, toc } = await processMdx(page.content, options);
```
### 4. Build navigation
```ts
const tree = await source.getPageTree(); // sidebar tree
const crumbs = getBreadcrumbs(tree, url); // breadcrumbs
const { previous, next } = getPageNeighbours(tree, url);
```
### 5. Add search
```ts
import { createSearchRoute } from "@document0/core/search";
export const { GET } = createSearchRoute(source);
```
Each framework guide covers how to wire these pieces into your specific routing and rendering layer.
## Next steps
- [Core package reference](/docs/core): all APIs for source scanning, navigation, search, and llms.txt
- [MDX package reference](/docs/mdx): MDX processing and Shiki options
- [CLI](/docs/cli): install plugins and components from the registry
- [Search](/docs/search): add full-text search to your docs
- [llms.txt](/docs/llms-txt): serve AI-ingestible documentation
---
# React (Vite)
URL: https://document0.dev/docs/react-vite
# React (Vite)
**Coming soon.** This guide is under development. In the meantime, see the [Quickstart](/docs/quickstart) for the framework-agnostic basics — `DocsSource` and `processMdx` work in any Node.js environment.
This guide will cover:
- Setting up document0 with Vite + React
- File-based routing with `react-router`
- Rendering MDX content client-side
- Adding search and navigation
---
# Search
URL: https://document0.dev/docs/search
# Search
document0 includes full-text search powered by [Orama](https://orama.com) with fuzzy matching and relevance ranking. The search index is built on first request and cached per `DocsSource` instance.
## 1. Create the search API route
```ts
// app/internal/search/route.ts
import { createSearchRoute } from "@document0/core/search";
import { source } from "@/lib/source";
export const { GET } = createSearchRoute(source);
```
This creates a `GET` endpoint at `/internal/search?q=...` that returns `SearchResult[]`.
## 2. SearchResult
```ts
interface SearchResult {
title: string;
description?: string;
url: string;
score: number;
}
```
## 3. Add a search UI
Install the pre-built search dialog from the registry:
```bash
document0 add document0/search-dialog
```
Or build your own — fetch from the endpoint and render the results however you like.
---
# Svelte
URL: https://document0.dev/docs/svelte
# Svelte
**Coming soon.** This guide is under development. In the meantime, see the [Quickstart](/docs/quickstart) for the framework-agnostic basics — `DocsSource` and `processMdx` work in any Node.js environment.
This guide will cover:
- Setting up document0 with SvelteKit
- File-based routing with SvelteKit routes
- Rendering MDX content in Svelte components
- Adding search and navigation
---
# Vue
URL: https://document0.dev/docs/vue
# Vue
This guide builds a working docs site from scratch using Vue 3, Vite, and document0.
Unlike the React/Next.js guides that use `@document0/mdx`, the Vue integration uses `@document0/mdc` — a lightweight Markdown Components processor that outputs HTML directly. No React, no JSX runtime, no server-side rendering shims.
## 1. Create a Vue + Vite app
```bash
npm create vite@latest my-docs -- --template vue-ts
cd my-docs
```
Install Vue Router and Tailwind CSS:
```bash
npm install vue-router@4 tailwindcss @tailwindcss/vite
```
## 2. Install document0
```bash
npm install @document0/core @document0/mdc shiki
```
| Package | Purpose |
|---|---|
| `@document0/core` | File-system source, page trees, navigation, search |
| `@document0/mdc` | MDC processing — parses markdown to HTML with Shiki highlighting |
| `shiki` | Syntax highlighting engine |
No React dependencies needed. `@document0/mdc` handles markdown processing and outputs HTML strings that Vue renders with `v-html`.
## 3. Create your source loader
```ts
// server/source.ts
import path from "node:path";
import { DocsSource, buildPageTree } from "@document0/core";
const rootDir = path.join(process.cwd(), "content/docs");
export const source = new DocsSource({ rootDir, baseUrl: "/docs" });
export async function getPageTree() {
return buildPageTree(await source.getPages(), rootDir);
}
```
## 4. Create a Shiki highlighter
```ts
// server/highlighter.ts
import { createHighlighter } from "shiki";
let highlighter: Awaited> | null = null;
export async function getHighlighter() {
if (highlighter) return highlighter;
highlighter = await createHighlighter({
themes: ["github-dark", "github-light"],
langs: ["typescript", "javascript", "vue", "bash", "json", "css", "html"],
});
return highlighter;
}
```
## 5. Create a Vite plugin to serve docs data
Since document0's core runs in Node.js (file system, MDC compilation, Shiki), you need a Vite plugin that serves page data as JSON during development. The Vue client fetches this data and renders it.
```ts
// server/api.ts
import fs from "node:fs";
import { source, getPageTree } from "./source";
import { getHighlighter } from "./highlighter";
import { processMdcToHtml } from "@document0/mdc";
import { getBreadcrumbs, getPageNeighbours } from "@document0/core";
import type { Plugin } from "vite";
async function getPage(slug: string) {
const page = await source.getPage(slug);
if (!page) return null;
const raw = fs.readFileSync(page.filePath, "utf-8");
const highlighter = await getHighlighter();
const { html, toc } = await processMdcToHtml(raw, { highlighter });
const tree = await getPageTree();
const breadcrumbs = getBreadcrumbs(tree, page.url);
const { previous, next } = getPageNeighbours(tree, page.url);
return {
title: page.frontmatter.title,
description: page.frontmatter.description,
html,
toc,
breadcrumbs,
previous,
next,
};
}
export function document0ApiPlugin(): Plugin {
return {
name: "document0-api",
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
if (req.url === "/api/tree") {
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(await getPageTree()));
return;
}
const match = req.url?.match(/^\/api\/page\/(.*)$/);
if (match) {
const data = await getPage(decodeURIComponent(match[1]));
if (!data) {
res.statusCode = 404;
res.end(JSON.stringify({ error: "Not found" }));
return;
}
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(data));
return;
}
next();
});
},
};
}
```
Register the plugin in `vite.config.ts`:
```ts
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import tailwindcss from "@tailwindcss/vite";
import { document0ApiPlugin } from "./server/api";
export default defineConfig({
plugins: [vue(), tailwindcss(), document0ApiPlugin()],
});
```
## 6. Set up routing
```ts
// src/main.ts
import { createApp } from "vue";
import { createRouter, createWebHistory } from "vue-router";
import App from "./App.vue";
import "./globals.css";
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/", redirect: "/docs" },
{
path: "/docs/:slug(.*)*",
component: () => import("./pages/DocPage.vue"),
},
],
});
const app = createApp(App);
app.use(router);
app.mount("#app");
```
## 7. Create the doc page
```vue
{{ page.title }}
```
The HTML returned by `processMdcToHtml` includes Shiki-highlighted code blocks, heading anchors, and GFM features (tables, task lists, etc.) — ready for `v-html`.
## 8. Add some content
```bash
mkdir -p content/docs
```
Create `content/docs/index.mdx`:
```mdx
---
title: My Docs
description: Welcome to my documentation.
---
# My Docs
Hello world!
```
## 9. Run the dev server
```bash
npm run dev
```
Open [http://localhost:5173/docs](http://localhost:5173/docs). Your docs site is live.
## MDC vs MDX
| | `@document0/mdx` | `@document0/mdc` |
|---|---|---|
| Output | Compiled JS (needs `run()` + React) | HTML string or JSON AST |
| Peer deps | `react`, `react-dom`, `@mdx-js/mdx` | None |
| Best for | React / Next.js | Vue, Svelte, or any non-React framework |
| Custom components | JSX component map | `v-html` styling or JSON AST renderer |
See the [@document0/mdc reference](/docs/mdc) for the full API.
## Next steps
- Install Vue UI components: `npx @document0/cli add document0-vue/sidebar document0-vue/toc document0-vue/search-dialog`
- Add a sidebar, table of contents, and breadcrumbs using the [registry components](/plugins)
- [Core package reference](/docs/core): all APIs for source, navigation, search, and llms.txt
- [@document0/mdc reference](/docs/mdc): processMdc, processMdcToHtml, types