HIP-45: Documentation Framework Standard. Status Draft. Hanzo's own standard — read this before implementing against it.
This proposal defines the documentation framework standard for the Hanzo ecosystem. All developer-facing documentation sites -- from API references to protocol specifications -- MUST be built using the @hanzo/docs-* package family, a purpose-built fork of the Fumadocs framework extended with multi-brand theming, OpenAPI generation, and cross-ecosystem search.
The framework ships as 24 independently versioned packages in a pnpm monorepo. Applications compose only the packages they need. A single unified wrapper (@hanzo/docs) re-exports everything for convenience.
Repository: github.com/hanzoai/docs Primary Package: @hanzo/docs (unified wrapper) Build Toolchain: tsdown for packages, Turbo for monorepo orchestration Runtime: Next.js 15+ with App Router, React 19+, Tailwind CSS 4+
This section explains the why behind each architectural decision. Documentation infrastructure is high-leverage -- the wrong choice here affects every product team and every developer who reads Hanzo docs.
Fumadocs is the best Next.js documentation framework available. It supports the App Router, React Server Components, and ships with a clean Radix UI component library. It is 40% lighter than Docusaurus and has first-class MDX support.
We forked it because we need capabilities that do not fit upstream's scope:
@hanzo/docs-* namespace so that consumers clearly depend on the Hanzo-maintained version, which receives security patches, multi-brand features, and ecosystem integrations that upstream does not.The fork maintains an upstream remote pointing to fuma-nama/fumadocs. We periodically merge upstream changes and resolve conflicts in our extension points. This gives us upstream improvements (bug fixes, performance, new Radix UI components) while maintaining our custom features.
Trade-off acknowledged: Maintaining a fork requires ongoing merge effort. We accept this because the alternative -- building a docs framework from scratch -- is orders of magnitude more work, and using upstream directly does not meet our multi-brand and OpenAPI requirements.
Each package is independently versioned and publishable. This matters because:
@hanzo/docs-core + @hanzo/docs-ui and nothing else.@hanzo/docs-openapi does not force a release of @hanzo/docs-core. Sites that do not use OpenAPI docs are unaffected.@hanzo/docs wrapper re-exports everything but consumers who import from it still benefit from tree-shaking at the module level.@hanzo/docs-core handles source loading and search. @hanzo/docs-ui handles Radix UI components. @hanzo/docs-openapi handles spec parsing and page generation. Contributors know where to make changes.The framework consists of 24 packages organized by responsibility:
| Package | NPM Name | Purpose | |---------|----------|---------| | packages/core | @hanzo/docs-core | Source loading, search indexing, i18n, page tree construction, breadcrumb generation, TOC extraction, syntax highlighting | | packages/mdx | @hanzo/docs-mdx | MDX processing, frontmatter parsing, content collections, file watching, Next.js/Vite integration | | packages/hanzo-docs | @hanzo/docs | Unified wrapper that re-exports all packages under a single import namespace |
| Package | NPM Name | Purpose | |---------|----------|---------| | packages/radix-ui | @hanzo/docs-ui | Full UI component library built on Radix primitives (sidebar, TOC, breadcrumb, search dialog, code blocks, tabs, cards, callouts) | | packages/base-ui | @hanzo/docs-base-ui | Headless UI components built on @base-ui/react for maximum styling flexibility | | packages/tailwind | @hanzo/docs-tailwind | Tailwind CSS utilities, preset configuration, and CSS custom property definitions | | packages/story | @hanzo/docs-story | Component story/preview system for documentation UI components |
| Package | NPM Name | Purpose | |---------|----------|---------| | packages/openapi | @hanzo/docs-openapi | Auto-generate endpoint documentation from OpenAPI 3.x specs, with interactive playground | | packages/typescript | @hanzo/docs-typescript | Auto-generate type tables from TypeScript declarations and .d.ts files | | packages/python | @hanzo/docs-python | Python API documentation generation (includes fumapy Python package for docstring extraction) | | packages/twoslash | @hanzo/docs-twoslash | TypeScript code hints and inline type annotations in code blocks | | packages/doc-gen | @hanzo/docs-docgen | Generic documentation generation utilities |
| Package | NPM Name | Purpose | |---------|----------|---------| | packages/mdx-remote | @hanzo/docs-mdx-remote | Remote MDX content loading (fetch MDX from URLs or CMS) | | packages/mdx-runtime | @hanzo/mdx-runtime | Runtime MDX compilation for dynamic content | | packages/obsidian | @hanzo/docs-obsidian | Obsidian vault adapter (use Obsidian markdown as docs source) | | packages/content-collections | @hanzo/docs-content-collections | Content Collections integration for type-safe content | | packages/press | @hanzo/docs-press | Minimal setup package for quick documentation sites |
| Package | NPM Name | Purpose | |---------|----------|---------| | packages/cli | @hanzo/docs-cli | CLI for scaffolding new doc sites, customizing components, and running dev tasks | | packages/create-app | @hanzo/docs-create-app | Project scaffolding (pnpm create @hanzo/docs-app) | | packages/create-app-versions | @hanzo/docs-create-app-versions | Version tracking for create-app templates | | packages/stf | @fumari/stf | Upstream schema/transform dependency (kept under original namespace) | | packages/org | @hanzo/docs-org | Organization-level shared utilities |
| Package | NPM Name | Purpose | |---------|----------|---------| | packages/tsconfig | tsconfig | Shared TypeScript configuration (base, Next.js, React library) | | packages/eslint-config-custom | eslint-config-custom | Shared ESLint configuration | | packages/shared | shared | Shared internal utilities and registry |
@hanzo/docs (unified wrapper)
|
+-- @hanzo/docs-core .............. source, search, i18n, page tree
| |
| +-- shiki .................. syntax highlighting
| +-- @orama/orama .......... full-text search engine
| +-- unified/remark ........ markdown processing pipeline
|
+-- @hanzo/docs-mdx .............. MDX processing, collections
| |
| +-- @mdx-js/mdx ........... MDX compiler
| +-- chokidar .............. file watching
| +-- esbuild ............... fast JS/TS bundling
|
+-- @hanzo/docs-ui ............... Radix UI components
| |
| +-- @radix-ui/* ........... UI primitives
| +-- motion ................ animations
| +-- next-themes ........... dark/light mode
| +-- tailwind-merge ........ class merging
|
+-- @hanzo/docs-openapi .......... OpenAPI doc generation
| |
| +-- @scalar/openapi-parser . spec parsing
| +-- openapi-sampler ....... example generation
| +-- react-hook-form ....... playground forms
|
+-- @hanzo/docs-typescript ....... TS type tables
+-- @hanzo/docs-python ........... Python docgen
+-- @hanzo/docs-twoslash ......... TS code hints
+-- @hanzo/docs-obsidian ......... Obsidian adapter
+-- @hanzo/docs-press ............ minimal setup
+-- @hanzo/docs-cli .............. scaffolding
+-- @hanzo/docs-mdx-remote ....... remote MDX
+-- @hanzo/mdx-runtime ........... runtime MDX
+-- @hanzo/docs-content-collections
+-- @hanzo/docs-docgen
| Site | URL | App Directory | Purpose | |------|-----|---------------|---------| | Main Docs | docs.hanzo.ai | apps/docs | Primary Hanzo documentation, API reference | | HIPs | hips.hanzo.ai | ../hips/docs | Hanzo Improvement Proposals | | Zen LM | zenlm.org | apps/zen-docs | Zen model family documentation | | ZAP Protocol | zap.hanzo.ai | apps/zap-docs | ZAP protocol specification | | Dev Docs | dev.hanzo.ai | apps/dev-docs | Developer guides and tutorials | | Bot Docs | bot.hanzo.ai | apps/bot-docs | Bot framework documentation |
Every documentation site MUST define a source.config.ts at its root:
import { defineConfig, defineDocs } from '@hanzo/docs-mdx/config';
export default defineConfig({
docs: defineDocs({
dir: 'content/docs',
}),
});
This file declares the content sources. The MDX package uses it to:
Each site MUST create a source loader in lib/source.ts:
import { docs } from '@/.source';
import { loader } from '@hanzo/docs-core/source';
export const source = loader({
baseUrl: '/docs',
source: docs.toFumadocsSource(),
});
The loader transforms raw content into a structured page tree with:
01-getting-started.mdx)meta.json for custom orderingDocumentation pages follow this pattern:
import { source } from '@/lib/source';
import { DocsPage, DocsBody } from '@hanzo/docs-ui/layouts/docs/page';
import defaultMdxComponents from '@hanzo/docs-ui/mdx';
import { notFound } from 'next/navigation';
export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();
const MDX = page.data.body;
return (
<DocsPage toc={page.data.toc}>
<DocsBody>
<MDX components={{ ...defaultMdxComponents }} />
</DocsBody>
</DocsPage>
);
}
The framework provides four layout variants:
| Layout | Import Path | Use Case | |--------|------------|----------| | Docs | @hanzo/docs-ui/layouts/docs | Standard documentation with sidebar navigation | | Home | @hanzo/docs-ui/layouts/home | Landing pages with hero sections and feature grids | | Notebook | @hanzo/docs-ui/layouts/notebook | Notebook-style layout for tutorials | | Flux | @hanzo/docs-ui/layouts/flux | Alternative layout with fluid navigation |
Each layout provides a root layout component and a page component:
// app/docs/layout.tsx
import { DocsLayout } from '@hanzo/docs-ui/layouts/docs';
import { source } from '@/lib/source';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<DocsLayout tree={source.pageTree}>
{children}
</DocsLayout>
);
}
Each site configures its brand identity through CSS custom properties and layout configuration. The theming system uses three layers:
/* Brand: Hanzo */
:root {
--fd-primary: 0 84% 61%; /* #fd4444 in HSL */
--fd-background: 0 0% 4%;
--fd-foreground: 0 0% 98%;
--fd-muted: 0 0% 15%;
--fd-accent: 0 84% 61%;
--fd-border: 0 0% 15%;
--fd-ring: 0 84% 61%;
}
/* Brand: Zen */
:root {
--fd-primary: 210 100% 50%;
--fd-accent: 210 100% 50%;
}
<DocsLayout
tree={source.pageTree}
nav={{
title: 'Hanzo Docs',
url: 'https://docs.hanzo.ai',
}}
sidebar={{
banner: <Logo brand="hanzo" />,
}}
links={[
{ text: 'GitHub', url: 'https://github.com/hanzoai' },
{ text: 'Discord', url: 'https://discord.gg/CJCyAsm9Vr' },
]}
>
Each brand's content lives in its own content/ directory. The source loader is configured per-app, so cross-brand content leakage is impossible at the build level.
The framework supports three search backends:
Full-text search using Orama, indexed at build time:
import { source } from '@/lib/source';
import { createSearchAPI } from '@hanzo/docs-core/search/server';
export const { GET } = createSearchAPI('advanced', {
indexes: source.getPages().map((page) => ({
title: page.data.title,
description: page.data.description,
url: page.url,
structuredData: page.data.structuredData,
})),
});
For sites that need hosted search with analytics:
import { createSearchAPI } from '@hanzo/docs-core/search/algolia-server';
export const { GET } = createSearchAPI({
appId: process.env.ALGOLIA_APP_ID!,
apiKey: process.env.ALGOLIA_API_KEY!,
indexName: 'hanzo-docs',
});
For integration with Hanzo Search (HIP-0012):
import { createSearchAPI } from '@hanzo/docs-core/search/server';
export const { GET } = createSearchAPI('custom', {
search: async (query) => {
const results = await fetch(`https://search.hanzo.ai/api/v1/search?q=${query}`);
return results.json();
},
});
Built-in i18n support with file-based routing:
content/
docs/
index.mdx # Default language (en)
index.zh.mdx # Chinese
index.ja.mdx # Japanese
Configuration:
// lib/source.ts
import { loader } from '@hanzo/docs-core/source';
import { i18n } from '@hanzo/docs-core/i18n';
export const { source, openapi } = loader({
baseUrl: '/docs',
source: docs.toFumadocsSource(),
i18n: {
languages: ['en', 'zh', 'ja'],
defaultLanguage: 'en',
},
});
Middleware for locale negotiation:
// middleware.ts
import { createI18nMiddleware } from '@hanzo/docs-core/i18n/middleware';
export default createI18nMiddleware({
languages: ['en', 'zh', 'ja'],
defaultLanguage: 'en',
});
The framework provides a standard set of MDX components available in all documentation pages:
| Component | Description | Usage | |-----------|-------------|-------| | Callout | Info/warning/error callouts | <Callout type="warn">Text</Callout> | | Card | Linked card with title and description | <Card title="Guide" href="/docs/guide"> | | Cards | Card grid container | <Cards><Card .../><Card .../></Cards> | | Tab / Tabs | Tabbed content panels | <Tabs items={['npm', 'pnpm']}><Tab>...</Tab></Tabs> | | Step / Steps | Numbered step-by-step guides | <Steps><Step>...</Step></Steps> | | TypeTable | Auto-generated type property tables | <TypeTable type={MyInterface} /> | | Accordion | Collapsible content sections | <Accordion title="Details">...</Accordion> | | Files | File tree visualization | <Files><File name="app.ts" /><Folder name="lib" /></Files> | | ImageZoom | Zoomable images | <ImageZoom src="/img/arch.png" /> | | CodeBlock | Syntax-highlighted code with copy button | Automatic from fenced code blocks | | InlineTOC | Inline table of contents | <InlineTOC /> | | Banner | Announcement banner | <Banner>New release!</Banner> |
These are registered as default MDX components and available in all .mdx files without explicit imports.
The @hanzo/docs-openapi package generates documentation pages from OpenAPI 3.x specification files.
// source.config.ts
import { defineConfig, defineDocs } from '@hanzo/docs-mdx/config';
import { defineOpenAPI } from '@hanzo/docs-openapi';
export default defineConfig({
docs: defineDocs({ dir: 'content/docs' }),
openapi: defineOpenAPI({
input: './openapi-specs/*.yaml',
output: './content/docs/api',
}),
});
For each endpoint in the spec, the package generates:
For interactive API exploration, the OpenAPI package integrates with Scalar:
import { createScalarPage } from '@hanzo/docs-openapi/scalar';
export default createScalarPage({
spec: './openapi-specs/hanzo-api.yaml',
theme: 'kepler',
});
The @hanzo/docs-typescript package generates type tables from TypeScript declarations:
import { AutoTypeTable } from '@hanzo/docs-typescript/ui';
// Automatically generates a property table from the TypeScript interface
<AutoTypeTable path="./src/types.ts" name="UserConfig" />
This resolves the full type, including inherited properties, generics, and JSDoc comments. The output is a structured table showing property name, type, default value, and description.
The @hanzo/docs-python package includes a Python component (fumapy) that extracts docstrings and type annotations:
# Generate Python API docs
uv run fumapy generate --module hanzo.sdk --output content/docs/python-sdk
The generated MDX files include function signatures, parameter tables, return types, and docstring content.
All packages (except the @hanzo/docs wrapper) use tsdown for compilation:
// packages/core/tsdown.config.ts
import { defineConfig } from 'tsdown';
export default defineConfig({
entry: ['src/**/*.ts', 'src/**/*.tsx'],
format: 'esm',
dts: true,
clean: true,
});
The @hanzo/docs wrapper uses tsup because it needs special re-export handling.
Turbo manages the build graph:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "out"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
The ^build dependency ensures packages build before the apps that consume them.
Documentation apps use Next.js with static export:
// next.config.ts
import { createMDX } from '@hanzo/docs-mdx/next';
const withMDX = createMDX();
export default withMDX({
output: 'export', // Static HTML for CDN deployment
images: { unoptimized: true },
});
Documentation sites support three deployment targets:
Sites with server-side features (search API routes, ISR) deploy to Vercel:
# Automatic via GitHub integration
# Or manual:
vercel deploy --prod
For globally distributed static sites with edge functions:
// open-next.config.ts (for Cloudflare adapter)
export default {
default: {
override: {
wrapper: 'cloudflare-node',
converter: 'edge',
},
},
};
Deploy via Wrangler:
pnpm build && wrangler pages deploy out
For maximum simplicity, export static HTML and serve from any HTTP server:
pnpm build # Produces ./out/ directory
# Serve via nginx, Caddy, or S3+CloudFront
The apps/bot-docs and apps/zt-docs sites use this pattern with nginx in a Docker container:
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN pnpm install && pnpm build
FROM nginx:alpine
COPY --from=builder /app/out /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
New documentation sites MUST follow this structure:
apps/my-docs/
app/
layout.tsx # Root layout with DocsLayout
page.tsx # Landing page
docs/
[[...slug]]/
page.tsx # Dynamic docs page
api/
search/
route.ts # Search API endpoint
content/
docs/
index.mdx # Docs root page
getting-started.mdx
guides/
meta.json # Navigation ordering
installation.mdx
configuration.mdx
lib/
source.ts # Source loader configuration
source.config.ts # Content source definition
mdx-components.tsx # MDX component registration
next.config.ts # Next.js configuration with MDX
package.json
postcss.config.mjs
tsconfig.json
The @hanzo/docs-cli can scaffold this structure:
npx @hanzo/docs-create-app my-docs
The fork maintains compatibility with upstream Fumadocs. The sync process:
# 1. Fetch upstream changes
git remote add upstream https://github.com/fuma-nama/fumadocs.git # one-time
git fetch upstream
# 2. Create merge branch
git checkout -b merge-upstream-YYYY-MM-DD
# 3. Merge upstream dev branch
git merge upstream/dev
# 4. Resolve conflicts (usually in package.json names and brand-specific files)
# 5. Run bulk rename script
bash scripts/mirror-ui-change.sh
# 6. Verify build
pnpm install && pnpm build && pnpm test
# 7. Open PR for review
The scripts/setup-fork.sh script automates creating brand-specific documentation forks for ecosystem partners.
During upstream sync, all references are renamed:
| Upstream Name | Hanzo Name | |--------------|------------| | fumadocs-core | @hanzo/docs-core | | fumadocs-ui | @hanzo/docs-ui | | fumadocs-mdx | @hanzo/docs-mdx | | fumadocs-openapi | @hanzo/docs-openapi | | fumadocs-typescript | @hanzo/docs-typescript | | fumadocs-twoslash | @hanzo/docs-twoslash | | @fumadocs/cli | @hanzo/docs-cli | | @fumadocs/base-ui | @hanzo/docs-base-ui | | @fumadocs/story | @hanzo/docs-story | | @fumadocs/tailwind | @hanzo/docs-tailwind | | fumadocs-docgen | @hanzo/docs-docgen |
Note: packages/radix-ui publishes as @hanzo/docs-ui (the primary UI package). The original fumadocs-ui name maps to this package.
github.com/hanzoai/docs/
.changeset/ # Changeset configuration for versioning
.github/
workflows/
deploy-docs.yml # Deploy docs.hanzo.ai
deploy-zen-docs.yml # Deploy zenlm.org
deploy-zap-docs.yml # Deploy zap.hanzo.ai
deploy-bot-docs.yml # Deploy bot.hanzo.ai
lint.yml # Lint all packages
test.yml # Test all packages
release.yml # Publish to npm via changesets
apps/
docs/ # docs.hanzo.ai
zen-docs/ # zenlm.org
zap-docs/ # zap.hanzo.ai
bot-docs/ # bot.hanzo.ai
dev-docs/ # dev.hanzo.ai
cloud/ # cloud docs
flow/ # flow docs
zt-docs/ # zt docs
packages/ # 24 packages (see Package Architecture)
examples/ # 24 example applications
scripts/
build-all-docs.sh # Build all doc sites
dev-all-docs.sh # Run all doc sites in dev mode
mirror-ui-change.sh # Sync UI changes across radix-ui and base-ui
setup-fork.sh # Create branded doc forks
cf-pages-deploy.mjs # Cloudflare Pages deployment script
package.json # Root workspace configuration
pnpm-workspace.yaml # Workspace package declarations
turbo.json # Turbo build pipeline configuration
vitest.config.ts # Test configuration
Packages use Changesets for version management:
# Add a changeset
pnpm changeset
# Version all changed packages
pnpm version
# Publish to npm
pnpm release
The release process:
pnpm changeset and selects affected packageschangeset version to bump versionsturbo run build --filter=./packages/* to build all packageschangeset publish to push to npmpostpublish scripts run any post-publish tasks# pnpm-workspace.yaml
packages:
- packages/*
- apps/*
- examples/*
All packages, apps, and examples are part of the workspace. Internal dependencies use workspace:* protocol for always-latest linking during development.
release.yml)Triggered on push to main. Uses changesets to determine which packages need publishing:
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install
- uses: changesets/action@v1
with:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
deploy-*.yml)Each documentation site has its own deployment workflow. Example for docs.hanzo.ai:
name: Deploy Docs
on:
push:
branches: [main]
paths:
- 'apps/docs/**'
- 'packages/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm build --filter docs
- uses: cloudflare/pages-deploy-action@v1
with:
directory: apps/docs/out
project-name: hanzo-docs
| Dependency | Minimum Version | Tested Version | |-----------|----------------|----------------| | Node.js | 18.17.0 | 20.x, 22.x | | Next.js | 15.3.0 | 16.1.x | | React | 19.2.0 | 19.2.x | | Tailwind CSS | 4.0.0 | 4.1.x | | pnpm | 10.0.0 | 10.18.x | | TypeScript | 5.5.0 | 5.9.x |
Every documentation app requires these configuration files:
next.config.tsimport { createMDX } from '@hanzo/docs-mdx/next';
const withMDX = createMDX();
export default withMDX({
output: 'export',
});
postcss.config.mjsexport default {
plugins: {
'@tailwindcss/postcss': {},
},
};
mdx-components.tsximport type { MDXComponents } from 'mdx/types';
import defaultComponents from '@hanzo/docs-ui/mdx';
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...defaultComponents,
...components,
};
}
rehype-raw plugin processes inline HTML through a sanitizer before rendering.sk-..., your-api-key).pnpm-lock.yaml is committed and verified in CI.| Metric | Description | Target | |--------|-------------|--------| | docs.build.duration_seconds | Full build time for a documentation site | < 120s | | docs.package.build_seconds | Individual package build time | < 30s | | docs.bundle.size_kb | Client-side JavaScript bundle size | < 250KB | | docs.pages.count | Total pages across all sites | Informational | | docs.search.index_size_kb | Search index size | < 5MB per site |
All documentation sites MUST meet these Lighthouse scores:
| Category | Minimum Score | |----------|--------------| | Performance | 90 | | Accessibility | 95 | | Best Practices | 95 | | SEO | 95 |
Documentation sites are monitored via the Hanzo status page. Target uptime: 99.9%.
# 1. Scaffold a new docs site
npx @hanzo/docs-create-app my-docs
# 2. Install dependencies
cd my-docs && pnpm install
# 3. Add content
# Create .mdx files in content/docs/
# 4. Run development server
pnpm dev
# 5. Build for production
pnpm build
Copyright and related rights waived via CC0.