Written by

Fuma Nama

At

Sun Nov 16 2025

Fumadocs MDX 14

Performance Improvements & API redesign.

Back

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

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.

tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "fumadocs-mdx:collections/*": [".source/*"]
    }
  }
}

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

import { docs } from '@/.source';
import { docs } from 'fumadocs-mdx:collections/server';

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

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

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

See Browser Entry for more examples of browserCollections.

Fumadocs Adapter

The adapter for loader() API is also changed.

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

import { docs } from 'fumadocs-mdx:collections/server';
import { loader } from 'fumadocs-core/source';

export const source = loader({
  baseUrl: '/docs',
  source: await create.sourceAsync(docs.doc, docs.meta),
  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().

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

export const blog = loader({
  baseUrl: '/blog',
  // for Next.js
  source: createMDXSource(blogPosts, []),
  // for Vite
  source: await create.sourceAsync(blogPosts, []),
  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.

source.config.ts
import { defineDocs } from 'fumadocs-mdx/config';

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

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

source.config.ts
import { defineDocs } from 'fumadocs-mdx/config';

export const docs = defineDocs({
  dir: 'content/docs',
  docs: {
    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 customise more properties:

import { postInstall } from 'fumadocs-mdx/next';
// or
import { postInstall } from 'fumadocs-mdx/vite';

postInstall('source.config.ts');

getDefaultMDXOptions() -> applyMdxPreset()

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

source.config.ts
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],
  }),
});

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.

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

export default defineConfig({
  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.

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.

source.config.ts
import { defineDocs } from 'fumadocs-mdx/config';

export const docs = defineDocs({
  dir: 'content/docs',
  docs: {
    postprocess: {
      extractLinkReferences: true,
    },
  },
});

Vite: generateIndexFile -> index

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

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.