Orientation
Content Processing
Search Infrastructure
Rendering and UI
Developer Tooling
How It Works
The following files were used as context for generating this wiki page:
Page Trees serve as the central hierarchical navigation model in Fumadocs, decoupling the physical organization of source files from the logical representation presented to end-users. They transform raw file-system content into a structured graph of folders, items, and separators, enabling features like nested navigation, automatic breadcrumbs, and cross-file relationship management. By formalizing this structure, the system allows for modular content scaling where documentation can be reorganized or linked without modifying the underlying source files themselves.
At its core, the Page Tree subsystem addresses the complexity of mapping filesystem-based content to web-based URLs. It handles the resolution of metadata, sorting of directory contents, and the application of custom transformations. By utilizing a "Page Tree Builder" pattern, the framework ensures that even complex documentation sets—including those spread across multiple storage providers or internationalized locales—are resolved into a unified, traversable navigation structure consistent with the project's configuration.
The subsystem integrates deeply with the UI layer, where "renderers" traverse these tree objects to generate interactive sidebar components. This separation between the construction of the tree (server-side) and its consumption (client-side) allows the platform to support both static generation and dynamic, client-side re-hydration. Through a well-defined set of visitor patterns and serialization utilities, Page Trees remain portable, efficient to navigate, and easy to extend with custom plugins.
The Page Tree Builder is the engine responsible for synthesizing a PageTree.Root object. It operates by consuming raw file storage and applying a sequence of transformations defined by the project configuration. The build process follows a recursive descent model, starting at a defined root and visiting directories to resolve individual file nodes, metadata, and symbolic references.
A key design choice is the use of an Attached data structure internally during construction, which tracks state such as ownership and completion status (SymbolUnfinished) via JavaScript Symbols. This allows the builder to manage complex scenarios, such as when multiple folders might technically reference the same page or resource, by establishing ownership rules that prevent circular dependencies and redundant rendering.
Sources: packages/core/src/source/page-tree/builder.ts:89-105, 140-145, 474-503
The subsystem implements a sophisticated ownership model to resolve how pages are assigned to folders when those pages appear in multiple metadata configurations. The own() function acts as a critical guard during node resolution.
Important
The own() function dictates node membership based on a priority integer. If a node is already owned by a higher priority process, ownership transfer is denied. This prevents race conditions or configuration conflicts where two folders compete to claim the same document index.
The resolution logic follows this sequence:
own(ownerPath, node, priority) is called.SymbolUnfinished, ownership resolution is rejected.Sources: packages/core/src/source/page-tree/builder.ts:152-184
Transformers allow users to intercept and modify the Page Tree structure as it is built. These are defined through an interface that allows hooks into file, folder, root, and separator generation.
Sources: packages/core/src/source/page-tree/builder.ts:18-28
To facilitate the consumption of trees (for breadcrumbs, finding neighbors, or flattening the structure), the framework provides a suite of utility functions centered on the visit() function. This function performs a depth-first search on the tree nodes.
// Example: Using visit to modify icon rendering during tree traversal
import { visit } from 'fumadocs-core/page-tree/utils';
const transformedTree = visit(myTree, (node) => {
if (node.type === 'page' && node.icon) {
// Custom processing logic
}
});Tip
The visit() function accepts return values 'skip' and 'break'. Returning 'skip' prevents recursion into children, while 'break' immediately exits the entire traversal, which is essential for performance when searching large documentation trees.
Sources: packages/core/src/page-tree/utils.ts:176-220
Since Page Trees are generated on the server (often via React Server Components), they must be serialized before reaching the client. The subsystem includes serializePageTree and deserializePageTree functions to bridge this gap.
Sources: packages/core/src/source/client/index.tsx:14-39
Sidebar components in the Radix-UI and Base-UI layers utilize a renderer-context pattern. They define an internalComponents set that maps tree node types to actual React elements. The createPageTreeRenderer function takes these dependencies and returns a function that manages the rendering lifecycle of the tree.
Sources: packages/radix-ui/src/components/sidebar/page-tree.tsx:31-98, packages/base-ui/src/components/sidebar/page-tree.tsx:31-98
Sources: packages/core/src/source/page-tree/builder.ts:74-82, 93-96