# Fumadocs (Framework Mode, Next.js): ローカル Markdown
URL: /ja/docs/next/integrations/content/local-md
Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/(framework)/[framework]/integrations/content/local-md.ja.mdx

ローカルの Markdown コンテンツを扱うためのコンテンツソースです。

## はじめに [#introduction]

`@fumadocs/local-md` は、ローカルの Markdown/MDX ファイル向けのコンテンツソースです。設計上バンドラーを使用しないため、ランタイムのみで完結して動作します。

[MDX Remote](/docs/next/integrations/content/mdx-remote) と比べると、ローカルファイルに特化している分、パイプラインのより多くの部分(フロントマター、meta ファイル、検索インデックスの作成)を担います。

[Fumadocs MDX](/docs/mdx) と比べると、型生成やバンドラーなしで動作しますが、ビルド時の画像最適化は無効になります。

### 制限事項 [#limitations]

* ビルド時の画像最適化には対応していません。
* MDX ファイル内ではインポート/エクスポートを使用できませんが、レンダリング時にコンポーネントを渡すことはできます。

## セットアップ [#setup]

パッケージをインストールします。

```package-install
@fumadocs/local-md shiki
```

> `shiki` をインストールしているのは、バンドラーで外部化する必要があるためです。

`localMd` インスタンスを作成し、Fumadocs に接続します。

```ts title="lib/source.ts"
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();
}
```

### スキーマ [#schema]

`frontmatterSchema` と `metaSchema` を渡すと、バリデーションに使用するスキーマをカスタマイズできます。

```ts title="lib/source.ts"
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]

`getSource()` は通常の[コンテンツローダー](/docs/headless/source-api)を返します。各ページには `page.data.load()` があり、ページをコンパイルしてレンダラーを返します。

ページ内でレンダリングします。

```tsx tab="app/docs/layout.tsx"
import { getSource } from '@/lib/source';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';

export default async function Layout({ children }: LayoutProps<'/docs'>) {
  const source = await getSource();

  return <DocsLayout tree={source.getPageTree()}>{children}</DocsLayout>;
}
```

```tsx tab="app/docs/[[...slug]]/page.tsx"
import { notFound } from 'next/navigation';
import { DocsBody, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import { getSource } from '@/lib/source';
import { getMDXComponents } from '@/components/mdx';

export default async function Page(props: PageProps<'/docs/[[...slug]]'>) {
  const params = await props.params;
  const source = await getSource();
  const page = source.getPage(params.slug);
  if (!page) notFound();

  const { render } = await page.data.load();
  const { body, toc } = await render(getMDXComponents());

  return (
    <DocsPage toc={toc}>
      <DocsTitle>{page.data.title}</DocsTitle>
      <DocsBody>{body}</DocsBody>
    </DocsPage>
  );
}
```

### インクルード [#include]

[`<include>`](/docs/mdx/include) はデフォルトで有効になっています。

```mdx title="content/docs/page.mdx"
<include>../shared/note.md</include>
```

Markdown ファイルでは、`remark-directive` を設定したうえで `::include[../shared/note.md]` ディレクティブを使用します。

```ts title="lib/source.ts"
import remarkDirective from 'remark-directive';

const docs = localMd({
  dir: 'content/docs',
  mdOptions: { remarkPlugins: [remarkDirective] },
});
```

### ホットリロード [#hot-reload]

`@fumadocs/local-md` はランタイムにファイルを読み込むため、デフォルトではファイルの変更は監視されません。

開発中は、同梱の開発サーバーをアプリと並行して実行してください。開発サーバーは単一のプロセスでファイルを監視し、変更を WebSocket 経由でブロードキャストするため、Next.js が複数のワーカーを実行する場合でも動作します。

```json title="package.json"
{
  "scripts": {
    "dev": "local-md dev -- next dev"
  }
}
```

ソースを開発サーバーに接続します。

```ts title="lib/source.ts"
import { watchWithDevServer } from '@fumadocs/local-md/dev/ws';

if (process.env.NODE_ENV === 'development') {
  void watchWithDevServer(docs);
}
```

コマンドが環境変数経由で URL を公開するため、設定は不要です。サーバーを自分で起動する場合は URL を明示的に渡してください。ポートを変更するには `-p` を使用します。

```ts
void watchWithDevServer(docs, { url: 'ws://127.0.0.1:8000/_fumadocs_local_md' });
```

これによりサーバー上のソースは無効化されますが、ブラウザ側では Next.js がレンダリング済みのページを保持したままです。ページも更新されるように、ルートレイアウトで `<DevClient />` をレンダリングしてください。

```tsx title="app/layout.tsx"
import { DevClient } from '@fumadocs/local-md/dev/react-client';

export function RootLayout({ children }) {
  return (
    <RootProvider>
      {/* [!code ++] */}
      <DevClient />
      {children}
    </RootProvider>
  );
}
```

Markdown や MDX ファイル (それらがインクルードするファイルを含む) が変更されると、ソースがリロードされます。独自のウォッチャーを組み込む場合は、`docs.dir` と `docs.onDependency()` に渡されたファイルを監視し、ファイルの変更時に `docs.invalidateFile(path)` を呼び出してください。

### JavaScript エンジン [#javascript-engine]

MDX ファイル(`*.mdx`)は `new Function` で実行されるため、`eval()` が禁止されている環境(Cloudflare Workers など)では動作しません。

Markdown ファイル(`*.md`)は `eval()` を使わずに構文木からレンダリングされます。プラグインが生成した JSX は、組み込みの仮想 JavaScript エンジンで評価されます。このエンジンは `rendererOptions.executor` で差し替えられます。

<Callout type="warn">
信頼できない MDX ファイルは `dir` に置かないでください。アプリ内で JavaScript として実行されます。
</Callout>

### 再検証を無効にする [#disable-revalidation]

`staticSource()` は、再検証を行わない一度きりのスナップショットを返します。`dynamicLoader()` ではなく `loader()` と組み合わせて使用します：

```ts title="lib/source.ts"
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 からの移行 [#migration-from-fumadocs-mdx]

移行するには、Fumadocs MDX のセットアップを `@fumadocs/local-md` のセットアップに置き換えます。

### 移行前 [#before]

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

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

export default defineConfig();
```

```ts tab="lib/source.ts"
import { docs } from 'collections/server';
import { loader } from 'fumadocs-core/source';

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

### 移行後 [#after]

```ts title="lib/source.ts"
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();
}
```

続いて、次の作業を行います。

* `source.config.ts` を削除します。
* `next.config.mjs` から `createMDX()` を削除します。
* `collections/server` のインポートを、`localMd()` から作成したローダーに置き換えます。
* [ホットリロード](#hot-reload)を設定します。

最後に、`source` オブジェクトを参照している箇所を `getSource()` を使うように書き換えます。

```tsx title="app/docs/layout.tsx"
import { getSource } from '@/lib/source';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';

export default async function Layout({ children }: LayoutProps<'/docs'>) {
  // [!code --]
  return <DocsLayout tree={source.getPageTree()}>{children}</DocsLayout>;

  // [!code ++:2]
  const docs = await getSource();
  return <DocsLayout tree={docs.getPageTree()}>{children}</DocsLayout>;
}
```

ページデータの API も変わります。

```tsx tab="移行前"
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>
);
```

```tsx tab="移行後"
const page = docs.getPage(['...']);

// タイトルと説明（変更なし）
page.data.title;

// カスタムのフロントマタープロパティ
page.data.frontmatter.full;

// getText() API はないため、代わりに data.content を使用します
console.log(page.data.content);

// コンパイル済みのプロパティ:
const structuredData = await page.data.structuredData();
const { toc, body } = await (await page.data.load()).render(mdxComponents);

return <div>{body}</div>;
```