---
title: "BaseHub Integration"
description: "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 b..."
last_updated: "2026-10-12T07:53:59.831064+00:00"
canonical_url: "https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/content-sources/basehub-integration"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/runtime/server.ts)
- [packages/content/src/runtime.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/runtime.ts)
- [packages/preview/src/pages/...slugs.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/openapi/src/ui/base.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/openapi/src/ui/base.tsx)
- [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/runtime/dynamic.ts)
- [packages/mdx/src/plugins/index-file.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/plugins/index-file.ts)
- [packages/content-collections/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content-collections/src/index.ts)
- [packages/basehub/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/package.json)
- [packages/local-md/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/index.ts)
- [packages/sanity/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/sanity/src/index.ts)
- [packages/asyncapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/server/index.tsx)
- [packages/base-ui/registry/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/base-ui/registry/index.ts)
- [packages/preview/src/lib/source/storage.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/lib/source/storage.ts)
- [packages/content-collections/src/types.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content-collections/src/types.ts)
- [packages/create-app/template/+orama-cloud/@app/lib/export-static-indexes.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/template/%2Borama-cloud/%40app/lib/export-static-indexes.ts)
- [packages/base-ui/src/provider/waku.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/base-ui/src/provider/waku.tsx)
- [packages/openapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/openapi/src/utils/pages/builder.ts)
- [packages/preview/src/lib/source/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/lib/source/index.ts)
- [packages/base-ui/src/layouts/flux/page/index.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/base-ui/src/layouts/flux/page/index.tsx)
- [packages/base-ui/src/utils/use-footer-items.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/base-ui/src/utils/use-footer-items.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content-collections/src/configuration.ts)
- [packages/preview/src/components/provider.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/components/provider.tsx)
- [packages/base-ui/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/base-ui/package.json)
- [packages/basehub/src/toc.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx)
- [packages/base-ui/src/layouts/notebook/page/index.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/base-ui/src/layouts/notebook/page/index.tsx)
- [packages/preview/src/layouts/config.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/layouts/config.tsx)
- [packages/asyncapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/utils/pages/builder.ts)
- [packages/preview/src/pages/_api/api/search.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/_api/api/search.ts)
- [packages/radix-ui/src/layouts/flux/page/index.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/radix-ui/src/layouts/flux/page/index.tsx)
</details>

## Overview

The `@fumadocs/basehub` package provides official integration tools to connect BaseHub content services with Fumadocs documentation projects. It exports [table of contents](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/ui-layouts/table-of-contents) parsers and utilities that bridge BaseHub data structures with core Fumadocs [source loader](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/core-architecture/source-loader) conventions.
Sources: [packages/basehub/package.json:1-48](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/package.json#L1-L48), [packages/basehub/src/toc.tsx:1-49](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L1-L49)

## 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/package.json#L1-L23)

### 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/package.json#L24-L30)

### 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/package.json#L31-L48)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/package.json#L43-L53)

## 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L5-L12)

```tsx
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L5-L12)

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

```tsx
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L14-L49)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L27-L35)

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

```tsx
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L51-L57)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L36-L40)

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

```tsx
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L51-L57)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L56-L56)

### 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L18-L28)

```tsx
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L18-L28)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/basehub/src/toc.tsx#L27-L27)

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

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/runtime.ts#L12-L42)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/runtime.ts#L26-L31)

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

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/sanity/src/index.ts#L63-L161)

| 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/sanity/src/index.ts#L16-L36)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/sanity/src/index.ts#L93-L99)

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

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/base-ui/src/utils/use-footer-items.ts#L10-L27)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/base-ui/src/utils/use-footer-items.ts#L5-L13)

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

```tsx
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/layouts/config.tsx#L31-L38)

| 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/layouts/config.tsx#L13-L41)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/base-ui/src/utils/use-footer-items.ts#L17-L19)

## Related

- [Source Loader](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/core-architecture/source-loader)
- [Sanity CMS Integration](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/content-sources/sanity-cms-integration)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/llms.txt) for all pages in this wiki.
