---
title: Fumadocs MDX 14
description: Performance Improvements & API redesign.
author: Fuma Nama
date: 2025-11-16
---

We are pleased to announce Fumadocs MDX 14. This update introduces several major enhancements designed to:

- Improve flexibility, taking the plugin approach further.
- Improve performance & memory consumption.
- Improve browser/dynamic compilation support.

## New Model

The output is now separated into:

- **`.source/server`:** the content data available for server (e.g. RSC, SSR), should not be referenced in client code.
- **`.source/browser`:** the data available for browser, with built-in serialization, chunking and loader layer.
- **`.source/dynamic`:** interface for dynamic content compilation, only generated for collections with `dynamic: true`.

## Breaking Changes

### Entry Files

Fumadocs MDX now generates multiple entry files in `.source` folder.

To migrate, add/update the path aliases in `tsconfig.json` for new entry files.

```json title="tsconfig.json"
{
  "compilerOptions": {
    "paths": {
      // [!code ++]
      "collections/*": ["./.source/*"]
    }
  }
}
```

Import your collections from the entry files instead of `@/.source`:

```ts tab="Server/RSC"
// [!code --]
import { docs } from '@/.source';
// [!code ++]
import { docs } from 'collections/server';

// access collections on server-side, e.g.
import { loader } from 'fumadocs-core/source';

export const source = loader({
  source: docs.toFumadocsSource(),
});
```

```tsx tab="Browser/non-RSC"
// non-RSC environment (e.g. browser)
// [!code --:20]
import { docs } from '@/.source';

const clientLoader = createClientLoader(docs.doc, {
  id: 'docs',
  component({ toc, frontmatter, default: MDX }) {
    return (
      <DocsPage toc={toc}>
        <DocsTitle>{frontmatter.title}</DocsTitle>
        <DocsDescription>{frontmatter.description}</DocsDescription>
        <DocsBody>
          <MDX
            components={{
              ...defaultMdxComponents,
            }}
          />
        </DocsBody>
      </DocsPage>
    );
  },
});

// [!code ++:20]
import browserCollections from 'collections/browser';

const clientLoader = browserCollections.docs.createClientLoader({
  component({ frontmatter, default: MDX }) {
    return (
      <DocsPage toc={toc}>
        <DocsTitle>{frontmatter.title}</DocsTitle>
        <DocsDescription>{frontmatter.description}</DocsDescription>
        <DocsBody>
          <MDX
            components={{
              ...defaultMdxComponents,
            }}
          />
        </DocsBody>
      </DocsPage>
    );
  },
});
```

<Callout>

The new browser `createClientLoader()` should work seamlessly with old usage.

See [Browser Entry](/docs/mdx/entry/browser#examples) for more examples of `browserCollections`.

</Callout>

### Fumadocs Adapter

The adapter for `loader()` API is also changed.

For `docs` collection, `create.sourceAsync()` (Vite) is now consistent with Next.js:

```ts
import { docs } from 'collections/server';
import { loader } from 'fumadocs-core/source';

export const source = loader({
  baseUrl: '/docs',
  // [!code --]
  source: await create.sourceAsync(docs.doc, docs.meta),
  // [!code ++]
  source: docs.toFumadocsSource(),
});
```

For `doc` collections, `createMDXSource()` (Next.js) is renamed to `toFumadocsSource()`.

If you use `create.sourceAsync()` (Vite) for `doc` collections, you should also switch to `toFumadocsSource()`.

```ts
import { blogPosts } from 'collections/server';
import { toFumadocsSource } from 'fumadocs-mdx/runtime/server';
import { loader } from 'fumadocs-core/source';

export const blog = loader({
  baseUrl: '/blog',
  // [!code --] for Next.js
  source: createMDXSource(blogPosts, []),
  // [!code --] for Vite
  source: await create.sourceAsync(blogPosts, []),
  // [!code ++]
  source: toFumadocsSource(blogPosts, []),
});
```

### Async & Dynamic Mode

On Next.js, **Async Mode** now aligns with the behaviour on Vite to generate async imports. This enables server-side chunking to improve memory usage.

```ts title="source.config.ts"
import { defineDocs } from 'fumadocs-mdx/config';

export const docs = defineDocs({
  dir: 'content/docs',
  docs: {
    // This no longer forces dynamic compilation! [!code ++]
    async: true,
  },
});
```

Instead, we introduced a `dynamic` option to enable dynamic compilation. This is available for both Vite and Next.js.

```ts title="source.config.ts"
import { defineDocs } from 'fumadocs-mdx/config';

export const docs = defineDocs({
  dir: 'content/docs',
  docs: {
    // [!code ++]
    dynamic: true,
  },
});
```

By enabling Async or Dynamic modes, the relevant collections are considered lazy-loaded, offering a `load()` function to load compiled properties.

### Updated Signature for `postInstall()`

It now accepts the framework plugin options, you can customize more properties:

```ts tab="Before"
import { postInstall } from 'fumadocs-mdx/next';
// or
import { postInstall } from 'fumadocs-mdx/vite';

postInstall('source.config.ts');
```

```ts tab="After"
import { postInstall } from 'fumadocs-mdx/next';
// or
import { postInstall } from 'fumadocs-mdx/vite';

postInstall({ configPath: 'source.config.ts' });
```

### `getDefaultMDXOptions()` -> `applyMdxPreset()`

To support a broader range of presets and accommodate dynamic modes, `getDefaultMDXOptions()` has been renamed to `applyMdxPreset()`.

```ts title="source.config.ts" tab="Before"
import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config';
import { myPlugin } from './remark-plugin';

export const blog = defineCollections({
  type: 'doc',
  mdxOptions: getDefaultMDXOptions({
    remarkPlugins: [myPlugin],
    // Alternatively, use a function to customize plugin order:
    remarkPlugins: (v) => [myPlugin, ...v],
  }),
});
```

```ts title="source.config.ts" tab="After"
import { defineCollections, applyMdxPreset } from 'fumadocs-mdx/config';
import { myPlugin } from './remark-plugin';

export const blog = defineCollections({
  type: 'doc',
  mdxOptions: applyMdxPreset({
    remarkPlugins: [myPlugin],
    // Alternatively, use a function to customize plugin order:
    remarkPlugins: (v) => [myPlugin, ...v],
  }),
});
```

### Introduction of `lastModified` Plugin

**The `lastModifiedTime` option has been replaced by a dedicated `lastModified` plugin.**

This shift promotes a plugin-based architecture for better extensibility.

```ts title="source.config.ts"
import { defineConfig } from 'fumadocs-mdx/config';
import lastModified from 'fumadocs-mdx/plugins/last-modified';

export default defineConfig({
  // [!code ++]
  plugins: [lastModified()],
});
```

### Removal of Multiple `dir` Support

Support for specifying multiple directories in a single collection has been discontinued. Instead, utilize the `files` property to filter and include specific files.

```ts title="source.config.ts"
import { defineDocs } from 'fumadocs-mdx/config';

export const docs = defineDocs({
  dir: 'content/guides',
  docs: {
    files: ['./i-love-fumadocs/**/*.{md,mdx}'],
  },
});
```

### Disabling Default Generation of `extractedReferences`

The automatic generation of `extractedReferences` is no longer enabled by default. To retain this functionality, explicitly activate it via the `postprocess` option.

```ts title="source.config.ts"
import { defineDocs } from 'fumadocs-mdx/config';

export const docs = defineDocs({
  dir: 'content/docs',
  docs: {
    postprocess: {
      // [!code ++]
      extractLinkReferences: true,
    },
  },
});
```

### Vite: `generateIndexFile` -> `index`

The `generateIndexFile` option of our Vite plugin has been renamed to `index`.

```ts title="vite.config.ts"
import { defineConfig } from 'vite';
import mdx from 'fumadocs-mdx/vite';
import * as MdxConfig from './source.config';

export default defineConfig({
  plugins: [
    mdx(MdxConfig, {
      index: {
        // options
      },
    }),
  ],
});
```

## Patch Changes

- Resolved an issue with meta file validation when using Bun.
- Introduced optimizations to reduce unnecessary entry files re-generations.

Thank you for your continued support of Fumadocs. If you encounter any issues during the update, welcome to reach out through our GitHub Discussions.
