# Fumadocs (Framework Mode, Astro): ローカル Markdown
URL: /ja/docs/astro/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/astro/integrations/content/mdx-remote) と比べると、ローカルファイルに特化している分、パイプラインのより多くの部分(フロントマター、meta ファイル、検索インデックスの作成)を担います。

Astro のコンテンツコレクションと比べると、バンドラーを介さずランタイムでコンテンツをコンパイルするため、ビルド時の画像最適化は無効になります。

### 制限事項 [#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()` があり、ページをコンパイルしてレンダラーを返します。

ページでシリアライズし、React アイランドでレンダリングします。

```astro tab="src/pages/docs/[...slug].astro"
---
import type { GetStaticPaths, InferGetStaticPropsType } from 'astro';
import Layout from '@/layouts/Layout.astro';
import { Docs } from '@/components/docs';
import { getSource } from '@/lib/source';

export const prerender = true;

export const getStaticPaths = (async () => {
  const source = await getSource();
  return source.getPages().map((page) => ({
    params: { slug: decodeURI(page.slugs.join('/')) || undefined },
    props: { page },
  }));
}) satisfies GetStaticPaths;

type Props = InferGetStaticPropsType<typeof getStaticPaths>;

const { page } = Astro.props;
const source = await getSource();
const pageTree = await source.serializePageTree(source.getPageTree());
const { serialize } = await page.data.load();
---

<Layout title={page.data.title}>
  <Docs
    client:load
    pathname={Astro.url.pathname}
    params={Astro.params}
    tree={pageTree}
    title={page.data.title}
    render={serialize()}
  />
</Layout>
```

```tsx tab="src/components/docs.tsx"
import { useMemo } from 'react';
import { type SerializedPageTree, useFumadocsLoader } from 'fumadocs-core/source/client';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { DocsBody, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import { rendererFromSerialized } from '@fumadocs/local-md/client';
import type { MarkdownRendererSerializedOptions } from '@fumadocs/local-md';
import { Provider, type ProviderProps } from '@/components/provider';
import { getMDXComponents } from '@/components/mdx';

export interface DocsProps extends ProviderProps {
  tree: SerializedPageTree;
  title: string;
  // アイランド内でレンダリングされる構文木
  render: MarkdownRendererSerializedOptions;
}

export function Docs({ tree, title, render, ...props }: DocsProps) {
  const data = useFumadocsLoader({ tree });
  const renderer = useMemo(() => rendererFromSerialized(render), [render]);
  const { body, toc } = renderer.renderSync(getMDXComponents());

  return (
    <Provider {...props}>
      <DocsLayout tree={data.tree}>
        <DocsPage toc={toc}>
          <DocsTitle>{title}</DocsTitle>
          <DocsBody>{body}</DocsBody>
        </DocsPage>
      </DocsLayout>
    </Provider>
  );
}
```

### インクルード [#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` はランタイムにファイルを読み込むため、デフォルトではファイルの変更は監視されません。

開発中は、Vite プラグインが Vite のファイルウォッチャーを通じてソースを無効化するため、追加のプロセスやポートは不要です。設定にプラグインを追加し、ソースを接続してください。

```js tab="astro.config.mjs"
import { defineConfig } from 'astro/config';
import { localContentPlugin } from '@fumadocs/local-md/dev/vite';

export default defineConfig({
  vite: {
    plugins: [localContentPlugin()],
  },
});
```

```ts tab="lib/source.ts"
import { watchWithVite } from '@fumadocs/local-md/dev/vite';

if (import.meta.env.DEV) {
  watchWithVite(docs);
}
```

コンテンツファイルはモジュールグラフの外にあるため、プラグインはコンテンツディレクトリを Vite のウォッチャーに追加します。無効化されるモジュールがないので、プラグインは代わりにブラウザをリロードします。リロードを自分で制御したい場合は、`localContentPlugin({ reload: false })` を渡してください。

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',
});
```