Skip to content

Fix: Fumadocs Not Working — Pages Not Found, Search Not Indexing, or MDX Components Missing

FixDevs · (Updated: )

Part of:  React & Frontend Errors

Quick Answer

How to fix Fumadocs documentation framework issues — Next.js App Router setup, content source configuration, sidebar generation, MDX components, search, OpenAPI integration, and custom themes.

The Problem

Fumadocs pages render as 404:

GET /docs/getting-started → 404 Not Found

Or the sidebar is empty:

Navigation panel shows no entries despite MDX files existing

Or built-in components don’t render:

import { Callout } from 'fumadocs-ui/components/callout';
// Module not found: Can't resolve 'fumadocs-ui/components/callout'

Why This Happens

Fumadocs is a Next.js documentation framework built on App Router. It uses content collections with a source adapter that pulls MDX files through a remark/rehype pipeline and exposes them as a page tree to your route handlers. The framework intentionally splits responsibilities across three packages — fumadocs-core (routing and content), fumadocs-ui (theme and components), and fumadocs-mdx (the file-based source adapter). When something breaks, the failure point is almost always at the boundary between one of those packages and your Next.js configuration.

The most frequent root cause is a missing or misconfigured content source. Fumadocs reads MDX from a source — typically the file-system adapter shipped in fumadocs-mdx. Without a properly configured source, no pages are generated, source.pageTree is empty, and source.getPage() always returns undefined. The file-based routing also requires a source.ts (or lib/source.ts) module that converts the source’s output into a Fumadocs-compatible page tree. The page tree itself is built from the file structure plus the meta.json files in each directory, so a missing meta.json will hide a whole section from the sidebar even if its MDX files build successfully.

The second class of failures is the split between fumadocs-core and fumadocs-ui. fumadocs-core handles content processing and routing; fumadocs-ui provides the theme, layout, and components like Callout, Tabs, and Steps. Both must be installed and both must be imported correctly. MDX processing also needs the Fumadocs remark/rehype plugins — code highlighting, callouts, and frontmatter parsing all require specific plugins in the content-source config. If you skip fumadocs-ui/style.css in your root layout, the components render but look unstyled, which is easy to mistake for “components missing.”

  • Content source must be configured — without source.toFumadocsSource() wired up, no pages exist.
  • fumadocs-ui and fumadocs-core are separate — both must be installed, and fumadocs-ui/style.css must be imported.
  • MDX processing needs the Fumadocs plugins — code highlighting, callouts, and structured data depend on them.
  • The catch-all route is requiredapp/docs/[[...slug]]/page.tsx (double brackets) is non-negotiable.

In Production: Incident Lens

When Fumadocs breaks in production, the blast radius is the public documentation site. Users land on /docs/getting-started and hit a 404, which both kills the onboarding funnel and looks like an outage to anyone evaluating your library. The failure mode is almost always at deploy time, not runtime: a successful local next build masks issues that surface only when the build runs in CI against a clean checkout where .source/ has not been generated.

How it surfaces: the docs Vercel/Cloudflare deploy goes green, but every nested doc URL returns 404. The root /docs page may still work because it falls back to a static page, which makes the regression easy to miss in smoke tests. Marketing or DevRel notices first when a launch tweet links to a freshly broken page.

Monitoring signal: hook a synthetic check into the doc home plus three deep URLs (/docs/getting-started/installation, an API reference page, and a guide). Watch the 404 rate on /docs/* in your CDN analytics — a spike to 100% on non-index docs URLs is the canonical Fumadocs failure pattern. Also alert on the doc build duration: if fumadocs-mdx fails to generate .source/, the build step finishes suspiciously fast.

Recovery sequence: revert the failing commit first to get docs back, then reproduce locally with rm -rf .source .next && npm run build to mirror the CI environment. Inspect .source/ for the expected index.ts exports; if it’s empty, the source config or MDX frontmatter is the culprit. The postmortem preventive is a CI gate that runs next build and then asserts that at least N pages were generated — a simple find .next/server/app/docs -name "*.html" | wc -l check catches the silent regression before deploy.

Fix 1: Setup with Next.js

npm install fumadocs-core fumadocs-ui fumadocs-mdx
// source.ts — content source configuration
import { docs, meta } from '@/.source';

export const source = docs.toFumadocsSource();
// app/layout.tsx
import { RootProvider } from 'fumadocs-ui/provider';
import 'fumadocs-ui/style.css';  // Required CSS

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <RootProvider>{children}</RootProvider>
      </body>
    </html>
  );
}
// app/docs/layout.tsx — docs layout with sidebar
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { source } from '@/lib/source';

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <DocsLayout
      tree={source.pageTree}
      nav={{ title: 'My Docs' }}
    >
      {children}
    </DocsLayout>
  );
}
// app/docs/[[...slug]]/page.tsx — docs page
import { source } from '@/lib/source';
import { notFound } from 'next/navigation';
import defaultMdxComponents from 'fumadocs-ui/mdx';

export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
  const params = await props.params;
  const page = source.getPage(params.slug);
  if (!page) notFound();

  const MDX = page.data.body;

  return (
    <article>
      <h1>{page.data.title}</h1>
      <MDX components={{ ...defaultMdxComponents }} />
    </article>
  );
}

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

export async function generateMetadata(props: { params: Promise<{ slug?: string[] }> }) {
  const params = await props.params;
  const page = source.getPage(params.slug);
  if (!page) return {};
  return { title: page.data.title, description: page.data.description };
}

Fix 2: Content Structure

content/docs/
├── index.mdx                 # /docs
├── meta.json                 # Root sidebar config
├── getting-started/
│   ├── meta.json             # Group sidebar config
│   ├── index.mdx             # /docs/getting-started
│   ├── installation.mdx      # /docs/getting-started/installation
│   └── quick-start.mdx
├── guides/
│   ├── meta.json
│   ├── basic-usage.mdx
│   └── advanced.mdx
└── api/
    ├── meta.json
    ├── client.mdx
    └── server.mdx
// content/docs/meta.json — root sidebar order
{
  "title": "Documentation",
  "pages": [
    "---Getting Started---",
    "getting-started",
    "---Guides---",
    "guides",
    "---API---",
    "api"
  ]
}

// content/docs/getting-started/meta.json
{
  "title": "Getting Started",
  "pages": [
    "index",
    "installation",
    "quick-start"
  ]
}
---
title: Installation
description: How to install and configure the project
---

# Installation

## Prerequisites

You need Node.js 18+.

## Install

```bash
npm install my-package

## Fix 3: Built-In MDX Components

```mdx
---
title: Component Examples
---

import { Callout } from 'fumadocs-ui/components/callout';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Card, Cards } from 'fumadocs-ui/components/card';
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { File, Folder, Files } from 'fumadocs-ui/components/files';
import { TypeTable } from 'fumadocs-ui/components/type-table';

## Callouts

<Callout>This is an informational note.</Callout>

<Callout type="warn">Be careful with this approach.</Callout>

<Callout type="error">This will cause data loss!</Callout>

## Tabs

<Tabs items={['npm', 'pnpm', 'yarn']}>
  <Tab value="npm">
    ```bash
    npm install my-package
    ```
  </Tab>
  <Tab value="pnpm">
    ```bash
    pnpm add my-package
    ```
  </Tab>
  <Tab value="yarn">
    ```bash
    yarn add my-package
    ```
  </Tab>
</Tabs>

## Steps

<Steps>
  <Step>
    ### Install dependencies

    Run the install command.
  </Step>
  <Step>
    ### Configure the project

    Create a configuration file.
  </Step>
  <Step>
    ### Start developing

    Run the dev server.
  </Step>
</Steps>

## Cards

<Cards>
  <Card title="Quick Start" href="/docs/getting-started/quick-start">
    Get up and running in 5 minutes.
  </Card>
  <Card title="API Reference" href="/docs/api">
    Complete API documentation.
  </Card>
</Cards>

## File Tree

<Files>
  <Folder name="src" defaultOpen>
    <Folder name="components">
      <File name="Header.tsx" />
      <File name="Footer.tsx" />
    </Folder>
    <File name="index.ts" />
  </Folder>
  <File name="package.json" />
</Files>

## Type Table

<TypeTable
  type={{
    name: { type: 'string', description: 'The user name', required: true },
    email: { type: 'string', description: 'Email address' },
    role: { type: "'admin' | 'user'", description: 'User role', default: "'user'" },
  }}
/>

Fix 4: Search Configuration

// Fumadocs includes built-in search powered by Flexsearch

// 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 in the layout
import { RootProvider } from 'fumadocs-ui/provider';

<RootProvider
  search={{
    links: [
      ['Home', '/'],
      ['Docs', '/docs'],
    ],
  }}
>
  {children}
</RootProvider>

Fix 5: Theme Customization

/* app/global.css — override Fumadocs theme */
:root {
  --fd-primary: 234 89% 56%;      /* HSL values for primary color */
  --fd-background: 0 0% 100%;
  --fd-foreground: 222 84% 5%;
  --fd-muted: 210 40% 96%;
  --fd-accent: 210 40% 96%;
  --fd-border: 214 32% 91%;
}

.dark {
  --fd-background: 222 84% 5%;
  --fd-foreground: 210 40% 98%;
  --fd-muted: 217 33% 17%;
  --fd-border: 217 33% 17%;
}
// app/docs/layout.tsx — customize nav and sidebar
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { source } from '@/lib/source';

export default function Layout({ children }) {
  return (
    <DocsLayout
      tree={source.pageTree}
      nav={{
        title: (
          <div className="flex items-center gap-2">
            <img src="/logo.svg" width={24} height={24} alt="Logo" />
            <span className="font-bold">My Docs</span>
          </div>
        ),
        links: [
          { text: 'Blog', url: '/blog' },
          { text: 'GitHub', url: 'https://github.com/user/repo', external: true },
        ],
      }}
      sidebar={{
        defaultOpenLevel: 1,
      }}
    >
      {children}
    </DocsLayout>
  );
}

Fix 6: OpenAPI Integration

npm install fumadocs-openapi
// Generate API reference docs from OpenAPI spec
// scripts/generate-docs.mts
import { generateFiles } from 'fumadocs-openapi';

void generateFiles({
  input: ['./openapi.json'],
  output: './content/docs/api',
  per: 'operation',  // One page per operation
});
// package.json
{
  "scripts": {
    "generate:api": "tsx scripts/generate-docs.mts",
    "prebuild": "npm run generate:api"
  }
}

Still Not Working?

404 on all docs pages — check the catch-all route file exists at app/docs/[[...slug]]/page.tsx. The double brackets [[...slug]] are required for optional catch-all routing. Also verify source.getPage(params.slug) finds the page — log the slug to debug.

Sidebar is emptymeta.json files control sidebar ordering. Without them, pages appear in the sidebar but may be unsorted. Check that source.pageTree returns a non-empty tree. The content source must be properly configured to scan the correct directory.

MDX components not found — install fumadocs-ui and import from fumadocs-ui/components/*. Pass defaultMdxComponents from fumadocs-ui/mdx to the MDX component: <MDX components={{ ...defaultMdxComponents }} />.

Search returns no results — the search API route must exist at /api/search. Check that createSearchAPI receives indexed pages. Search indexes are built at request time from source.getPages(). Verify pages have structuredData in their data.

.source/ directory missing in CI — Fumadocs MDX generates a .source/ directory at build time. If your CI cache restores node_modules but not .source/, the build runs against a stale or empty source. Run next build from a clean checkout to reproduce. Add .source/ to .gitignore and let every build regenerate it — or commit a tiny smoke test that imports from @/.source and exits non-zero if the export is empty.

generateStaticParams returns empty array on Vercel — when the catch-all route uses generateStaticParams, Fumadocs needs source.generateParams() to return every slug. If your source path is wrong (for example, content instead of content/docs), the function returns an empty array and Vercel deploys a build with zero static doc pages. Log source.generateParams().length in the build to catch this before deploy.

MDX frontmatter typing errors break the build — Fumadocs’s content schema is strict. Adding an unknown frontmatter field (or omitting a required one like title) fails the type check at build time with a confusing error pointing at .source/index.ts. Open your source.config.ts and align the zod schema with the frontmatter you actually use.

Sidebar shows pages but they all 404 — the sidebar reads from source.pageTree, but the catch-all route resolves URLs via source.getPage(params.slug). If your meta.json files reference filenames that don’t match what’s on disk (case sensitivity is a common offender on Linux CI versus macOS dev), the tree shows ghost entries that 404 on click. Run the build on Linux locally with docker run --rm -v $PWD:/app -w /app node:20 npm run build to surface case mismatches before they hit production.

Code blocks render unhighlighted in production — Fumadocs uses Shiki for syntax highlighting at build time. If your source.config.ts does not include the Shiki rehype plugin, code blocks render as plain <pre> with no token spans. The fix is to add rehypeCodeDefaultOptions from fumadocs-core/mdx-plugins in the source config, or to upgrade to the latest fumadocs-mdx which wires it by default. Verify by inspecting the rendered HTML for <span class="shiki-token-keyword"> markers.

For related documentation issues, see Fix: Starlight Not Working, Fix: Nextra Not Working, Fix: MDX Not Working, and Fix: Next.js Build Failed.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles