Fix: Fumadocs Not Working — Pages Not Found, Search Not Indexing, or MDX Components Missing
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 FoundOr the sidebar is empty:
Navigation panel shows no entries despite MDX files existingOr 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-uiandfumadocs-coreare separate — both must be installed, andfumadocs-ui/style.cssmust be imported.- MDX processing needs the Fumadocs plugins — code highlighting, callouts, and structured data depend on them.
- The catch-all route is required —
app/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 empty — meta.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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Nextra Not Working — Pages Not Rendering, Sidebar Missing, or MDX Components Broken
How to fix Nextra documentation site issues — Next.js integration, _meta.json sidebar configuration, custom MDX components, search setup, theme customization, and static export.
Fix: Chakra UI Not Working — Provider Missing, Styles Not Applied, or Dark Mode Broken
How to fix Chakra UI v3 issues — ChakraProvider setup, color mode, theming, server-side rendering in Next.js, component styling, and common runtime errors.
Fix: Clerk Not Working — Auth Not Loading, Middleware Blocking, or User Data Missing
How to fix Clerk authentication issues — ClerkProvider setup, middleware configuration, useUser and useAuth hooks, server-side auth, webhook handling, and organization features.
Fix: Contentlayer Not Working — Content Not Generated, Types Missing, or Build Errors
How to fix Contentlayer and Contentlayer2 issues — content source configuration, document type definitions, MDX processing, computed fields, Next.js integration, and migration to alternatives.