Fumadocs

AI & LLMs

Integrate AI functionality to Fumadocs.

Docs for LLM

You can make your docs site more AI-friendly with dedicated docs content for large language models.

To begin, make a getLLMText function that converts pages into static MDX content.

In Fumadocs MDX, you can do:

lib/get-llm-text.ts
import { source } from '@/lib/source';

export async function getLLMText(page: (typeof source)['$inferPage']) {
  const processed = await page.data.getText('processed');

  return `# ${page.data.title} (${page.url})

${processed}`;
}

It requires includeProcessedMarkdown to be enabled:

source.config.ts
import { defineDocs } from 'fumadocs-mdx/config';

export const docs = defineDocs({
  docs: {
    postprocess: {
      includeProcessedMarkdown: true,
    },
  },
});

llms.txt

You can generate it from page tree using Loader API.

app/llms.txt/route.ts
import { source } from '@/lib/source';
import { llms } from 'fumadocs-core/source';

// cached forever
export const revalidate = false;

export function GET() {
  return new Response(llms(source).index());
}

llms-full.txt

A version of docs for AIs to read.

app/llms-full.txt/route.ts
import { source } from '@/lib/source';
import { getLLMText } from '@/lib/get-llm-text';

// cached forever
export const revalidate = false;

export async function GET() {
  const scan = source.getPages().map(getLLMText);
  const scanned = await Promise.all(scan);

  return new Response(scanned.join('\n\n'));
}

*.md

Allow AI agents to get the content of a page as Markdown/MDX, by appending .md to the end of path.

app/llms.mdx/docs/[[...slug]]/route.ts
import { getLLMText } from '@/lib/get-llm-text';
import { source } from '@/lib/source';
import { notFound } from 'next/navigation';

export const revalidate = false;

export async function GET(_req: Request, { params }: RouteContext<'/llms.mdx/docs/[[...slug]]'>) {
  const { slug } = await params;
  const page = source.getPage(slug);
  if (!page) notFound();

  return new Response(await getLLMText(page), {
    headers: {
      'Content-Type': 'text/markdown',
    },
  });
}

export function generateStaticParams() {
  return source.generateParams();
}
next.config.ts
import type { NextConfig } from 'next';

const config: NextConfig = {
  async rewrites() {
    return [
      {
        source: '/docs/:path*.md',
        destination: '/llms.mdx/docs/:path*',
      },
    ];
  },
};

Accept

To serve the Markdown content instead for AI agents, you can leverage the Accept header.

proxy.ts (Next.js)
import { NextRequest, NextResponse } from 'next/server';
import { isMarkdownPreferred, rewritePath } from 'fumadocs-core/negotiation';

const { rewrite: rewriteLLM } = rewritePath('/docs{/*path}', '/llms.mdx/docs{/*path}');

export default function proxy(request: NextRequest) {
  if (isMarkdownPreferred(request)) {
    const result = rewriteLLM(request.nextUrl.pathname);

    if (result) {
      return NextResponse.rewrite(new URL(result, request.nextUrl), {
        headers: { Vary: 'Accept' },
      });
    }
  }

  return NextResponse.next();
}

Because the same URL now has two representations, the response needs Vary: Accept so shared caches key on the header they were selected by, rather than serving Markdown to a browser (or the other way round).

Next.js

Next.js discards Vary on App Router page responses, so the HTML side of the branch above can't carry the header from inside the app — set it at your CDN if you serve documentation through a shared cache.

Page Actions

Common page actions for AI, require *.md to be implemented first.

AI Page Actions

npx @fumadocs/cli add ai/page-actions

Use it in your docs page like:

app/docs/[[...slug]]/page.tsx
// URL to fetch Markdown content, only need to append .mdx to URL if you have `*.mdx` configured.
// Use .md instead for the Tanstack Start example above.
// Use getPageMarkdownUrl(page).url for the Waku example above.
const markdownUrl = `${page.url}.mdx`

<div className="flex flex-row gap-2 items-center border-b pt-2 pb-6">
  <LLMCopyButton markdownUrl={markdownUrl} />
  <ViewOptions
    markdownUrl={markdownUrl}
    githubUrl={`https://github.com/${owner}/${repo}/blob/main/content/docs/${page.path}`}
  />
</div>

Ask AI

AI Search

You can install the AI chat dialog using Fumadocs CLI.

npx @fumadocs/cli add ai/openrouter

It's automatically configured for OpenRouter using Vercel AI SDK, with a /search tool for AI.

You can use other models by updating the /api/chat route.

Fumadocs doesn't provide the AI model, it's up to you.

Your AI model can use the llms-full.txt file generated above, or more diversified sources of information when combined with 3rd party solutions.

Add the component & trigger to docs layout:

import { DocsLayout } from 'fumadocs-ui/layouts/docs';
// import the installed components, e.g.
import { AISearch, AISearchPanel, AISearchTrigger } from '@/components/ai/search';
import { MessageCircleIcon } from 'lucide-react';
// or import your own button styles
import { buttonVariants } from 'fumadocs-ui/components/ui/button';

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <DocsLayout>
      <AISearch>
        <AISearchPanel />
        <AISearchTrigger
          position="float"
          className={cn(
            buttonVariants({
              variant: 'secondary',
              className: 'text-fd-muted-foreground rounded-2xl',
            }),
          )}
        >
          <MessageCircleIcon className="size-4.5" />
          Ask AI
        </AISearchTrigger>
      </AISearch>

      {children}
    </DocsLayout>
  );
}

How is this guide?

Last updated on

On this page