Setup Fumadocs with Nextjs in 5 Minutes
Build beautiful docs with lightning speed
Fumadocs is a Javascript library that helps you build docs for your Nextjs site faster.
Fumadocs requires little configuration, and it allows markdownX syntax to write user-friendly documentation with speed and simplicity.
In this article, we will integrate Fumadocs with Nextjs 14, and we will do so within 5 minutes.
So without further ado… Let’s dive right in!
Install Dependencies
npm add fumadocs-core fumadocs-mdx fumadocs-ui @types/mdx
Bootstrap Nextjs with Fumadocs
First we need to wrap our application with the Fumadocs Root provider.
Do this by going into your root layout.tsx
or providers
file and wrap children
with the RootProvider
component:
"use client"
import { RootProvider } from "fumadocs-ui/provider"
import type { ReactNode } from "react"
export function NextInjectProvider({ children }: { children: ReactNode }) {
return <RootProvider>{children}</RootProvider>
}
Now lets make sure our Nextjs app is able to render MDX.
Do this by utilizing the withMDX
wrapper for our next.config.mjs
config object:
// MUST USE ESM MODULES
import createMDX from "fumadocs-mdx/config"
const withMDX = createMDX()
/** @type {import('next').NextConfig} */
const nextConfig = {
// ...
}
export default withMDX(nextConfig)
Lets ensure that the styles for fumadocs will be present by modifying our tailwind.config.ts
file and adding the createPreset
plugin:
import { createPreset } from "fumadocs-ui/tailwind-plugin"
import type { Config } from "tailwindcss"
const config = {
presets: [createPreset()],
content: [
// ...
"./node_modules/fumadocs-ui/dist/**/*.js",
"./content/**/*.mdx",
"./mdx-components.tsx",
],
// ...
} satisfies Config
export default config
If you are not using tailwind, feel free to simply import the fumadocs stylesheet in the root layout.tsx
as follows:
import 'fumadocs-ui/style.css'
Configure Content
Now that our Nextjs app can work with fumadocs, lets create and configure MDX content we would like to display.
Lets start by creating a mdx-components.tsx
file in the root of your nextjs project.
Here you should return any custom components that you wish to use in your markdownX files without using the import syntax:
import defaultComponents from "fumadocs-ui/mdx"
import type { MDXComponents } from "mdx/types"
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...defaultComponents,
...components,
// Can add more components here...
}
}
Next, lets start creating our MDX content.
To do this, create a folder called content
in the root of your project.
Inside this define docs/index.mdx
which represents your first documentation page:
---
title: Getting Started
description: Everything you need to get started
---
## Learn More
```js
console.log("hello world!")
```
In this file, we define the frontmatter attributes between dividers ---
, and you will be able to access these in your code as we will discover shortly.
From here, you just use the GFM MDX syntax to write your content.
The next step is to tell fumadocs that we have a content source inside content/docs
, and we must also specify a frontmatter schema that all of our markdownX files will adhere to.
Do this by creating src/app/source.ts
and copying the following code:
import { loader } from "fumadocs-core/source"
import { createMDXSource, defaultSchemas } from "fumadocs-mdx"
import { map } from "@/../.map"
export const frontmatterSchema = defaultSchemas.frontmatter.extend({
// ! Add any additional properties to the frontmatter
})
export const { getPage, getPages, pageTree } = loader({
baseUrl: "/docs",
rootDir: "docs",
source: createMDXSource(map, { schema: { frontmatter: frontmatterSchema } }),
})
Keep in mind that you can change the base url and root directory to whatever you like, but you will also need to rename content/docs
to match this.
And if you would like multiple content sources, you are able to define multiple loaders too.
Here is an official example from the fumadocs website:
import { map } from '@/.map';
import { loader } from 'fumadocs-core/source';
import { createMDXSource } from 'fumadocs-mdx';
export const docs = loader({
baseUrl: '/docs',
rootDir: 'docs',
source: createMDXSource(map),
});
export const blogs = loader({
baseUrl: '/blog',
rootDir: 'blog',
source: createMDXSource(map),
});
Another cool feature of fumadocs is the meta.json
file.
This allows you to organize how your content will be displayed on the fumadocs sidebar (which we will code in a bit).
For example, we can use meta.json
inside content/docs
to provide a section heading "Guide" and below it display the rest of your mdx pages from /docs
:
{
"root": true,
"pages": ["---Guide---", "..."]
}
Here is how it would look:
And if you wish to learn more about the meta.json file, feel free to visit this page.
Configure UI
Let's start by creating the folder structure src/app/docs
, then add a layout.tsx
file with the following content:
import { DocsLayout } from "fumadocs-ui/layout"
import type { ReactNode } from "react"
import { pageTree } from "@/app/source"
export default function RootDocsLayout({ children }: { children: ReactNode }) {
return (
<DocsLayout tree={pageTree} nav={{ title: "My App" }}>
{children}
</DocsLayout>
)
}
This will be in charge of rendering the sidebar, table of contents, and search bar to make your docs ergonomic and aesthetic.
But to actually see your individual pages, we must need a page.tsx
file for all potential slugs (e.g. docs/tutorial, docs/help/feature)
To do this, create a [[...slug]]/page.tsx
file inside your app/docs
folder with the following content:
import {
DocsBody,
DocsDescription,
DocsPage,
DocsTitle,
} from "fumadocs-ui/page"
import type { Metadata } from "next"
import { notFound } from "next/navigation"
import { getPage, getPages } from "@/app/source"
export default async function Page({
params,
}: {
params: { slug?: string[] }
}) {
const page = getPage(params.slug)
if (page == null) {
notFound()
}
const MDX = page.data.exports.default
return (
<DocsPage toc={page.data.exports.toc} full={page.data.full}>
{/* Display the title and description from the frontmatter */}
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
{/* Render the actual MDX content you have defined in the content/ folder. */}
<DocsBody>
<MDX />
</DocsBody>
</DocsPage>
)
}
// Statically generate your docs pages at runtime
export async function generateStaticParams() {
return getPages().map((page) => ({
slug: page.slugs,
}))
}
// Generate any dynamic metadata from your blog pages.
export function generateMetadata({ params }: { params: { slug?: string[] } }) {
const page = getPage(params.slug)
if (page == null) notFound()
return {
title: page.data.title,
description: page.data.description,
} satisfies Metadata
}
Search
You can implement simple flexsearch using the following code:
src/app/api/search/route.ts
import { createSearchAPI } from "fumadocs-core/search/server"
import { getPages } from "@/app/source"
export const { GET } = createSearchAPI("advanced", {
indexes: getPages().map((page) => ({
title: page.data.title,
structuredData: page.data.exports.structuredData,
id: page.url,
url: page.url,
})),
})
If you enjoyed this article, please make sure to Subscribe, Clap, Comment and Connect with me today! 🌐