Build a Modern Documentation Site with Fumadocs and Next.js 15 in 2026

Documentation is the public face of every serious developer product. A great docs site converts curious visitors into paying customers, while a slow or confusing one quietly drives them away. In 2026, a new generation of docs frameworks has emerged, and Fumadocs has rapidly become the favorite choice for teams using Next.js. It delivers Mintlify-grade aesthetics, deep MDX support, full-text search, and zero vendor lock-in — all running on top of the App Router you already know.
In this tutorial, we will build a complete documentation site from scratch using Fumadocs and Next.js 15. By the end, you will have a production-ready docs platform with versioning, dark mode, instant search, code group tabs, callouts, custom MDX components, and an OpenAPI explorer.
Prerequisites
Before starting, ensure you have:
- Node.js 20 or newer installed
- A working knowledge of React and Next.js App Router
- Familiarity with TypeScript and MDX
- A code editor (VS Code with the MDX extension is recommended)
- A terminal and Git
This tutorial assumes intermediate knowledge of Next.js 15. If you are new to the App Router, review the official Next.js docs first.
What You Will Build
We will build NoqtaDocs, a fictional documentation site for an internal API platform. By the end, the site will have:
- A polished landing page with a hero and feature cards
- A sidebar navigation that mirrors the folder structure
- MDX pages with custom components (callouts, tabs, file trees)
- Full-text client-side search powered by Orama
- Dark mode and accessible theming
- An OpenAPI explorer page
- Version switching for v1 and v2 of the API
- Static export ready for deployment to Vercel or Cloudflare
Why Fumadocs in 2026
Three years ago, most teams reached for Mintlify, GitBook, or a hand-rolled Next.js site. Each option came with trade-offs. Mintlify locked you into a SaaS pricing model, GitBook felt sluggish for code-heavy docs, and DIY sites required reinventing search, theming, and navigation every time.
Fumadocs solves all three problems at once. It is a library, not a platform, so you own your code. It ships with first-class App Router support, server components, MDX 3, and full Tailwind CSS v4 compatibility. The default theme is gorgeous out of the box, but every component is customizable.
Compared to Nextra 4, Fumadocs gives you finer control over routing and layouts, and its tree-walking algorithm produces faster builds on large doc sets. Compared to Starlight, it stays in the Next.js ecosystem, which means you can colocate marketing pages, dashboards, and docs in the same monorepo.
Step 1: Project Setup
Let's start by creating a fresh Next.js 15 project. Open your terminal and run the Fumadocs scaffolding command, which sets up everything for you.
npx create-fumadocs-app@latest noqtadocsThe CLI will ask several questions. For this tutorial, choose:
- Framework: Next.js
- Content source: Fumadocs MDX
- Tailwind CSS: Yes
- Install dependencies: Yes
Once installation finishes, navigate into the project and start the dev server.
cd noqtadocs
npm run devOpen http://localhost:3000 in your browser. You should see the default Fumadocs landing page with a sidebar already populated by example MDX files.
Folder structure overview
The scaffold produces a clean, predictable structure.
noqtadocs/
├── app/
│ ├── (home)/
│ │ └── page.tsx
│ ├── docs/
│ │ ├── [[...slug]]/
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── api/
│ │ └── search/
│ │ └── route.ts
│ ├── layout.config.tsx
│ └── layout.tsx
├── content/
│ └── docs/
│ ├── index.mdx
│ └── test.mdx
├── lib/
│ └── source.ts
├── source.config.ts
└── package.jsonKey files to remember:
source.config.ts— declares your content collections and frontmatter schemalib/source.ts— exports thesourceobject used to read your docsapp/docs/[[...slug]]/page.tsx— the dynamic route rendering each doc pageapp/api/search/route.ts— the built-in Orama search endpointcontent/docs/— where your MDX files live
Step 2: Configure Your Content Source
Open source.config.ts. This file tells Fumadocs how to parse your MDX, what frontmatter to expect, and how to generate the searchable index.
import { defineDocs, defineConfig } from "fumadocs-mdx/config";
import { z } from "zod";
export const docs = defineDocs({
dir: "content/docs",
docs: {
schema: z.object({
title: z.string(),
description: z.string().optional(),
icon: z.string().optional(),
version: z.enum(["v1", "v2"]).default("v2"),
}),
},
});
export default defineConfig({
mdxOptions: {
rehypeCodeOptions: {
themes: {
light: "github-light",
dark: "github-dark",
},
},
},
});We added a custom version field to the frontmatter schema. We will use it later to power version switching. The rehypeCodeOptions block enables Shiki syntax highlighting with both light and dark themes, which automatically adapts to the user's color scheme.
Restart the dev server so the schema change takes effect.
npm run devStep 3: Build the Sidebar Navigation
Fumadocs derives navigation directly from your folder structure. To group docs into sections, create a meta.json file in each folder. Replace the contents of content/docs/ with the following structure.
content/docs/
├── index.mdx
├── meta.json
├── getting-started/
│ ├── meta.json
│ ├── installation.mdx
│ └── quickstart.mdx
├── guides/
│ ├── meta.json
│ ├── authentication.mdx
│ └── webhooks.mdx
└── reference/
├── meta.json
└── openapi.mdxHere is the root meta.json:
{
"title": "NoqtaDocs",
"pages": [
"index",
"---Getting Started---",
"getting-started",
"---Guides---",
"guides",
"---Reference---",
"reference"
]
}The ---Title--- syntax creates section dividers in the sidebar. The order of the array controls the visual order. This explicit control is what makes Fumadocs feel curated rather than alphabetically chaotic.
Inside each subfolder, add a meta.json that lists the pages.
{
"title": "Getting Started",
"pages": ["installation", "quickstart"]
}Step 4: Write Your First Doc Pages
Let's add real content. Open content/docs/getting-started/installation.mdx and paste the following.
---
title: Installation
description: Install the Noqta SDK in your project
icon: Download
---
The Noqta SDK is published on npm and supports Node.js 20 and newer.
## Install with your package manager
import { Tabs, Tab } from "fumadocs-ui/components/tabs";
<Tabs items={["npm", "pnpm", "bun"]}>
<Tab value="npm">```bash npm install @noqta/sdk ```</Tab>
<Tab value="pnpm">```bash pnpm add @noqta/sdk ```</Tab>
<Tab value="bun">```bash bun add @noqta/sdk ```</Tab>
</Tabs>
## Verify the installation
Create a small test script and run it.
```typescript
import { NoqtaClient } from "@noqta/sdk";
const client = new NoqtaClient({ apiKey: process.env.NOQTA_API_KEY });
const status = await client.health.check();
console.log(status);If everything is set up correctly, you will see a JSON response confirming the connection.
The `Tabs` component is one of dozens shipped with `fumadocs-ui`. Each tab can host any MDX content, including more code blocks, tables, or images.
## Step 5: Add Custom Callouts and File Trees
Fumadocs ships with a rich set of MDX components. The two most useful are `Callout` and `Files`. Add a new section to your installation page.
```mdx
import { Callout } from "fumadocs-ui/components/callout";
import { Files, Folder, File } from "fumadocs-ui/components/files";
<Callout type="info">
The SDK ships with TypeScript types out of the box. No extra installs needed.
</Callout>
<Callout type="warn">
Never commit your API key to git. Use environment variables instead.
</Callout>
## Project structure
After installation, your project should look like this.
<Files>
<Folder name="src" defaultOpen>
<File name="index.ts" />
<Folder name="lib">
<File name="noqta.ts" />
</Folder>
</Folder>
<File name=".env.local" />
<File name="package.json" />
</Files>
Save the file and refresh your browser. The callouts render with colored borders and the file tree is interactive — folders can be expanded and collapsed.
Step 6: Customize the Layout
Open app/layout.config.tsx. This file controls the global layout: the top nav, the sidebar header, and the GitHub link.
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
export const baseOptions: BaseLayoutProps = {
nav: {
title: (
<span className="font-semibold">
NoqtaDocs
</span>
),
},
links: [
{ text: "Documentation", url: "/docs" },
{ text: "Blog", url: "/blog" },
{ text: "Changelog", url: "/changelog" },
],
githubUrl: "https://github.com/your-org/noqtadocs",
};The top nav now displays your branding and external links. Because everything is server-rendered, these change instantly with no client-side flash.
Step 7: Implement Full-Text Search
The scaffold already includes Orama-powered search at app/api/search/route.ts. Open it and confirm it looks like this.
import { source } from "@/lib/source";
import { createFromSource } from "fumadocs-core/search/server";
export const { GET } = createFromSource(source);That is all the code needed. Fumadocs walks your content tree, extracts headings, and builds an Orama index in memory. The search results include rich highlighting of matched terms.
To open the search dialog, press Cmd+K or Ctrl+K. Search results are grouped by page and show the matching heading, which makes deep linking to specific sections trivial.
For larger doc sets with more than a thousand pages, switch to the Algolia or Trieve adapters. The same createFromSource API is used, with a different backend.
Step 8: Add a Beautiful Landing Page
The default landing page is a placeholder. Replace app/(home)/page.tsx with a hero, feature grid, and call to action.
import Link from "next/link";
export default function HomePage() {
return (
<main className="flex flex-1 flex-col">
<section className="container flex flex-col items-center gap-6 py-24 text-center">
<h1 className="text-5xl font-bold tracking-tight">
The fastest way to ship docs.
</h1>
<p className="max-w-2xl text-lg text-fd-muted-foreground">
NoqtaDocs is a documentation platform built on Fumadocs and Next.js 15.
Zero vendor lock-in, gorgeous defaults, instant search.
</p>
<div className="flex gap-4">
<Link
href="/docs"
className="rounded-md bg-fd-primary px-6 py-3 font-medium text-fd-primary-foreground"
>
Read the docs
</Link>
<Link
href="https://github.com/your-org/noqtadocs"
className="rounded-md border px-6 py-3 font-medium"
>
View on GitHub
</Link>
</div>
</section>
</main>
);
}Notice the design tokens prefixed with fd-. Fumadocs ships with a complete CSS variable system that adapts to light and dark mode automatically. Use these tokens instead of hardcoding colors.
Step 9: Implement Version Switching
API documentation often needs to support multiple versions. We added a version field to the frontmatter earlier. Now we will use it to filter pages.
Create a new file lib/source.ts if it does not already exist.
import { docs } from "@/.source";
import { loader } from "fumadocs-core/source";
export const source = loader({
baseUrl: "/docs",
source: docs.toFumadocsSource(),
});
export function getVersionPages(version: "v1" | "v2") {
return source.getPages().filter((page) => page.data.version === version);
}Add a version switcher to the sidebar by creating app/docs/layout.tsx.
import { DocsLayout } from "fumadocs-ui/layouts/docs";
import { source } from "@/lib/source";
import { baseOptions } from "@/app/layout.config";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<DocsLayout
tree={source.pageTree}
sidebar={{
banner: (
<select className="w-full rounded-md border bg-fd-card p-2">
<option value="v2">API v2 (latest)</option>
<option value="v1">API v1 (legacy)</option>
</select>
),
}}
{...baseOptions}
>
{children}
</DocsLayout>
);
}For a fully interactive switcher, extract the dropdown into a client component and use useRouter to navigate to the correct version subtree. Production sites typically segment versions into folders like content/docs/v1/ and content/docs/v2/.
Step 10: Add an OpenAPI Explorer
Fumadocs includes an official OpenAPI integration that turns your API spec into interactive documentation. Install it.
npm install fumadocs-openapiGenerate MDX files from an OpenAPI spec. Create scripts/generate-openapi.ts.
import { generateFiles } from "fumadocs-openapi";
void generateFiles({
input: ["./openapi.json"],
output: "./content/docs/reference",
per: "operation",
});Run the script.
npx tsx scripts/generate-openapi.tsEach API operation becomes its own MDX page, complete with parameter tables, response samples, and a live "Try it" panel. The generated pages live alongside your hand-written docs and inherit the same theme.
Step 11: Theme and Polish
Open app/global.css and customize the design tokens. Fumadocs uses HSL CSS variables that you can override per theme.
@layer base {
:root {
--color-fd-primary: 250 95% 55%;
--color-fd-primary-foreground: 0 0% 100%;
--color-fd-background: 0 0% 100%;
}
.dark {
--color-fd-primary: 250 95% 65%;
--color-fd-background: 240 10% 4%;
}
}The brand color now flows through buttons, links, active sidebar items, and search highlights. Changes hot-reload instantly thanks to Tailwind v4.
To add your logo to the top nav, drop an SVG in public/logo.svg and reference it in layout.config.tsx.
import Image from "next/image";
nav: {
title: (
<span className="flex items-center gap-2">
<Image src="/logo.svg" alt="" width={24} height={24} />
<span className="font-semibold">NoqtaDocs</span>
</span>
),
},Step 12: Build and Deploy
Build the production bundle.
npm run buildFumadocs uses Next.js incremental static regeneration by default. Every page is statically generated at build time, and the search index is shipped as a single JSON file fetched on demand.
For Vercel deployment, push to GitHub and import the repo. No additional configuration is needed.
For Cloudflare Pages, add a wrangler.toml and use the @cloudflare/next-on-pages adapter. Build size is typically under 5 MB even for 500-page sites, well within Cloudflare's free tier.
Testing Your Implementation
Run through this checklist before sharing your site.
- Open
/docsand click every sidebar item. All should render without errors. - Press
Cmd+Kand search for terms across multiple pages. Results should highlight matches. - Toggle dark mode using the theme switcher. Verify code blocks adapt correctly.
- Resize the window down to mobile width. The sidebar should collapse into a drawer.
- Inspect the page source. Each doc should ship with proper meta tags and OpenGraph images.
- Run Lighthouse. A correctly configured Fumadocs site scores 95 or higher on all metrics.
Troubleshooting
Sidebar does not show new pages. Restart the dev server. Fumadocs caches the page tree at startup and rebuilds it whenever source.config.ts or meta.json changes.
MDX components render as raw HTML. Make sure you imported the component at the top of the MDX file. Unlike the Pages Router, App Router MDX files do not auto-import.
Search returns no results. Check app/api/search/route.ts is exporting GET correctly. Open the browser network tab and look for a 200 response from /api/search.
Build fails with type errors on frontmatter. Your Zod schema in source.config.ts does not match the frontmatter in your MDX files. Add the missing field or mark it optional.
Next Steps
You now have a production-grade documentation site. Here are some ideas for extending it.
- Add a
/blogsection using the same MDX pipeline - Integrate analytics with PostHog or Plausible to track popular pages
- Auto-generate changelog pages from your GitHub releases
- Add an AI-powered docs assistant using the Vercel AI SDK and your search index
- Create a public API explorer that runs requests directly from the browser
For more advanced patterns, see our related tutorials on Next.js 15 server actions, the AI SDK, and TanStack Query.
Conclusion
Fumadocs strikes a rare balance between developer ergonomics and design polish. It gives you the same beautiful aesthetics as paid SaaS docs platforms while keeping every line of code in your repository. By combining server components, MDX 3, Tailwind v4, and Orama search, it produces a docs site that is fast to build, fast to load, and easy to maintain.
If you are starting a new project in 2026, Fumadocs is the safest bet for documentation. The community is vibrant, the API is stable, and the upgrade path is friendly. Ship it, share it, and watch your developer experience improve overnight.
Discuss Your Project with Us
We're here to help with your web development needs. Schedule a call to discuss your project and how we can assist you.
Let's find the best solutions for your needs.
Related Articles

Building an Autonomous AI Agent with Agentic RAG and Next.js
Learn how to build an AI agent that autonomously decides when and how to retrieve information from vector databases. A comprehensive hands-on guide using Vercel AI SDK and Next.js with executable examples.

Build a Semantic Search Engine with Next.js 15, OpenAI, and Pinecone
Learn how to build a production-ready semantic search engine using Next.js 15, OpenAI Embeddings, and Pinecone vector database. This comprehensive tutorial covers setup, indexing, querying, and deployment.

Build a Full-Stack App with Appwrite Cloud and Next.js 15
Learn how to build a complete full-stack application using Appwrite Cloud as your backend-as-a-service and Next.js 15 App Router. Covers authentication, databases, file storage, and real-time features.