AI と LLM
Fumadocs に AI 機能を組み込みます。
LLM向けドキュメント
LLMやAIエージェント向けに、ドキュメントをMarkdown形式で提供します。
npx @fumadocs/cli feature llmsこれにより、docs コレクションで処理済みの Markdown が有効になり、lib/source.ts に docsLlms が追加されて、以下のファイルが配信されます。手動でセットアップする場合に備えて、生成されるファイルを以下に記載しています。
docsLlms
llms() は、ドキュメントを LLM 向けにレンダリングします。renderPage は、生のファイル内容ではなく処理済みのドキュメントをもとに、1 つのページを Markdown に変換します。
import { llms } from 'fumadocs-core/source';
export const docsLlms = llms(source, {
renderPage: async (page) => `# ${page.data.title} (${page.url})
${await page.data.getText('processed')}`,
});
| メソッド | 出力 |
|---|---|
index(lang?) | ページツリーから構築される llms.txt インデックス |
page(page) | renderPage でレンダリングした 1 つのページ |
full(lang?) | renderPage でレンダリングした全ページを結合したもの |
files() | エージェント向けファイルのパス |
serve(path) | 指定したパスにあるファイルのレスポンス |
renderPage は必須です
コンテンツソースが Markdown をどのように公開しているかを Fumadocs 側では判断できないため、page()、full()、files()、serve() は
renderPage を渡した場合にのみ利用できます。
renderPage はページの処理済み Markdown を読み取るため、includeProcessedMarkdown を有効にしてください。
import { defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
docs: {
postprocess: {
includeProcessedMarkdown: true,
},
},
});
MDX コンポーネントは JSX 構文のまま出力されます。これらを Markdown 形式にレンダリングするには、MDX コンポーネントをページの props として渡してください。
@fumadocs/local-md のようなランタイムのコンテンツソースは、リクエスト時に解決されます。その getSource を渡すと、CLI が同じファイルを生成します。
export const docsLlms = llms(getSource, {
renderPage: (page) => `# ${page.data.title} (${page.url})
${page.data.content}`,
});getSource を使用する場合、index() と indexNode() は文字列ではなく Promise を返します。
ファイル
CLIは、AIエージェント向けに次のファイルを配信します。
/llms.txt: ドキュメントのインデックスです。各ページのMarkdownへのリンクが含まれます。/llms-full.txt: 全ページのコンテンツです。<page>.md: 各ページのMarkdownです (例:/docs/ui.md) 。/のページは/index.mdになります。<section>/llms-full.txt: セクションのコンテンツです (例:/docs/ui/llms-full.txt) 。
下書きなどのページをこれらのファイルから除外するには、filter を指定します。除外したページも llms.txt には引き続き掲載され、HTMLページへのリンクになります。
export const docsLlms = llms(source, {
filter: (page) => page.slugs[0] !== 'drafts',
renderPage: async (page) => `# ${page.data.title}\n\n${await page.data.getText('processed')}`,
});これらのファイルでは、renderUrl はリンク先のファイルのパス (ページの .md ファイルなど) を受け取ります。
ページの HTML を読み取るエージェントは、<head> 内のリンクからそのページの Markdown を見つけます:
<link rel="alternate" type="text/markdown" href="/docs/ui.md" />セクション
セクションとは、独自の llms-full.txt を持つページツリー内のフォルダーです。これにより、エージェントは大規模なドキュメントを部分ごとに読み込めます。デフォルトでは、すべてのルートフォルダーがセクションになります。llms.txt では、各フォルダーの行がそのセクションの llms-full.txt へのリンクになります。
セクションとするフォルダーを指定するには、section を渡します。この関数はフォルダーを受け取り、そのフォルダーをセクションとするかどうかを返します。
export const docsLlms = llms(source, {
// index ページを持つすべてのフォルダー
section: (folder) => folder.index !== undefined,
renderPage: async (page) => `# ${page.data.title}\n\n${await page.data.getText('processed')}`,
});大規模なサイトでは、セクションを折りたたむと llms.txt を短く保てます。折りたたんだセクションは、llms.txt ではそのセクション専用の <section>/llms.txt への1つのリンクとして記載され、セクション内のページはそのファイルに一覧表示されます。
export const docsLlms = llms(source, {
section: (folder) => (folder.root ? { collapse: true } : false),
renderPage: async (page) => `# ${page.data.title}\n\n${await page.data.getText('processed')}`,
});セクションのファイルは、そのフォルダーのインデックスページの URL 配下で配信されます。インデックスページがないフォルダーでは、代わりに配下のページの URL ディレクトリが使われます。フォルダーグループのように 2 つのフォルダーが同じ URL を共有する場合は、asPath を渡すと、一方を別のパスで配信できます (例:{ asPath: '/docs/guides' }) 。
ルート
files() はファイルのパスを列挙し、serve(path) は指定したパスのファイルをレスポンスとして返します。そのため、各ルートはリクエストパスを serve() に渡すだけで済みます。
ドキュメントページ docs/$ は /docs 配下のすべてのパスにマッチするため、ドキュメント配下のファイル用のルートには docs/{$}.md のように、より具体的なパスを使用します:
import { createFileRoute } from '@tanstack/react-router';
import { docsLlms } from '@/lib/source';
export const Route = createFileRoute('/llms.txt')({
server: {
handlers: {
GET: async ({ request }) => (await docsLlms.serve(new URL(request.url).pathname)) ?? new Response(null, { status: 404 }),
},
},
});
serve() はファイルが存在しないパスに対して undefined を返すため、ミドルウェアはリクエストを後続の処理に渡せます。hideLocale: 'always' を指定した場合は URL にロケールが含まれないため、言語を serve() の第2引数、および files() の第1引数として渡してください。
静的出力
SPA モードでは、Nitro でファイルをプリレンダリングします。TanStack Start はプリレンダリング対象のパスに末尾スラッシュを付けてリクエストしますが、ファイル用のルートはこれに一致しません。Vite はプラグインを適用せずに設定を読み込むため、runnerImport を使ってソースを読み込んでください。
import { defineConfig, runnerImport } from 'vite';
import { fumadocsMdx } from 'fumadocs-mdx/vite';
import { nitro } from 'nitro/vite';
const { docsLlms } = (
await runnerImport<typeof import('./src/lib/source')>('./src/lib/source.ts', {
plugins: [fumadocsMdx()],
resolve: { tsconfigPaths: true },
})
).module;
export default defineConfig({
plugins: [
// その他のプラグイン
nitro({
prerender: { routes: await docsLlms.files() },
}),
],
});Accept
AIエージェントは、Accept ヘッダーを使って Markdown を要求できます。
リクエストが Markdown を優先する場合は、ページの Markdown へリダイレクトするようにします。
import { createMiddleware, createStart } from '@tanstack/react-start';
import { redirect } from '@tanstack/react-router';
import { isMarkdownPreferred } from 'fumadocs-core/negotiation';
const llmMiddleware = createMiddleware().server(({ next, request }) => {
const url = new URL(request.url);
// `/docs/ui.md` のようなパスはファイルのルートで配信されます
if (
url.pathname.startsWith('/docs') &&
!/\.(md|txt)$/.test(url.pathname) &&
isMarkdownPreferred(request)
) {
url.pathname += '.md';
throw redirect({ href: url.href, headers: { Vary: 'Accept' } });
}
return next();
});
export const startInstance = createStart(() => ({
requestMiddleware: [llmMiddleware],
}));同じ URL に2つの表現が存在することになるため、キャッシュの衝突を防ぐには、レスポンスに Vary: Accept を含める必要があります。
ページアクション
ページアクションを使うと、読者はページの Markdown をコピーしたり、AI アプリでページを開いたりできます。ページアクションは、ページの .md ファイルを読み込みます。

docs ページのローダーから Markdown の URL を返し、ページ内でレンダリングしてください。
import { MarkdownCopyButton, ViewOptionsPopover } from 'fumadocs-ui/layouts/docs/page';
// ローダー内
return {
path: page.path,
markdownUrl: `${page.url}.md`,
};
// ページ内
<div className="flex flex-row gap-2 items-center border-b pt-2 pb-6">
<MarkdownCopyButton markdownUrl={markdownUrl} />
<ViewOptionsPopover
markdownUrl={markdownUrl}
githubUrl={`https://github.com/${owner}/${repo}/blob/main/content/docs/${encodeURI(path)}`}
/>
</div>;MCP サーバー
MCP サーバーを通じてドキュメントをAIエージェントに公開できます。ページの一覧表示・検索・読み取りを行うツールも提供されます。
npx @fumadocs/cli feature mcp上記の LLM ルート に加えて、Streamable HTTP トランスポートを使用する /api/mcp ルートを作成します。
ツールは fumadocs-core/mcp によって提供されます。
import { createFileRoute } from '@tanstack/react-router';
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import { registerSearchTool, registerSourceTools } from 'fumadocs-core/mcp';
import { createFromSource } from 'fumadocs-core/search/server';
import { docsLlms, source } from '@/lib/source';
const handler = createMcpHandler(() => {
const mcp = new McpServer(
{
name: 'docs',
version: '1.0.0',
},
// the tools never change, a stream to watch them would hold a serverless function open
{ capabilities: { tools: { listChanged: false } } },
);
registerSourceTools(mcp, source, docsLlms);
registerSearchTool(mcp, createFromSource(source));
return mcp;
});
export const Route = createFileRoute('/api/mcp')({
server: {
handlers: {
GET: async ({ request }) => handler.fetch(request),
POST: async ({ request }) => handler.fetch(request),
DELETE: async ({ request }) => handler.fetch(request),
},
},
});
エージェントを接続するには、次のようにします。
{
"mcpServers": {
"docs": { "url": "https://your-site.com/api/mcp" }
}
}WebMCP
実験的機能
WebMCP は策定初期段階の Web 標準で、Chrome 149 以降で
#enable-webmcp-testing フラグを有効にするか、オリジントライアルを通じて利用できます。API は今後
変更される可能性があります。
前述の MCP サーバーがサイトに接続してくるエージェント向けであるのに対し、WebMCP は読者が閲覧しているページ上で、ブラウザに組み込まれた AIエージェント にツールを公開します。
npx @fumadocs/cli feature webmcpLLM ルートをベースにした WebMCP コンポーネントが作成されます。これを <RootProvider /> の内側でレンダリングしてください。
search_docsは検索 API ルートにクエリを送信します。fetchClientは他のクライアントに置き換えることもできます。read_pageは、/docs/page.mdからページの Markdown を取得します。
AIに質問

Fumadocs CLI を使用して、AI チャットダイアログをインストールできます。
npx @fumadocs/cli add ai/openrouterVercel AI SDK を使用して OpenRouter 向けに自動で設定され、AI 用の /search ツールも含まれます。
/api/chat ルートを更新すれば、他のモデルも使用できます。
Fumadocs は AI モデルを提供していません。モデルはご自身で用意してください。
AI モデルには、上記で生成した llms-full.txt ファイルを利用できます。また、サードパーティのソリューションと組み合わせれば、より多様な情報ソースを活用することもできます。
Docs レイアウトは、aiChat オプションをもとにチャットをレンダリングします。このオプションはクライアントの状態を受け取るため、レイアウト用のクライアントコンポーネントを作成し、元のレイアウトの代わりに使用してください。
'use client';
// Notebook、Glass、Spacious レイアウトも使用できます
import { DocsLayout as Layout, type DocsLayoutProps } from 'fumadocs-ui/layouts/docs';
// インストールしたコンポーネント
import { AIChat, AIChatPanel, useAIChat } from './search';
export function DocsLayout(props: DocsLayoutProps) {
return (
<AIChat>
<ChatLayout {...props} />
</AIChat>
);
}
function ChatLayout(props: DocsLayoutProps) {
const { open, setOpen } = useAIChat();
return (
<Layout
{...props}
aiChat={{ open, onOpenChange: setOpen, panel: <AIChatPanel />, trigger: true }}
/>
);
}How is this guide?
最終更新日
