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:
MDX Bundling in Fumadocs is the bridge between static Markdown/MDX files and the framework-specific build pipelines (Vite, Next.js, Bun, Rolldown). It provides a unified system for transforming content, ensuring consistent parsing, plugin execution, and asset handling across different environments. By decoupling the core MDX logic from the build tool integration, it enables specialized loaders and plugins to share the same processing pipeline while respecting environment-specific requirements.
The system is architected around a central Core engine that manages configurations and plugins. Build-tool-specific entry points (like createMdxPlugin for Bun or createMDX for Next.js) initialize this core and register loaders for MDX and metadata files. This design ensures that regardless of the underlying bundler, the content is consistently compiled, frontmatter is transformed, and output files are emitted according to the project’s configuration.
Key responsibilities of the bundling system include resolving content files via globbing, managing the MDX compilation lifecycle (including cache hash generation and post-processing), and generating virtual entry files that provide dynamic, type-safe access to content collections. This approach simplifies development, enabling features like hot reloading for configuration files and efficient, lazy-loaded content distribution.
The bundling system relies on createCore as the central initialization primitive. This core maintains the global state for MDX processing, including build configurations and collection metadata. Build-tool-specific adapters bridge this core into the target environment (e.g., Vite plugins, Webpack loaders).
Sources: packages/mdx/src/core.ts, packages/mdx/src/vite/index.ts
The MDX loader is the primary mechanism for transforming .mdx files. It manages cache generation, frontmatter parsing, and the invocation of the build processor.
only=frontmatter).buildMDX, which uses an MDX processor configured with the environment-specific plugins.Sources: packages/mdx/src/loaders/mdx/index.ts
The buildMDX function is the gatekeeper for processor instantiation. It utilizes a cache to store processor instances by collection and format (md vs mdx) to optimize repeated builds.
function getProcessor(format: 'md' | 'mdx') {
const cache = core.cache as Map<string, Processor>;
const key = `build-mdx:${collection?.name ?? 'global'}:${format}`;
let processor = cache.get(key);
// ...
processor = createProcessor({
outputFormat: 'program',
development: isDevelopment,
...mdxOptions,
remarkPlugins: [
remarkInclude,
...(mdxOptions.remarkPlugins ?? []),
[remarkPostprocess, postprocessOptions],
],
format,
});
cache.set(key, processor);
return processor;
}Sources: packages/mdx/src/loaders/mdx/build-mdx.ts
The index-file plugin generates virtual entry points (e.g., server.ts, dynamic.ts) to provide runtime access to collections. These files contain code that invokes the server or dynamic compilation runtime to resolve and serve content efficiently.
dynamic, the generator uses glob patterns to identify files, extracts frontmatter, and creates lazy entry objects for runtime consumption.createFSCache) to handle file updates efficiently, ensuring that index generation only triggers when necessary.Sources: packages/mdx/src/plugins/index-file.ts
The system uses a preset-based approach to resolve plugins for both Remark and Rehype, allowing users to extend the pipeline without replacing the core logic. resolvePlugins and pluginOption are the primary mechanisms for merging default presets with user-defined overrides.
Sources: packages/core/src/content/mdx/preset-bundler.ts, packages/mdx/src/config/preset.ts
For dynamic content, the runtime (dynamic.ts) provides a mechanism to execute compiled MDX code directly. This is crucial for environments where build-time pre-compilation is insufficient.
Important
The runtime assumes the input code is safe and executes it directly via an AsyncFunction constructor. This is designed for content managed within the repository, not for arbitrary user input.
async function executeMdx(compiled: string, options: ExecuteOptions = {}) {
// ...
const hydrateFn = new AsyncFunction(...Object.keys(fullScope), compiled);
return await hydrateFn.apply(hydrateFn, Object.values(fullScope));
}Sources: packages/mdx/src/runtime/dynamic.ts
Sources: packages/mdx/src/plugins/index-file.ts, packages/mdx/src/loaders/mdx/build-mdx.ts, packages/mdx/src/runtime/dynamic.ts