# 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 ``` 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