Local Markdown
Content source for local Markdown content.
Introduction
@fumadocs/local-md is a content source for local Markdown/MDX files, it is bundleless (works fully at runtime) by design.
As compared to MDX Remote, it is more comprehensive & robust while focused solely on local files.
As compared to Fumadocs MDX, it doesn't need a type-gen or bundler to work, but build-time image optimization will be disabled.
Limitations
- No build-time image optimization.
- No imports/exports in MDX files, but you can pass variables & components at render phase.
Setup
Install the package:
npm install @fumadocs/local-md shikishiki is installed because it has to be externalized by the bundler.
Create a localMd instance and connect it to Fumadocs:
import { dynamicLoader } from 'fumadocs-core/source/dynamic';
import { localMd } from '@fumadocs/local-md';
const docs = localMd({
dir: 'content/docs',
// options
});
const docsLoader = dynamicLoader(docs.dynamicSource(), {
baseUrl: '/docs',
});
export async function getSource() {
return docsLoader.get();
}Schema
You may pass frontmatterSchema and metaSchema to customize the validation schemas:
import { localMd } from '@fumadocs/local-md';
import { pageSchema, metaSchema } from 'fumadocs-core/source/schema';
const docs = localMd({
dir: 'content/docs',
frontmatterSchema: pageSchema.extend({
// ...
}),
metaSchema: metaSchema.extend({
// ...
}),
});Usage
The recommended integration is:
- Create
localMd({ dir })for your content directory. - Pass
docs.dynamicSource()todynamicLoader(). - Read the source in your route/layout and render it with Fumadocs UI.
For example, in a docs layout:
import { getSource } from '@/lib/source';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
export default async function Layout({ children }: LayoutProps<'/docs'>) {
const docs = await getSource();
return <DocsLayout tree={docs.getPageTree()}>{children}</DocsLayout>;
}The returned source from docsLoader.get() is a normal content loader instance.
Hot Reload
local-md works at runtime, but during development you can connect to its dev server for file watching.
Start the dev server with local-md dev -- xxx like:
{
"scripts": {
"dev": "local-md dev -- npm next dev"
}
}And connect to it:
import { localMd } from '@fumadocs/local-md';
const docs = localMd({
dir: 'content/docs',
});
// change it if you use a different framework (e.g. import.meta.env.DEV)
if (process.env.NODE_ENV === 'development') {
void docs.devServer();
}This keeps the loader in sync when local Markdown or MDX files change.
JavaScript Engine
When compiling Markdown files (*.md), @fumadocs/local-md uses a virtual JavaScript engine to avoid eval() at runtime.
This allows your app to work on environments like Cloudflare Worker, while the performance will be slower than the native JavaScript JIT compiler.
Disable Revalidation
You can use staticSource() when you only need a one-time snapshot without revalidation.
It works with a normal loader() instead of only dynamicLoader():
import { loader } from 'fumadocs-core/source';
import { localMd } from '@fumadocs/local-md';
const docs = localMd({
dir: 'content/docs',
});
export const source = loader(await docs.staticSource(), {
baseUrl: '/docs',
});Migration from Fumadocs MDX
If you're already using Fumadocs MDX for local docs content, migrating is usually straightforward.
Before
With Fumadocs MDX, a common setup looks like:
import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
});
export default defineConfig();After
With @fumadocs/local-md, you can replace that setup with:
import { dynamicLoader } from 'fumadocs-core/source/dynamic';
import { localMd } from '@fumadocs/local-md';
const docs = localMd({
dir: 'content/docs',
});
if (process.env.NODE_ENV === 'development') {
void docs.devServer();
}
const docsLoader = dynamicLoader(docs.dynamicSource(), {
baseUrl: '/docs',
});
export async function getSource() {
return docsLoader.get();
}Then:
- remove
source.config.ts. - remove framework-specific MDX integration like
createMDX()innext.config.mjs. - replace
collections/serverimports with a loader created fromlocalMd().
Finally, update references to your source object with getSource():
import { getSource } from '@/lib/source';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
export default async function Layout({ children }: LayoutProps<'/docs'>) {
return <DocsLayout tree={source.getPageTree()}>{children}</DocsLayout>;
const docs = await getSource();
return <DocsLayout tree={docs.getPageTree()}>{children}</DocsLayout>;
}And the type of pages is also changed:
const page = source.getPage(['...']);
// title & description are unchanged
page.data.title;
// custom frontmatter properties
page.data.full;
// getText() API
await page.data.getText('processed');
// compiled properties:
page.data.structuredData;
page.data.toc;
return (
<div>
<page.data.body components={{}} />
</div>
);How is this guide?
Last updated on
