ローカル Markdown
ローカルの Markdown コンテンツを扱うためのコンテンツソースです。
はじめに
@fumadocs/local-md は、ローカルの Markdown/MDX ファイル向けのコンテンツソースです。設計上バンドラーを使用しないため、ランタイムのみで完結して動作します。
MDX Remote と比べると、ローカルファイルに特化している分、パイプラインのより多くの部分(フロントマター、meta ファイル、検索インデックスの作成)を担います。
Fumadocs MDX と比べると、型生成やバンドラーなしで動作しますが、ビルド時の画像最適化は無効になります。
制限事項
- ビルド時の画像最適化には対応していません。
- MDX ファイル内ではインポート/エクスポートを使用できませんが、レンダリング時にコンポーネントを渡すことはできます。
セットアップ
パッケージをインストールします。
npm install @fumadocs/local-md shikishiki をインストールしているのは、バンドラーで外部化する必要があるためです。
localMd インスタンスを作成し、Fumadocs に接続します。
import { dynamicLoader } from 'fumadocs-core/source';
import { localMd } from '@fumadocs/local-md';
const docs = localMd({
dir: 'content/docs',
// オプション
});
const docsLoader = dynamicLoader(docs.dynamicSource(), {
baseUrl: '/docs',
});
export async function getSource() {
return docsLoader.get();
}スキーマ
frontmatterSchema と metaSchema を渡すと、バリデーションに使用するスキーマをカスタマイズできます。
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({
// ...
}),
});使い方
getSource() は通常のコンテンツローダーを返します。各ページには page.data.load() があり、ページをコンパイルしてレンダラーを返します。
ローダーでシリアライズし、クライアントでレンダリングします。
import type { Route } from './+types/docs';
import { useMemo } from 'react';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { DocsBody, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import { useFumadocsLoader } from 'fumadocs-core/source/client';
import { rendererFromSerialized } from '@fumadocs/local-md/client';
import { getSource } from '@/lib/source';
import { useMDXComponents } from '@/components/mdx';
export async function loader({ params }: Route.LoaderArgs) {
const source = await getSource();
const page = source.getPage(params['*'].split('/').filter((v) => v.length > 0));
if (!page) throw new Response('Not found', { status: 404 });
const { serialize } = await page.data.load();
return {
title: page.data.title,
// 構文木(クライアントでレンダリングされます)
render: serialize(),
pageTree: await source.serializePageTree(source.getPageTree()),
};
}
export default function Page({ loaderData }: Route.ComponentProps) {
const { title, render, pageTree } = useFumadocsLoader(loaderData);
const renderer = useMemo(() => rendererFromSerialized(render), [render]);
const { body, toc } = renderer.renderSync(useMDXComponents());
return (
<DocsLayout tree={pageTree}>
<DocsPage toc={toc}>
<DocsTitle>{title}</DocsTitle>
<DocsBody>{body}</DocsBody>
</DocsPage>
</DocsLayout>
);
}インクルード
<include> はデフォルトで有効になっています。
<include>../shared/note.md</include>Markdown ファイルでは、remark-directive を設定したうえで ::include[../shared/note.md] ディレクティブを使用します。
import remarkDirective from 'remark-directive';
const docs = localMd({
dir: 'content/docs',
mdOptions: { remarkPlugins: [remarkDirective] },
});ホットリロード
@fumadocs/local-md はランタイムにファイルを読み込むため、デフォルトではファイルの変更は監視されません。
開発中は、Vite プラグインが Vite のファイルウォッチャーを通じてソースを無効化するため、追加のプロセスやポートは不要です。設定にプラグインを追加し、ソースを接続してください。
import { defineConfig } from 'vite';
import { localContentPlugin } from '@fumadocs/local-md/dev/vite';
export default defineConfig({
plugins: [localContentPlugin()],
});コンテンツファイルはモジュールグラフの外にあるため、プラグインはコンテンツディレクトリを Vite のウォッチャーに追加します。無効化されるモジュールがないので、プラグインは代わりにブラウザをリロードします。リロードを自分で制御したい場合は、localContentPlugin({ reload: false }) を渡してください。
Markdown や MDX ファイル (それらがインクルードするファイルを含む) が変更されると、ソースがリロードされます。独自のウォッチャーを組み込む場合は、docs.dir と docs.onDependency() に渡されたファイルを監視し、ファイルの変更時に docs.invalidateFile(path) を呼び出してください。
JavaScript エンジン
MDX ファイル(*.mdx)は new Function で実行されるため、eval() が禁止されている環境(Cloudflare Workers など)では動作しません。
Markdown ファイル(*.md)は eval() を使わずに構文木からレンダリングされます。プラグインが生成した JSX は、組み込みの仮想 JavaScript エンジンで評価されます。このエンジンは rendererOptions.executor で差し替えられます。
信頼できない MDX ファイルは dir に置かないでください。アプリ内で JavaScript として実行されます。
再検証を無効にする
staticSource() は、再検証を行わない一度きりのスナップショットを返します。dynamicLoader() ではなく loader() と組み合わせて使用します:
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',
});Fumadocs MDX からの移行
移行するには、Fumadocs MDX のセットアップを @fumadocs/local-md のセットアップに置き換えます。
移行前
import { loader } from 'fumadocs-core/source';
import { defineDocs } from 'fumadocs-mdx/macro';
const docs = defineDocs({
dir: 'content/docs',
});
export const source = loader({
baseUrl: '/docs',
source: docs.toFumadocsSource(),
});移行後
import { dynamicLoader } from 'fumadocs-core/source';
import { localMd } from '@fumadocs/local-md';
const docs = localMd({
dir: 'content/docs',
});
const docsLoader = dynamicLoader(docs.dynamicSource(), {
baseUrl: '/docs',
});
export async function getSource() {
return docsLoader.get();
}続いて、次の作業を行います。
- Vite の設定から
fumadocsMdx()を削除します。 - ホットリロードを設定します。
最後に、ローダーなどで source オブジェクトを参照している箇所を getSource() を使うように書き換えます。
import { getSource } from '@/lib/source';
const source = await getSource();
const page = source.getPage(slugs);ページデータの API も変わります。
const page = source.getPage(['...']);
// タイトルと説明
page.data.title;
// カスタムのフロントマタープロパティ
page.data.full;
// getText() API
await page.data.getText('processed');
// コンパイル済みのプロパティ:
page.data.structuredData;
page.data.toc;
return (
<div>
<page.data.body components={{}} />
</div>
);How is this guide?
最終更新日
