Skip to main content
Technology & EngineeringSeo Content409 lines

Fumadocs

"fumadocs documentation framework: Next.js App Router native, MDX content collections, full-text search, OpenAPI integration, TypeScript-first, customizable UI components, and content source adapters."

Quick Summary30 lines
fumadocs is a modern documentation framework built natively on Next.js App Router. Unlike Nextra (which originated on Pages Router), fumadocs embraces React Server Components, the `app/` directory structure, and Next.js conventions from the ground up. It separates concerns into three packages: `fumadocs-core` (headless utilities), `fumadocs-ui` (styled components), and `fumadocs-mdx` (MDX content source). This modular architecture lets you use the full UI kit for rapid setup or just the core utilities when you need complete control. Content is defined through type-safe collections with Zod validation, and the framework provides built-in search (Orama or Algolia), OpenAPI page generation, and i18n support.

## Key Points

- Use `fumadocs-mdx` as the content source for most projects. It handles MDX compilation, frontmatter validation, and hot reload out of the box.
- Leverage `source.pageTree` for sidebar generation — it respects `meta.json` ordering and nesting, eliminating manual navigation configuration.
- Use the built-in Orama search for static sites. Switch to Algolia only when you need server-side search across thousands of pages.
- Define `generateStaticParams` and `generateMetadata` in your docs page component — fumadocs provides helper methods (`source.generateParams()`) that handle this cleanly.
- Use `meta.json` in each content directory to control page ordering and section titles. The `---` separator creates visual groupings in the sidebar.
- Import `defaultMdxComponents` from `fumadocs-ui/mdx` and spread them into your MDX component map to get styled headings, code blocks, tables, and links without extra configuration.
- Use the OpenAPI integration to auto-generate API reference pages from your spec, keeping docs in sync with your API.
- Run `fumadocs-openapi` generation as a build script rather than at runtime to keep build times predictable.
- **Using fumadocs with Pages Router**: fumadocs is built for App Router. Attempting to use it with Pages Router leads to broken layouts and missing features. Use Nextra for Pages Router projects.
- **Ignoring `source.config.ts`**: This file defines your content collections. Skipping schema validation means frontmatter errors slip through to production.
- **Overriding fumadocs-ui styles with global CSS**: Use the theme configuration and CSS variables (`--fd-*`) provided by fumadocs-ui. Global overrides break when the library updates.
- **Creating custom search from scratch**: fumadocs provides `createSearchAPI` for the server and a pre-built search dialog for the client. Building your own duplicates tested functionality.

## Quick Example

```bash
npx create-fumadocs-app my-docs
# or manual installation:
npm install fumadocs-ui fumadocs-core fumadocs-mdx
```

```bash
npm install fumadocs-openapi
```
skilldb get seo-content-skills/FumadocsFull skill: 409 lines
Paste into your CLAUDE.md or agent config

fumadocs — Next.js Documentation Framework

Core Philosophy

fumadocs is a modern documentation framework built natively on Next.js App Router. Unlike Nextra (which originated on Pages Router), fumadocs embraces React Server Components, the app/ directory structure, and Next.js conventions from the ground up. It separates concerns into three packages: fumadocs-core (headless utilities), fumadocs-ui (styled components), and fumadocs-mdx (MDX content source). This modular architecture lets you use the full UI kit for rapid setup or just the core utilities when you need complete control. Content is defined through type-safe collections with Zod validation, and the framework provides built-in search (Orama or Algolia), OpenAPI page generation, and i18n support.

Setup

Quick Start

npx create-fumadocs-app my-docs
# or manual installation:
npm install fumadocs-ui fumadocs-core fumadocs-mdx

Project Structure

my-docs/
├── app/
│   ├── layout.tsx
│   ├── page.tsx
│   ├── docs/
│   │   ├── layout.tsx
│   │   └── [[...slug]]/
│   │       └── page.tsx
│   └── api/
│       └── search/
│           └── route.ts
├── content/
│   └── docs/
│       ├── index.mdx
│       ├── getting-started.mdx
│       └── guides/
│           ├── meta.json
│           └── installation.mdx
├── source.config.ts
├── lib/
│   └── source.ts
└── next.config.mjs

Content Source Configuration

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

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

export default defineConfig();
// lib/source.ts
import { loader } from "fumadocs-core/source";
import { createMDXSource } from "fumadocs-mdx";
import { docs, meta } from "@/.source";

export const source = loader({
  baseUrl: "/docs",
  source: createMDXSource(docs, meta),
});
// next.config.mjs
import { createMDX } from "fumadocs-mdx/next";

const withMDX = createMDX();

/** @type {import('next').NextConfig} */
const config = {
  reactStrictMode: true,
};

export default withMDX(config);

Root Layout

// app/layout.tsx
import { RootProvider } from "fumadocs-ui/provider";
import type { ReactNode } from "react";
import "fumadocs-ui/style.css";

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <RootProvider>{children}</RootProvider>
      </body>
    </html>
  );
}

Docs Layout and Page

// app/docs/layout.tsx
import { DocsLayout } from "fumadocs-ui/layouts/docs";
import type { ReactNode } from "react";
import { source } from "@/lib/source";

export default function Layout({ children }: { children: ReactNode }) {
  return (
    <DocsLayout
      tree={source.pageTree}
      nav={{
        title: "My Docs",
        url: "/docs",
      }}
      sidebar={{
        defaultOpenLevel: 1,
      }}
    >
      {children}
    </DocsLayout>
  );
}
// app/docs/[[...slug]]/page.tsx
import { source } from "@/lib/source";
import {
  DocsPage,
  DocsBody,
  DocsTitle,
  DocsDescription,
} from "fumadocs-ui/page";
import { notFound } from "next/navigation";
import defaultMdxComponents from "fumadocs-ui/mdx";

interface Props {
  params: Promise<{ slug?: string[] }>;
}

export default async function Page({ params }: Props) {
  const { slug } = await params;
  const page = source.getPage(slug);
  if (!page) notFound();

  const MDX = page.data.body;

  return (
    <DocsPage
      toc={page.data.toc}
      lastUpdate={page.data.lastModified}
      full={page.data.full}
    >
      <DocsTitle>{page.data.title}</DocsTitle>
      <DocsDescription>{page.data.description}</DocsDescription>
      <DocsBody>
        <MDX components={{ ...defaultMdxComponents }} />
      </DocsBody>
    </DocsPage>
  );
}

export function generateStaticParams() {
  return source.generateParams();
}

export async function generateMetadata({ params }: Props) {
  const { slug } = await params;
  const page = source.getPage(slug);
  if (!page) return {};
  return {
    title: page.data.title,
    description: page.data.description,
  };
}

Key Techniques

Search with Orama

// app/api/search/route.ts
import { source } from "@/lib/source";
import { createSearchAPI } from "fumadocs-core/search/server";

export const { GET } = createSearchAPI("advanced", {
  indexes: source.getPages().map((page) => ({
    title: page.data.title,
    description: page.data.description,
    url: page.url,
    id: page.url,
    structuredData: page.data.structuredData,
  })),
});
// app/layout.tsx — enable search dialog
import { RootProvider } from "fumadocs-ui/provider";
import { ReactNode } from "react";
import "fumadocs-ui/style.css";

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <RootProvider
          search={{
            options: {
              type: "fetch",
            },
          }}
        >
          {children}
        </RootProvider>
      </body>
    </html>
  );
}

Navigation with meta.json

// content/docs/guides/meta.json
{
  "title": "Guides",
  "pages": [
    "installation",
    "configuration",
    "deployment",
    "---",
    "troubleshooting"
  ]
}

OpenAPI Integration

npm install fumadocs-openapi
// lib/source.ts — with OpenAPI
import { loader } from "fumadocs-core/source";
import { createMDXSource } from "fumadocs-mdx";
import { createOpenAPI } from "fumadocs-openapi/server";
import { docs, meta } from "@/.source";

export const source = loader({
  baseUrl: "/docs",
  source: createMDXSource(docs, meta),
});

export const openapi = createOpenAPI({
  source: "./openapi.yaml",
});
// scripts/generate-openapi.mts
import { generateFiles } from "fumadocs-openapi";

void generateFiles({
  input: ["./openapi.yaml"],
  output: "./content/docs/api",
  per: "operation",
  render: (title, description) => {
    return {
      frontmatter: {
        title,
        description: description ?? "",
        full: true,
      },
    };
  },
});

Custom MDX Components

// components/install-tabs.tsx
import { Tab, Tabs } from "fumadocs-ui/components/tabs";

interface InstallTabsProps {
  package: string;
}

export function InstallTabs({ package: pkg }: InstallTabsProps) {
  return (
    <Tabs items={["npm", "pnpm", "yarn", "bun"]}>
      <Tab value="npm">
        ```bash
        npm install {pkg}
        ```
      </Tab>
      <Tab value="pnpm">
        ```bash
        pnpm add {pkg}
        ```
      </Tab>
      <Tab value="yarn">
        ```bash
        yarn add {pkg}
        ```
      </Tab>
      <Tab value="bun">
        ```bash
        bun add {pkg}
        ```
      </Tab>
    </Tabs>
  );
}

i18n Configuration

// lib/source.ts — internationalized
import { loader } from "fumadocs-core/source";
import { createMDXSource } from "fumadocs-mdx";
import { i18n } from "@/lib/i18n";
import { docs, meta } from "@/.source";

export const source = loader({
  baseUrl: "/docs",
  source: createMDXSource(docs, meta),
  i18n,
});
// lib/i18n.ts
import type { I18nConfig } from "fumadocs-core/i18n";

export const i18n: I18nConfig = {
  defaultLanguage: "en",
  languages: ["en", "es", "ja"],
};

Content MDX with Frontmatter

---
title: Getting Started
description: Learn how to set up fumadocs in your Next.js project
icon: Rocket
---

import { Callout } from 'fumadocs-ui/components/callout'
import { Card, Cards } from 'fumadocs-ui/components/card'

## Prerequisites

<Callout title="Requirement" type="warn">
  fumadocs requires Next.js 14+ with App Router enabled.
</Callout>

## Next Steps

<Cards>
  <Card
    title="Configuration"
    description="Customize your documentation site"
    href="/docs/guides/configuration"
  />
  <Card
    title="Deployment"
    description="Deploy to production"
    href="/docs/guides/deployment"
  />
</Cards>

Best Practices

  • Use fumadocs-mdx as the content source for most projects. It handles MDX compilation, frontmatter validation, and hot reload out of the box.
  • Leverage source.pageTree for sidebar generation — it respects meta.json ordering and nesting, eliminating manual navigation configuration.
  • Use the built-in Orama search for static sites. Switch to Algolia only when you need server-side search across thousands of pages.
  • Define generateStaticParams and generateMetadata in your docs page component — fumadocs provides helper methods (source.generateParams()) that handle this cleanly.
  • Use meta.json in each content directory to control page ordering and section titles. The --- separator creates visual groupings in the sidebar.
  • Import defaultMdxComponents from fumadocs-ui/mdx and spread them into your MDX component map to get styled headings, code blocks, tables, and links without extra configuration.
  • Use the OpenAPI integration to auto-generate API reference pages from your spec, keeping docs in sync with your API.
  • Run fumadocs-openapi generation as a build script rather than at runtime to keep build times predictable.

Anti-Patterns

  • Using fumadocs with Pages Router: fumadocs is built for App Router. Attempting to use it with Pages Router leads to broken layouts and missing features. Use Nextra for Pages Router projects.
  • Ignoring source.config.ts: This file defines your content collections. Skipping schema validation means frontmatter errors slip through to production.
  • Overriding fumadocs-ui styles with global CSS: Use the theme configuration and CSS variables (--fd-*) provided by fumadocs-ui. Global overrides break when the library updates.
  • Creating custom search from scratch: fumadocs provides createSearchAPI for the server and a pre-built search dialog for the client. Building your own duplicates tested functionality.
  • Deeply nesting content directories: Like Nextra, more than 3 levels of nesting creates navigation friction. Flatten the structure and use meta.json titles to provide context.
  • Skipping generateStaticParams: Without it, docs pages are dynamically rendered on every request. Always pre-render documentation at build time for performance.
  • Mixing fumadocs-ui with another component library: fumadocs-ui components are designed to work together with consistent spacing, colors, and dark mode. Mixing in Chakra, MUI, or similar libraries creates visual inconsistency.
  • Not using full: true for API reference pages: Wide content like API tables and request/response examples need full-width layout. Set full: true in frontmatter for these pages.

Install this skill directly: skilldb add seo-content-skills

Get CLI access →