Relevant source files
The following files were used as context for generating this wiki page:
- packages/content/src/index.ts
- packages/mdx/src/runtime/server.ts
- packages/content/src/runtime.ts
- packages/preview/src/pages/...slugs.tsx
- packages/openapi/src/ui/base.tsx
- packages/mdx/src/runtime/dynamic.ts
- packages/mdx/src/plugins/index-file.ts
- packages/content-collections/src/index.ts
- packages/basehub/package.json
- packages/local-md/src/index.ts
- packages/sanity/src/index.ts
- packages/asyncapi/src/server/index.tsx
- packages/base-ui/registry/index.ts
- packages/preview/src/lib/source/storage.ts
- packages/content-collections/src/types.ts
- packages/create-app/template/+orama-cloud/@app/lib/export-static-indexes.ts
- packages/base-ui/src/provider/waku.tsx
- packages/openapi/src/utils/pages/builder.ts
- packages/preview/src/lib/source/index.ts
- packages/base-ui/src/layouts/flux/page/index.tsx
- packages/base-ui/src/utils/use-footer-items.ts
- packages/content-collections/src/configuration.ts
- packages/preview/src/components/provider.tsx
- packages/base-ui/package.json
- packages/basehub/src/toc.tsx
- packages/base-ui/src/layouts/notebook/page/index.tsx
- packages/preview/src/layouts/config.tsx
- packages/asyncapi/src/utils/pages/builder.ts
- packages/preview/src/pages/_api/api/search.ts
- packages/radix-ui/src/layouts/flux/page/index.tsx
Overview
The @fumadocs/basehub package provides official integration tools to connect BaseHub content services with Fumadocs documentation projects. It exports table of contents parsers and utilities that bridge BaseHub data structures with core Fumadocs source loader conventions.
Sources: packages/basehub/package.json:1-48, packages/basehub/src/toc.tsx:1-49
Package Architecture and Dependencies
Manifest Configuration
The @fumadocs/basehub package configures its build output as an ECMAScript module (type: "module") with public publishing access. Its exports map entry points standardizing consumer imports, exposing the table of contents module at ./dist/toc.mjs and the package manifest at ./package.json.
| Export Path | Target File | Purpose |
|---|---|---|
. | ./dist/toc.mjs | Main compiled entry point for table of contents parsing utilities |
./package.json | ./package.json | Package metadata definition for module resolution |
Sources: packages/basehub/package.json:1-23
Development and Build Scripts
The package uses tsdown driven by modern build pipelines and oxlint for linting. The build configuration leverages workspace dependencies for shared tooling and core Fumadocs packages.
| Script Name | Command | Purpose |
|---|---|---|
build | tsdown | Compiles source files into distribution artifacts |
clean | rimraf dist | Removes built distribution files |
dev | tsdown --watch --clean false | Runs the build tool in continuous watch mode |
lint | oxlint . | Executes linter checks across the package |
types:check | tsc --noEmit | Verifies TypeScript type correctness without emitting outputs |
Sources: packages/basehub/package.json:24-30
Dependencies and Peer Relationships
Dependencies are split between build-time development tools and runtime peer requirements. The package relies on basehub for upstream API definitions and fumadocs-core for structural table of contents types.
| Dependency Name | Version Specifier | Dependency Type | Purpose |
|---|---|---|---|
@types/node | 25.9.2 | devDependencies | TypeScript definitions for Node.js runtime APIs |
@types/react | ^19.2.17 / * | devDependencies / peerDependencies | TypeScript definitions and types for React components |
basehub | 9.6.0 / 9.x.x | devDependencies / peerDependencies | Upstream BaseHub client and API transaction types |
fuma-cli | ^0.1.1 | devDependencies | CLI toolset for Fuma workspace utilities |
fumadocs-core | workspace:* / ^16.8.0 | devDependencies / peerDependencies | Core Fumadocs framework utilities and TOC types |
fumadocs-ui | workspace:* | devDependencies | UI component library for Fumadocs themes |
react | ^19.2.7 / ^19.2.0 | devDependencies / peerDependencies | React library runtime dependency |
tsconfig | workspace:* | devDependencies | Shared workspace TypeScript configurations |
tsdown | 0.22.2 | devDependencies | Bundling tool built on esbuild |
typescript | ^6.0.3 | devDependencies | TypeScript compiler |
Sources: packages/basehub/package.json:31-48
Note
The @types/react peer dependency is marked as optional in peerDependenciesMeta, allowing consuming projects flexibility in their React typing setups while enforcing the basehub 9.x.x and fumadocs-core ^16.8.0 runtime contracts.
Sources: packages/basehub/package.json:43-53
Table of Contents Transformation
Options Configuration and Interface
The parseToc function extracts table of contents entries from BaseHub rich-text structures. It accepts an options object conforming to the ParseTOCOptions interface, which defines the input node structures, starting nesting level, and a rendering callback for rich text nodes.
| Option Property | Type | Default Value | Description |
|---|---|---|---|
node | RichTextTocNode | RichTextTocNode[] | none (required) | Root AST node or array of nodes representing the table of contents data from BaseHub |
initialLevel | number | 0 | Base heading depth offset applied during recursive tree traversal |
render | (node: RichTextNode) => ReactNode | none (required) | Callback function responsible for rendering rich text child nodes into React nodes |
Sources: packages/basehub/src/toc.tsx:5-12
export interface ParseTOCOptions {
node: RichTextTocNode | RichTextTocNode[];
/** @default 0 */
initialLevel?: number;
/** render rich text node */
render: (node: RichTextNode) => ReactNode;
}Sources: packages/basehub/src/toc.tsx:5-12
Recursive Traversal and Node Processing
The traversal algorithm processes root nodes, accepting either a single RichTextTocNode or an array of nodes. It invokes an internal next function recursively, incrementing levels when encountering list items or ordered lists while ignoring nodes without content or text types.
export function parseToc(options: ParseTOCOptions): TOCItemType[] {
const { node: root, render, initialLevel = 0 } = options;
const results: TOCItemType[] = [];
function next(node: RichTextTocNode, level = initialLevel) {
if (node.type === 'text' || !node.content) return;
for (const item of node.content) {
if (item.type === 'orderedList' || item.type === 'listItem') {
next(item, level + 1);
continue;
}
if (item.type !== 'paragraph' || !item.content || item.content.length === 0) continue;
for (const text of findTextNode(item.content[0])) {
const mark = text.marks?.find((m) => m.type === 'link');
if (!mark) continue;
results.push({
depth: level + 1,
url: mark.attrs.href,
title: render({
...text,
marks: text.marks?.filter((m) => m.type !== 'link'),
} as RichTextNode),
});
}
}
}
if (Array.isArray(root)) for (const item of root) next(item);
else next(root);
return results;
}Sources: packages/basehub/src/toc.tsx:14-49
Warning
Paragraph items without link marks are skipped during tree traversal. Each target TOC item requires a valid link mark inside its text node structure to supply the url attribute for Fumadocs navigation.
Sources: packages/basehub/src/toc.tsx:27-35
Text Node Extraction and Filtering
Text nodes are flattened using the findTextNode helper utility, which recursively unpacks nested node content into an array of text leaf nodes. The parser inspects the marks of each extracted text node for link types, strips the link mark before passing the node to the render callback, and constructs the final TOCItemType entry with the calculated depth and anchor URL.
function findTextNode(n: RichTextTocNode): Extract<RichTextTocNode, { type: 'text' }>[] {
if (n.type === 'text') {
return [n];
}
return n.content?.flatMap(findTextNode) ?? [];
}Sources: packages/basehub/src/toc.tsx:51-57
Tip
When filtering marks during title rendering, parseToc removes only the link mark type from the text node so that other formatting marks like bold or italic remain intact for the rendering callback.
Sources: packages/basehub/src/toc.tsx:36-40
Rich Text Structure Mapping
AST Node Parsing and Flattening
The extraction of table of contents entries from BaseHub abstract syntax trees relies on recursive AST node traversal and flattening helpers. The findTextNode function processes incoming RichTextTocNode items, checking if an individual node matches the text type or traversing nested content arrays via flatMap.
function findTextNode(n: RichTextTocNode): Extract<RichTextTocNode, { type: 'text' }>[] {
if (n.type === 'text') {
return [n];
}
return n.content?.flatMap(findTextNode) ?? [];
}Sources: packages/basehub/src/toc.tsx:51-57
Note
When findTextNode encounters a node without a content property or a non-text node lacking nested children, it safely returns an empty array to prevent runtime traversal errors during deep AST recursion.
Sources: packages/basehub/src/toc.tsx:56-56
Heading Hierarchy and Depth Resolution
The parser builds heading depth hierarchies by tracking nesting levels across ordered lists, list items, and paragraph blocks. The parseToc routine handles root inputs as either single node objects or arrays, iterating through child collections to assign appropriate depth offsets.
| AST Node Type / Check | Condition | Action Taken |
|---|---|---|
Array.isArray(root) | Root input is an array | Iterates over each item, invoking next(item) |
node.type === 'text' || !node.content | Leaf node or empty container | Terminates current traversal branch immediately |
item.type === 'orderedList' || item.type === 'listItem' | List container or list item | Recursively invokes next(item, level + 1) |
item.type !== 'paragraph' || !item.content || length === 0 | Non-paragraph or empty block | Skips item iteration via continue |
Sources: packages/basehub/src/toc.tsx:18-28
function next(node: RichTextTocNode, level = initialLevel) {
if (node.type === 'text' || !node.content) return;
for (const item of node.content) {
if (item.type === 'orderedList' || item.type === 'listItem') {
next(item, level + 1);
continue;
}
if (item.type !== 'paragraph' || !item.content || item.content.length === 0) continue;
Sources: packages/basehub/src/toc.tsx:18-28
Warning
Paragraph blocks that contain empty content arrays are bypassed during text extraction. The parser explicitly verifies that item.content.length > 0 before attempting to access nested text children.
Sources: packages/basehub/src/toc.tsx:27-27
Source Loader Integration Patterns
Content Source Adapters and Collection Stores
Fumadocs manages remote headless CMS queries and static document files by bridging collection adapters into standardized source trees. The toFumadocsSource helper function transforms active file collection stores—specifically MDX stores and metadata stores—into a uniform StaticSource structure containing explicit page and metadata file entries.
export function toFumadocsSource<
Mdx extends MDXStoreData<PageData> | MDXStoreLazyData<PageData, unknown> = MDXStoreData<
PageData,
unknown
>,
Meta extends { data: MetaData } = { data: MetaData },
>(mdxStore?: FileCollectionStore<Mdx>, metaStore?: FileCollectionStore<Meta>) {
const out: StaticSource<{
pageData: ToPageData<Mdx>;
metaData: Meta['data'];
}> = { files: [] };
for (const page of mdxStore?.list() ?? []) {
out.files.push({
type: 'page',
data: ('compiled' in page
? { ...page, ...page.compiled.frontmatter }
: { ...page, ...page.frontmatter }) as unknown as ToPageData<Mdx>,
path: page.path,
absolutePath: page.fullPath,
});
}
for (const file of metaStore?.list() ?? []) {
out.files.push({
type: 'meta',
data: file.data,
path: file.path,
absolutePath: file.fullPath,
});
}
return out;
}Sources: packages/content/src/runtime.ts:12-42
Note
During collection store iteration, toFumadocsSource inspects page objects for a compiled property, merging frontmatter attributes from either page.compiled.frontmatter or page.frontmatter depending on the compilation state of the underlying store item.
Sources: packages/content/src/runtime.ts:26-31
Dynamic Sanity and Content Collection Routing
Headless CMS platforms like Sanity map remote document structures to virtual file paths by resolving document slugs and types through client fetch options. The createSanitySource function queries documents filtered by document type, constructs filesystem-like paths via generatePath or slug arrays, and attaches lazy loading handlers and structured data extraction routines to each page record.
export function createSanitySource<Doc extends BaseDoc>(
options: SanityOptions<Doc>,
): DynamicSource<{
pageData: DocToPage<Doc>;
metaData: MetaData;
}> {
const { docType, baseDir, generatePath } = options;
let sanityFetch: <R = unknown>(query: string, params?: QueryParams) => Promise<R>;
if ('sanityFetch' in options) {
const fn = options.sanityFetch;
sanityFetch = async (query, params) => {
const res = await fn({ query, params });
return res.data as never;
};
} else {
const client = options.client;
sanityFetch = client.fetch.bind(client);
}
return {
async files() {
const data = await sanityFetch<ShallowDoc<Doc>[]>(
`*[_type == $docType]{ _id, _type, title, slug, description }`,
{
docType,
},
);
return data.map((file) => {
const slugs = file.slug?.current?.split('/').filter((v) => v.length > 0) ?? [];
let filePath = generatePath
? generatePath(file)
: slugs.length === 0
? 'index.mdx'
: `${slugs.join('/')}.mdx`;
if (baseDir) filePath = path.join(baseDir, filePath);
return {
type: 'page',
data: {
...file,
title: file.title ?? file._id,
async load() {
const data = await sanityFetch<Doc & { _toc?: PortableTextBlock[] }>(
`*[_type == $docType && _id == $id][0]{
...,
"_toc": body[style in ["h1", "h2", "h3", "h4", "h5", "h6"]]
}`,
{
id: file._id,
docType,
},
);
return {
...data,
_toc: data._toc ?? [],
renderToc(opts) {
if (!data._toc) return [];
return renderToc({ toc: data._toc, ...opts });
},
};
},
async structuredData() {
const data = await sanityFetch<{ structuredBody: StructuredBlock[] } | undefined>(
`*[_type == $docType && _id == $id][0]{
"structuredBody": body[]{
...,
_type == "block" => {
"heading": select(
style == "h1" => "h1",
style == "h2" => "h2",
style == "h3" => "h3",
style == "h4" => "h4",
style == "h5" => "h5",
style == "h6" => "h6",
null
),
"content": pt::text(@)Sources: packages/sanity/src/index.ts:63-161
| Configuration Option | Type | Purpose |
|---|---|---|
docType | string | CMS document type identifier for filtering queries |
generatePath | (doc: ShallowDoc<Doc>) => string | Custom virtual file path generator function |
baseDir | string | Base directory prefix applied to virtual file paths |
client | SanityClient | Direct Sanity client instance for fetching queries |
sanityFetch | DefinedFetchType | Live or custom fetch handler for Next.js Sanity integrations |
Sources: packages/sanity/src/index.ts:16-36
Tip
When slug arrays evaluate to an empty collection during Sanity source ingestion, the loader defaults the virtual file path to index.mdx before applying any configured baseDir offsets.
Sources: packages/sanity/src/index.ts:93-99
Page Tree and Navigation Synthesis
Hierarchical Tree Traversal and Footer Item Resolution
Fumadocs builds sequential page navigation and linear document flows by traversing root and folder nodes in the standard page tree structure. The useFooterItems utility accesses the active page tree context, walking down folder collections to extract inner index pages and child items while systematically ignoring external links and non-page nodes.
export function useFooterItems(): PageTree.Item[] {
const { root } = useTreeContext();
const cached = footerCache.get(root);
if (cached) return cached;
const list: PageTree.Item[] = [];
function onNode(node: PageTree.Node) {
if (node.type === 'folder') {
if (node.index) onNode(node.index);
for (const child of node.children) onNode(child);
} else if (node.type === 'page' && !node.external) {
list.push(node);
}
}
for (const child of root.children) onNode(child);
footerCache.set(root, list);
return list;Sources: packages/base-ui/src/utils/use-footer-items.ts:10-27
Note
Performance is optimized across tree rendering cycles by caching computed footer lists in a WeakMap keyed directly against the PageTree.Root or PageTree.Folder instance.
Sources: packages/base-ui/src/utils/use-footer-items.ts:5-13
Navigation Context and Layout Configuration
Layout configuration functions assemble complete navigation models by merging global application settings with dynamic source loaders. The layoutConfig module initializes documentation layouts by retrieving generated page trees from source loaders and injecting them alongside custom base navigation branding and logo elements.
async docs(): Promise<DocsLayoutProps> {
const config = await getConfigRuntime();
const source = await getSource(config);
return {
tree: source.getPageTree(),
...(await this.base()),
};
},Sources: packages/preview/src/layouts/config.tsx:31-38
| Layout Property | Return Type | Purpose |
|---|---|---|
base() | Promise<BaseLayoutProps> | Resolves default navigation branding and logo parameters |
docs() | Promise<DocsLayoutProps> | Combines source page trees with base layout configurations |
home() | Promise<HomeLayoutProps> | Returns landing page configuration settings |
Sources: packages/preview/src/layouts/config.tsx:13-41
Warning
When traversing folder nodes during footer synthesis, the recursive walker checks for the existence of node.index before iterating over node.children, ensuring index documents are always positioned ahead of standard sibling entries.
Sources: packages/base-ui/src/utils/use-footer-items.ts:17-19
Related
On This Page
OverviewPackage Architecture and DependenciesManifest ConfigurationDevelopment and Build ScriptsDependencies and Peer RelationshipsTable of Contents TransformationOptions Configuration and InterfaceRecursive Traversal and Node ProcessingText Node Extraction and FilteringRich Text Structure MappingAST Node Parsing and FlatteningHeading Hierarchy and Depth ResolutionSource Loader Integration PatternsContent Source Adapters and Collection StoresDynamic Sanity and Content Collection RoutingPage Tree and Navigation SynthesisHierarchical Tree Traversal and Footer Item ResolutionNavigation Context and Layout ConfigurationRelated