Software Development

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:

Blog Image

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! 🌐
1