# Fumadocs CLI (the CLI tool for automating Fumadocs apps): User Guide URL: /docs/cli Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/cli/index.mdx The CLI tool that automates setups and installing components. ## Installation Initialize a config for CLI: ```bash npx @fumadocs/cli ``` ```bash pnpm dlx @fumadocs/cli ``` ```bash yarn dlx @fumadocs/cli ``` ```bash bun x @fumadocs/cli ``` You can change the output paths of components in the config. ### Components Select and install components. ```bash npx @fumadocs/cli add ``` ```bash pnpm dlx @fumadocs/cli add ``` ```bash yarn dlx @fumadocs/cli add ``` ```bash bun x @fumadocs/cli add ``` You can pass component names directly. ```bash npx @fumadocs/cli add banner files ``` ```bash pnpm dlx @fumadocs/cli add banner files ``` ```bash yarn dlx @fumadocs/cli add banner files ``` ```bash bun x @fumadocs/cli add banner files ``` #### How the magic works? The CLI fetches the latest version of component from the GitHub repository of Fumadocs. When you install the component, it is guaranteed to be up-to-date. In addition, it also transforms import paths. Make sure to use the latest version of CLI > This is highly Inspired by Shadcn UI. ### Customise A simple way to customise Fumadocs layouts. ```bash npx @fumadocs/cli customise ``` ```bash pnpm dlx @fumadocs/cli customise ``` ```bash yarn dlx @fumadocs/cli customise ``` ```bash bun x @fumadocs/cli customise ``` ### Tree Generate files tree for Fumadocs UI `Files` component, using the `tree` command from your terminal. ```bash npx @fumadocs/cli tree ./my-dir ./output.tsx ``` ```bash pnpm dlx @fumadocs/cli tree ./my-dir ./output.tsx ``` ```bash yarn dlx @fumadocs/cli tree ./my-dir ./output.tsx ``` ```bash bun x @fumadocs/cli tree ./my-dir ./output.tsx ``` You can output MDX file too: ```bash npx @fumadocs/cli tree ./my-dir ./output.mdx ``` ```bash pnpm dlx @fumadocs/cli tree ./my-dir ./output.mdx ``` ```bash yarn dlx @fumadocs/cli tree ./my-dir ./output.mdx ``` ```bash bun x @fumadocs/cli tree ./my-dir ./output.mdx ``` See help for further details: ```bash npx @fumadocs/cli tree -h ``` ```bash pnpm dlx @fumadocs/cli tree -h ``` ```bash yarn dlx @fumadocs/cli tree -h ``` ```bash bun x @fumadocs/cli tree -h ``` #### Example Output ```tsx title="output.tsx" import { File, Folder, Files } from 'fumadocs-ui/components/files'; export default ( ); ``` # Fumadocs Core (core library of framework): Custom Source URL: /docs/headless/custom-source Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/custom-source.mdx Build your own content source ## Introduction **Fumadocs is very flexible.** You can integrate with any content source, even without an official adapter. > This guide assumes you are experienced with Next.js App Router. ### Examples You can see examples to use Fumadocs with a CMS, which allows a nice experience on publishing content, and real-time update without re-building the app. * [BaseHub](https://github.com/fuma-nama/fumadocs-basehub) * [Sanity](https://github.com/fuma-nama/fumadocs-sanity) For a custom content source implementation, you will need: ### Page Tree You can either hardcode the page tree, or write some code to generate one. See [Definitions of Page Tree](/docs/headless/page-tree). Pass your Page Tree to `DocsLayout` (usually in a `layout.tsx`). ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` The page tree is like a smarter "sidebar items", they will be referenced everywhere in the UI for navigation elements, such as the page footer. ### Docs Page Same as a normal Next.js app, the code of your docs page is located in `[[...slug]]/page.tsx`. #### SSG Define the [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) function. It should return a list of parameters (`params`) to populate the `[[...slug]]` catch-all route. #### Body In the main body of page, find the corresponding page according to the slug and render its content inside the `DocsPage` component. You also need table of contents, which can be generated with your own implementation, or using the [`getTableOfContents`](/docs/headless/utils/get-toc) utility (Markdown/MDX only). ```tsx import { DocsPage, DocsBody } from 'fumadocs-ui/page'; import { getPage } from './my-content-source'; import { notFound } from 'next/navigation'; export default function Page({ params }: { params: { slug?: string[] } }) { const page = getPage(params.slug); if (!page) notFound(); return ( {page.render()} ); } ``` #### Metadata Next.js offers a Metadata API for SEO, you can configure it with `generateMetadata` (similar as the code above). ### Document Search This can be difficult considering your content may not be necessarily Markdown/MDX. For Markdown and MDX, the built-in [Search API](/docs/headless/search/orama) is adequate for most use cases. Otherwise, you will have to bring your own implementation. We recommend 3rd party solutions like Algolia Search. They are more flexible than the built-in Search API, and is easier to integrate with remote sources. Fumadocs offers a simple [Algolia Search Adapter](/docs/headless/search/algolia), which includes a search client to integrate with Fumadocs UI. ## MDX Remote Fumadocs offers the **MDX Remote** package, it is a helper to integrate Markdown-based content sources with Fumadocs. You can think it as a `next-mdx-remote` with built-in plugins for Fumadocs. ### Setup ```bash npm install @fumadocs/mdx-remote ``` ```bash pnpm add @fumadocs/mdx-remote ``` ```bash yarn add @fumadocs/mdx-remote ``` ```bash bun add @fumadocs/mdx-remote ``` The main feature it offers is the MDX Compiler, it can compile MDX content to JSX nodes. Since it doesn't use a bundler, there's some limitations: * No imports and exports in MDX files. It's compatible with Server Components. For example: ```tsx import { compileMDX } from '@fumadocs/mdx-remote'; import { getPage } from './my-content-source'; import { DocsBody, DocsPage } from 'fumadocs-ui/page'; import { getMDXComponents } from '@/mdx-components'; export default async function Page({ params, }: { params: { slug?: string[] }; }) { const page = getPage(params.slug); const compiled = await compileMDX({ source: page.content, }); const MdxContent = compiled.body; return ( ); } ``` #### Images On some platforms like Vercel, the original `public` folder (including static assets like images) will be removed after `next build`. `compileMDX` might no longer be able to access local images in `public`. When referencing images, make sure to use a URL. # Fumadocs Core (core library of framework): Introduction URL: /docs/headless Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/index.mdx Getting started with core library ## What is this? Fumadocs Core offers server-side functions and headless components to build docs on any React.js frameworks like Next.js. * Search (built-in: Orama, Algolia Search) * Breadcrumb, Sidebar, TOC Components * Remark/Rehype Plugins * Additional utilities It can be used without Fumadocs UI, in other words, it's headless. For beginners and normal usages, use [Fumadocs UI](/docs/ui). ## Installation No other dependencies required. ```bash npm install fumadocs-core ``` ```bash pnpm add fumadocs-core ``` ```bash yarn add fumadocs-core ``` ```bash bun add fumadocs-core ``` For some components, a framework provider is needed: ```tsx tab="Next.js" import type { ReactNode } from 'react'; import { NextProvider } from 'fumadocs-core/framework/next'; export function RootLayout({ children }: { children: ReactNode }) { // or if you're using Fumadocs UI, use `` return {children}; } ``` ```tsx tab="React Router" import type { ReactNode } from 'react'; import { ReactRouterProvider } from 'fumadocs-core/framework/react-router'; export function Root({ children }: { children: ReactNode }) { return {children}; } ``` ```tsx tab="Tanstack Start/Router" import type { ReactNode } from 'react'; import { TanstackProvider } from 'fumadocs-core/framework/tanstack'; export function Root({ children }: { children: ReactNode }) { return {children}; } ``` It offers simple document searching as well as components for building a good docs. # Fumadocs Core (core library of framework): Internationalization URL: /docs/headless/internationalization Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/internationalization.mdx Support multiple languages in your documentation ## Introduction Fumadocs core provides necessary middleware and options for i18n support. You can define a config to share between utilities. ```ts title="lib/i18n.ts" import type { I18nConfig } from 'fumadocs-core/i18n'; export const i18n: I18nConfig = { defaultLanguage: 'en', languages: ['en', 'cn'], }; ``` ### Hide Locale Prefix To hide the locale prefix (e.g. `/en/page` -> `/page`), use the `hideLocale` option. ```ts import type { I18nConfig } from 'fumadocs-core/i18n'; export const i18n: I18nConfig = { defaultLanguage: 'en', languages: ['en', 'cn'], hideLocale: 'default-locale', }; ``` | Mode | Description | | ---------------- | -------------------------------------------------- | | `always` | Always hide the prefix, detect locale from cookies | | `default-locale` | Only hide the default locale | | `never` | Never hide the prefix (default) | Using always}> On `always` mode, locale is stored as a cookie (set by the middleware), which isn't optimal for static sites. This may cause undesired cache problems, and need to pay extra attention on SEO to ensure search engines can index your pages correctly. ### Middleware Redirects users to appropriate locale, it can be customised from `i18n.ts`. ```ts title="middleware.ts" import { createI18nMiddleware } from 'fumadocs-core/i18n'; import { i18n } from '@/lib/i18n'; export default createI18nMiddleware(i18n); export const config = { // Matcher ignoring `/_next/` and `/api/` matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], }; ``` > When `hideLocale` is enabled, it uses `NextResponse.rewrite` to hide locale prefixes. # Fumadocs Core (core library of framework): Routing URL: /docs/headless/page-conventions Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/page-conventions.mdx A shared convention for organizing your documents This guide only applies for content sources that uses `loader()` API, such as Fumadocs MDX. ## Overview While Next.js handles routing, Fumadocs generates **page slugs** and **sidebar items** (page tree) from your content directory using [`loader()`](/docs/headless/source-api). You can define folders and pages similar to the file-system based routing of Next.js. ## File A [MDX](https://mdxjs.com) or Markdown file, you can customise its frontmatter. ```mdx --- title: My Page description: Best document ever icon: HomeIcon full: true --- ## Learn More ``` | name | description | | ------------- | -------------------------------------------------- | | `title` | The title of page | | `description` | The description of page | | `icon` | The name of icon, see [Icons](#icons) | | `full` | Fill all available space on the page (Fumadocs UI) | You can use the [`schema`](/docs/mdx/collections#schema-1) option to add frontmatter properties. ### Slugs The slugs of a page are generated from its file path. | path (relative to content folder) | slugs | | --------------------------------- | ----------------- | | `./dir/page.mdx` | `['dir', 'page']` | | `./dir/index.mdx` | `['dir']` | ## Folder Organize multiple pages, you can create a [Meta file](#meta) to customise folders. ### Folder Group By default, putting a file into folder will change its slugs. You can wrap the folder name in parentheses to avoid impacting the slugs of child files. | path (relative to content folder) | slugs | | --------------------------------- | ---------- | | `./(group-name)/page.mdx` | `['page']` | ## Meta Customise folders by creating a `meta.json` file in the folder. ```json title="meta.json" { "title": "Display Name", "icon": "MyIcon", "pages": ["index", "getting-started"], "defaultOpen": true } ``` | name | description | | ------------- | ------------------------------------- | | `title` | Display name | | `icon` | The name of icon, see [Icons](#icons) | | `pages` | Folder items (see below) | | `defaultOpen` | Open the folder by default | ### Pages By default, folder items are sorted alphabetically. You can add or control the order of items using `pages`, items are not included unless they are listed inside. ```json title="meta.json" { "title": "Name of Folder", "pages": ["guide", "components", "---My Separator---", "./nested/page"] } ``` #### Rest Add a `...` item to include remaining pages (sorted alphabetically), or `z...a` for descending order. ```json title="meta.json" { "pages": ["guide", "..."] } ``` You can add `!name` to prevent an item from being included. ```json title="meta.json" { "pages": ["guide", "...", "!components"] } ``` #### Extract You can extract the items from a folder with `...folder_name`. ```json title="meta.json" { "pages": ["guide", "...nested"] } ``` #### Link Use the syntax `[Text](url)` to insert links, or `[Icon][Text](url)` to add icon. ```json title="meta.json" { "pages": [ "[Vercel](https://vercel.com)", "[Triangle][Vercel](https://vercel.com)" ] } ``` ## Icons Since Fumadocs doesn't include an icon library, you have to convert the icon names to JSX elements in runtime, and render it as a component. You can add an [`icon` handler](/docs/headless/source-api#icons) to `loader()`. ## Root Folder Marks the folder as a root folder, only items in the opened root folder will be considered. ```json title="meta.json" { "title": "Name of Folder", "description": "The description of root folder (optional)", "root": true } ``` For example, when you are opening a root folder `framework`, the other folders (e.g. `headless`) are not shown on the sidebar and other navigation elements. Fumadocs UI renders root folders as [Sidebar Tabs](/docs/ui/navigation/sidebar#sidebar-tabs), which allows user to switch between them. ## Internationalization You can add Markdown/meta files for different languages by attending `.{locale}` to your file name, like `page.cn.md` and `meta.cn.json`. Make sure to create a file for the default locale first, the locale code is optional (e.g. both `get-started.mdx` and `get-started.en.mdx` are accepted). # Fumadocs Core (core library of framework): Page Tree URL: /docs/headless/page-tree Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/page-tree.mdx The structure of page tree. Page tree is a tree structure that describes all navigation links, with other items like separator and folders. It will be sent to the client and being referenced in navigation elements including the sidebar and breadcrumb. Hence, you shouldn't store any sensitive or large data in page tree. By design, page tree only contains necessary information of all pages and folders. Unserializable data such as functions can't be passed to page tree. ## Conventions The type definitions of page tree, for people who want to hardcode/generate it. You can also import the type from Fumadocs. ```ts import type { PageTree } from 'fumadocs-core/server'; const tree: PageTree.Root = { // props }; ``` Certain nodes contain a `$ref` property, they are internal and not used when hardcoding it. ### Root The initial root of page trees. ### Page ```json { "type": "page", "name": "Quick Start", "url": "/docs" } ``` > External urls are also supported ### Folder ```json { "type": "folder", "name": "Guide", "index": { "type": "page", ... } "children": [ ... ] } ``` ### Separator A label between items. ```json { "type": "separator", "name": "Components" } ``` ## Icons Icon is a `ReactElement`, supported by pages and folders. # Fumadocs Core (core library of framework): loader() URL: /docs/headless/source-api Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/source-api.mdx Turn a content source into a unified interface ## Usage `loader()` provides an interface for Fumadocs to integrate with file-system based content sources. ### What it does? * Generate page trees based on file system. * Assign URL and slugs to each page. * Output useful utilities to interact with content. It doesn't rely on the real file system (zero `node:fs` usage), a virtual storage is also allowed. You can use it with built-in content sources like Fumadocs MDX. ```ts import { loader } from 'fumadocs-core/source'; import { docs } from '@/.source'; export const source = loader({ source: docs.toFumadocsSource(), }); ``` ### URL You can override the base URL, or specify a function to generate URL for each page. ```ts import { loader } from 'fumadocs-core/source'; loader({ baseUrl: '/docs', // or you can customise it with function url(slugs, locale) { if (locale) return '/' + [locale, 'docs', ...slugs].join('/'); return '/' + ['docs', ...slugs].join('/'); }, }); ``` ### Icons Load the [icon](/docs/headless/page-conventions#icons) property specified by pages and meta files. ```ts import { loader } from 'fumadocs-core/source'; import { icons } from 'lucide-react'; import { createElement } from 'react'; loader({ icon(icon) { if (!icon) { // You may set a default icon return; } if (icon in icons) return createElement(icons[icon as keyof typeof icons]); }, }); ``` ### I18n Pass the `i18n` config to loader. ```ts title="lib/source.ts" import { i18n } from '@/lib/i18n'; import { loader } from 'fumadocs-core/source'; export const source = loader({ i18n, // [!code highlight] }); ``` With i18n enabled, loader will generate a page tree for every locale. When looking for a page, it fallbacks to default locale if the page doesn't exist for specified locale. ## Output The loader outputs a source object. ### Get Page Get page with slugs. ```ts import { source } from '@/lib/source'; source.getPage(['slug', 'of', 'page']); // with i18n source.getPage(['slug', 'of', 'page'], 'locale'); ``` ### Get Pages Get a list of page available for locale. ```ts import { source } from '@/lib/source'; // from default locale source.getPages(); // for a specific locale source.getPages('locale'); ``` ### Page Tree ```ts import { source } from '@/lib/source'; // without i18n source.pageTree; // with i18n source.pageTree['locale']; ``` ### Get from Node The page tree nodes contain references to their original file path. You can find their original page or meta file from the tree nodes. ```ts import { source } from '@/lib/source'; source.getNodePage(pageNode); source.getNodeMeta(folderNode); ``` ### Params A function to generate output for Next.js `generateStaticParams`. The generated parameter names will be `slug: string[]` and `lang: string` (i18n only). ```ts title="app/[[...slug]]/page.tsx" import { source } from '@/lib/source'; export function generateStaticParams() { return source.generateParams(); } ``` ### Language Entries Get available languages and its pages. ```ts import { source } from '@/lib/source'; // language -> pages const entries = source.getLanguages(); ``` ## Deep Dive As mentioned, Source API doesn't rely on real file systems. During the process, your input source files will be parsed and form a virtual storage to avoid inconsistent behaviour between different OS. ### Transformer To perform virtual file-system operations before processing, you can add a transformer. ```ts import { loader } from 'fumadocs-core/source'; loader({ transformers: [ ({ storage }) => { storage.makeDir(); }, ], }); ``` ### Page Tree The page tree is generated from your file system, some unnecessary information (e.g. unused frontmatter properties) will be filtered. You can customise it using the `pageTree` option, such as attaching custom properties to nodes, or customising the display name of pages. ```tsx import React from 'react'; import { loader } from 'fumadocs-core/source'; loader({ pageTree: { attachFile(node, file) { // you can access its file information console.log(file?.data); // JSX nodes are allowed node.name = <>Some JSX Nodes here; return node; }, }, }); ``` ### Custom Source To plug your own content source, create a `Source` object. It includes a `files` property which is an array of virtual files. Each virtual file must contain its file path and corresponding data. You can check type definitions for more info. Since Source API doesn't rely on file system, file paths cannot be absolute or relative (for example, `./file.mdx` and `D://content/file.mdx` are not allowed). Instead, pass the file paths like `file.mdx` and `content/file.mdx`. ```ts import { Source } from 'fumadocs-core/source'; export function createMySource(): Source<{ metaData: { title: string; pages: string[] }; // Your custom type pageData: { title: string; description: string }; // Your custom type }> { return { files: [], }; } ``` # Fumadocs MDX (the built-in content source): Async Mode URL: /docs/mdx/async Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/async.mdx Runtime compilation of content files. ## Introduction By default, all Markdown and MDX files need to be pre-compiled first, the same constraint also applies on development server. This may result in longer dev server start time for large docs sites, you can enable Async Mode on `doc` collections to improve this. ### Setup Install required dependencies. ```bash npm install @fumadocs/mdx-remote shiki ``` ```bash pnpm add @fumadocs/mdx-remote shiki ``` ```bash yarn add @fumadocs/mdx-remote shiki ``` ```bash bun add @fumadocs/mdx-remote shiki ``` Enable Async Mode. ```ts tab="Docs Collection" import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', docs: { async: true, }, }); ``` ```ts tab="Doc Collection" import { defineCollections } from 'fumadocs-mdx/config'; export const doc = defineCollections({ type: 'doc', dir: 'content/docs', async: true, }); ``` ### Usage Async Mode allows on-demand compilation of Markdown and MDX content, by moving the compilation process from build time to Next.js runtime. However, you need to invoke the `load()` async function to load and compile content. For example: ```tsx title="lib/source.ts" import { loader } from 'fumadocs-core/source'; import { docs } from '@/.source'; export const source = loader({ baseUrl: '/docs', source: docs.toFumadocsSource(), }); ``` ```tsx title="page.tsx" import { source } from '@/lib/source'; import { getMDXComponents } from '@/mdx-components'; const page = source.getPage(['...']); if (page) { // frontmatter properties are available console.log(page.data); // Markdown content requires await const { body: MdxContent, toc } = await page.data.load(); console.log(toc); return ; } ``` When using Async Mode, we highly recommend to use 3rd party services to implement search, which usually has a better capability to handle massive amount of content to index. ### Constraints It comes with some limitations on MDX features. * No import/export allowed in MDX files, for MDX components, pass them from the `components` prop instead. * Images must be referenced with URL (e.g. `/images/test.png`). Don't use **file paths** like `./image.png`, you should locate your images in `public` folder, and reference them with URLs. # Fumadocs MDX (the built-in content source): Collections URL: /docs/mdx/collections Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/collections.mdx Collection of content data for your app ## Define Collections Define a collection to parse a certain set of files. ```ts import { defineCollections } from 'fumadocs-mdx/config'; import { z } from 'zod'; export const blog = defineCollections({ type: 'doc', dir: './content/blog', schema: z.object({ // schema }), // other options }); ``` ### `type` The accepted type of collection. ```ts import { defineCollections } from 'fumadocs-mdx/config'; // only scan for json/yaml files export const metaFiles = defineCollections({ type: 'meta', // options }); ``` * `type: meta` Accept JSON/YAML Files, available options: * `type: doc` Markdown/MDX Documents, available options: ### `dir` Directories to scan input files. ### `schema` The schema to validate file data (frontmatter on `doc` type, content on `meta` type). ```ts import { defineCollections } from 'fumadocs-mdx/config'; import { z } from 'zod'; export const blog = defineCollections({ type: 'doc', dir: './content/blog', schema: z.object({ name: z.string(), }), }); ``` > [Standard Schema](https://standardschema.dev) compatible libraries, including Zod are supported. Note that the validation is done by build time, hence the output must be serializable. You can also pass a function and receives the transform context. ```ts import { defineCollections } from 'fumadocs-mdx/config'; import { z } from 'zod'; export const blog = defineCollections({ type: 'doc', dir: './content/blog', schema: (ctx) => { return z.object({ name: z.string(), testPath: z.string().default( // original file path ctx.path, ), }); }, }); ``` ### `mdxOptions` Customise MDX options on collection level. ```ts title="source.config.ts" import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; export const blog = defineCollections({ type: 'doc', mdxOptions: { // mdx options }, }); ``` By design, this will remove all default settings applied by your global config and Fumadocs MDX. You have full control over MDX options. You can use `getDefaultMDXOptions` to apply default configurations, it accepts the [extended MDX Options](/docs/mdx/mdx#extended). ```ts title="source.config.ts" import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; export const blog = defineCollections({ type: 'doc', mdxOptions: getDefaultMDXOptions({ // extended mdx options }), }); ``` > This API only available on `doc` type. ## Define Docs Define a collection for Fumadocs. ```ts import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: '/my/content/dir', docs: { // optional, options of `doc` collection }, meta: { // optional, options of `meta` collection }, }); ``` ### `dir` Instead of per collection, you should customise `dir` from `defineDocs`: ```ts import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'my/content/dir', }); ``` ### `schema` You can extend the default Zod schema of `docs` and `meta`. ```ts import { frontmatterSchema, metaSchema, defineDocs } from 'fumadocs-mdx/config'; import { z } from 'zod'; export const docs = defineDocs({ docs: { schema: frontmatterSchema.extend({ index: z.boolean().default(false), }), }, meta: { schema: metaSchema.extend({ // other props }), }, }); ``` # Fumadocs MDX (the built-in content source): Global Options URL: /docs/mdx/global Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/global.mdx Customise Fumadocs MDX ## Global Options Shared options of Fumadocs MDX. ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ // global options }); ``` ### MDX Options Customise the MDX processor options for MDX files. ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; import rehypeKatex from 'rehype-katex'; import remarkMath from 'remark-math'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkMath], // When order matters rehypePlugins: (v) => [rehypeKatex, ...v], }, }); ``` Some default options are applied by Fumadocs MDX, see [Extended MDX Options](/docs/mdx/mdx#extended) for available options. # Fumadocs MDX (the built-in content source): Include URL: /docs/mdx/include Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/include.mdx Reuse content from other files. ## Usage ### Markdown Specify the target Markdown file path in `` tag (relative to the Markdown file itself). ```mdx title="page.mdx" ./another.mdx ``` This will display the content from target file (e.g. `another.mdx`). ### CodeBlock For other types of files, it will become a codeblock: ```mdx title="page.mdx" ./script.ts ./script.ts ``` ### `cwd` Resolve relative paths from cwd instead of Markdown file: ```mdx ./script.ts ``` # Fumadocs MDX (the built-in content source): Introduction URL: /docs/mdx Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/index.mdx What is Fumadocs MDX? Fumadocs MDX is the official content source of Fumadocs. It provides the tool for Next.js to transform content into type-safe data, similar to Content Collections. This library made for Next.js, you can use it to handle blog and other contents. ## Getting Started Setup Fumadocs MDX for your Next.js application. ```bash npm install fumadocs-mdx @types/mdx ``` ```bash pnpm add fumadocs-mdx @types/mdx ``` ```bash yarn add fumadocs-mdx @types/mdx ``` ```bash bun add fumadocs-mdx @types/mdx ``` Add the plugin to your `next.config.mjs` file. ```js import { createMDX } from 'fumadocs-mdx/next'; const withMDX = createMDX(); /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, }; export default withMDX(config); ``` The Next.js config must be a `.mjs` file since Fumadocs is ESM-only. ### Defining Collections **Collection** refers to a collection containing a certain type of files, you can define collections by creating a `source.config.ts` file. Fumadocs MDX transforms collections into arrays of type-safe data, accessible in your app, available collections: Compile Markdown & MDX files into a React Server Component, with useful properties like **Table of Contents**. ```ts title="source.config.ts" import { defineCollections } from 'fumadocs-mdx/config'; export const test = defineCollections({ type: 'doc', dir: 'content/docs', }); ``` Transform YAML/JSON files into an array of data. ```ts title="source.config.ts" import { defineCollections } from 'fumadocs-mdx/config'; export const test = defineCollections({ type: 'meta', dir: 'content/docs', }); ``` Combination of `meta` and `doc` collections, which is needed for Fumadocs. ```ts title="source.config.ts" import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', docs: { // options for `doc` collection }, meta: { // options for `meta` collection }, }); ``` For example, a `doc` collection will transform the `.md` and `.mdx` files: ### Output Folder A `.source` folder is generated in root directory when you run `next dev` or `next build`, it contains all output data and types, you should add it to `.gitignore`. The `fumadocs-mdx` command also generates types for `.source` folder, add it as a post install script to ensure types are generated when initializing the project. ```json title="package.json" { "scripts": { "postinstall": "fumadocs-mdx" } } ``` ### Accessing Collections **Collection Output** is the generated data of a collection, it can have a different type/shape depending on the collection type and schema. You can access the collection output from `.source` folder with its original name: ```ts tab="source.config.ts" import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', docs: { // options for `doc` collection }, meta: { // options for `meta` collection }, }); ``` ```ts tab="Usage" import { docs } from '@/.source'; console.log(docs); ``` > Make sure you are importing from `.source` rather than `source.config.ts`, we will import it with `@/.source` import alias in this guide. ## Integrate with Fumadocs Create a `docs` collection and use the `toFumadocsSource()` function of its output. ```ts title="lib/source.ts" import { docs } from '@/.source'; import { loader } from 'fumadocs-core/source'; export const source = loader({ baseUrl: '/docs', source: docs.toFumadocsSource(), }); ``` > You can do the same for multiple `docs` collections. Generally, you'll interact with the collection through [`loader()`](/docs/headless/source-api#output). ```tsx import { source } from '@/lib/source'; const page = source.getPage(['slugs']); if (page) { // access page data [!code highlight] console.log(page.data); // frontmatter properties are also inside [!code highlight] console.log(page.data.title); } ``` To render the page, use `page.data.body` as a component. ```tsx import { getMDXComponents } from '@/mdx-components'; const MDX = page.data.body; // set your MDX components with `components` prop return ; ``` ## FAQ ### Built-in Properties These properties are exported from MDX files by default. | Property | Description | | ---------------- | ----------------------------------------------- | | `frontmatter` | Frontmatter | | `toc` | Table of Contents | | `structuredData` | Structured Data, useful for implementing search | ### Customise Frontmatter Use the [`schema`](/docs/mdx/collections#schema-1) option to pass a validation schema to validate frontmatter and define its output properties. ### MDX Plugins For other customisation needs such as Syntax Highlighting, see [MDX Options](/docs/mdx/mdx). # Fumadocs MDX (the built-in content source): Last Modified Time URL: /docs/mdx/last-modified Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/last-modified.mdx Output the last modified time of a document ## Usage This feature is not enabled by default, you can enable this from config file. Notice that it only supports Git as version control. Please ensure you have Git installed on your machine, and **the repository is not shallow cloned**, as it relies on your local Git history. ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ lastModifiedTime: 'git', // [!code highlight] }); ``` ### Access the Property After doing this, a `lastModified` number will be exported for each document, you can convert it to a JavaScript Date object. ```ts import { source } from '@/lib/source'; const page = source.getPage(['...']); console.log(new Date(page.data.lastModified)); // or with async mode: const { lastModified } = await page.data.load(); console.log(new Date(lastModified)); ``` # Fumadocs MDX (the built-in content source): MDX Options URL: /docs/mdx/mdx Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/mdx.mdx Configure MDX processor for Fumadocs MDX ## Customising MDX Processor Fumadocs MDX uses [MDX Compiler](https://mdxjs.com/packages/mdx) to compile MDX files into JavaScript files. You can customise it on [Global Config](/docs/mdx/global#mdx-options) or [Collection Config](/docs/mdx/collections#mdxoptions). ## Extended MDX Options \[#extended] Fumadocs MDX will apply some default MDX options, to make features like **syntax highlighting** work out of the box. To allow overriding the defaults, Fumadocs MDX's `mdxOptions` option accepts **Extended MDX Options** on top of [`ProcessorOptions`](https://mdxjs.com/packages/mdx/#processoroptions). You can see the additional options below: ### Remark Plugins These plugins are applied by default: * [Remark Image](/docs/headless/mdx/remark-image) - Handle images * [Remark Heading](/docs/headless/mdx/headings) - Extract table of contents * [Remark Structure](/docs/headless/mdx/structure) - Generate search indexes * Remark Exports - Exports the output generated by remark plugins above You can add other remark plugins with: ```ts tab="Global Config" import { defineConfig } from 'fumadocs-mdx/config'; import { myPlugin } from './remark-plugin'; export default defineConfig({ mdxOptions: { remarkPlugins: [myPlugin], // You can also pass a function to control the order of remark plugins. remarkPlugins: (v) => [myPlugin, ...v], }, }); ``` ```ts tab="Collection Config" import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; import { myPlugin } from './remark-plugin'; export const blog = defineCollections({ type: 'doc', mdxOptions: getDefaultMDXOptions({ remarkPlugins: [myPlugin], // You can also pass a function to control the order of remark plugins. remarkPlugins: (v) => [myPlugin, ...v], }), }); ``` ### Rehype Plugins These plugins are applied by default: * [Rehype Code](/docs/headless/mdx/rehype-code) - Syntax highlighting Same as remark plugins, you can pass an array or a function to add other rehype plugins. ```ts tab="Global Config" import { defineConfig } from 'fumadocs-mdx/config'; import { myPlugin } from './rehype-plugin'; export default defineConfig({ mdxOptions: { rehypePlugins: (v) => [myPlugin, ...v], }, }); ``` ```ts tab="Collection Config" import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; import { myPlugin } from './rehype-plugin'; export const blog = defineCollections({ type: 'doc', mdxOptions: getDefaultMDXOptions({ rehypePlugins: (v) => [myPlugin, ...v], }), }); ``` ### Customise Built-in Plugins Customise the options of built-in plugins like: ```ts tab="Global Config" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { rehypeCodeOptions: { // options }, remarkImageOptions: { // options }, remarkHeadingOptions: { // options }, }, }); ``` ```ts tab="Collection Config" import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; export const blog = defineCollections({ type: 'doc', mdxOptions: getDefaultMDXOptions({ rehypeCodeOptions: { // options }, remarkImageOptions: { // options }, remarkHeadingOptions: { // options }, }), }); ``` ### Export Properties from `vfile.data` Some remark plugins store their output in `vfile.data` (an compile-time memory) which cannot be accessed from your code. Fumadocs MDX applies a remark plugin that turns `vfile.data` properties into ESM exports, so that you can access these properties when importing the MDX file. You can define additional properties to be exported. ```ts tab="Global Config" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { valueToExport: ['dataName'], }, }); ``` ```ts tab="Collection Config" import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config'; export const blog = defineCollections({ type: 'doc', mdxOptions: getDefaultMDXOptions({ valueToExport: ['dataName'], }), }); ``` By default, it includes: * `toc` for the Remark Heading plugin * `structuredData` for the Remark Structure Plugin * `frontmatter` for the frontmatter of MDX (using `gray-matter`) # Fumadocs MDX (the built-in content source): Use as Page URL: /docs/mdx/page Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/page.mdx Use MDX file as a page ## Setup You can use `page.mdx` instead of `page.tsx` for creating a new page under the app directory. However, it doesn't have MDX components by default so you have to provide them: ```tsx title="mdx-components.tsx" import defaultMdxComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, // for Fumadocs UI ...components, }; } // export a `useMDXComponents()` that returns MDX components export const useMDXComponents = getMDXComponents; // [!code ++] ``` ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { // Path to import your `mdx-components.tsx` above. [!code ++] providerImportSource: '@/mdx-components', }, }); ``` ### Usage ```mdx title="app/test/page.mdx" {/* this will enable Typography styles of Fumadocs UI */} export { withArticle as default } from 'fumadocs-ui/page'; ## Hello World ``` # Fumadocs MDX (the built-in content source): Performance URL: /docs/mdx/performance Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/performance.mdx The performance of Fumadocs MDX ## Overview Fumadocs MDX is a bundler plugin, in other words, it has a higher performance bottleneck. With bundlers like Webpack and Turbopack, it is enough for large docs sites with nearly 500+ MDX files, which is sufficient for almost all use cases. Since Fumadocs MDX works with your bundler, you can import any files including client components in your MDX files. This allows a high flexibility and ensures everything is optimized by default. ### Image Optimization Fumadocs MDX resolves images into static imports with [Remark Image](/docs/headless/mdx/remark-image). Therefore, your images will be optimized automatically by the Next.js Image API. ```mdx ![Hello](./hello.png) or in public folder ![Hello](/hello.png) ``` Yields: ```mdx import HelloImage from './hello.png'; Hello ``` ![Banner](/banner.png) ## Caveats Although Fumadocs MDX can handle nearly 500+ files, it could be slow and inefficient. A huge amount of MDX files can cause an extremely high memory usage during build and development mode. This is because of: * Bundlers do a lot of work under the hood to bundle MDX and JavaScript files and optimize performance. * Bundlers are not supposed to compile hundreds of MDX files. ### Solutions The main solution is to make the compilation on-demand, such that content is only loaded when it's being requested. #### Remote Source Remote sources don't need to pre-compile MDX files, it can compile them on-demand with SSG which can **highly increase your build speed.** However, you cannot use import in MDX files anymore. See [Custom Source](/docs/headless/custom-source) for configuring remote sources. #### Async Mode See [Async Mode](/docs/mdx/async). # Fumadocs MDX (the built-in content source): Next.js Loader URL: /docs/mdx/plugin Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/plugin.mdx Customise the Next.js loader ## Plugin Options Fumadocs MDX offers loaders and a Fumadocs [Source API](/docs/headless/source-api) adapter to integrate with Fumadocs. You can configure the plugin by passing options to `createMDX` in `next.config.mjs`. ### Config Path Customise the path of config file. ```ts import { createMDX } from 'fumadocs-mdx/next'; const withMDX = createMDX({ configPath: './my-config.ts', }); ``` ### Development Server When running in development mode (`next dev`), a file watcher will be started to watch for changes. It automatically re-generates the index file in `.source` folder, ensuring Next.js hot reload is working properly. # Fumadocs Framework: Comparisons URL: /docs/ui/comparisons Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/comparisons.mdx How is Fumadocs different from other existing frameworks? ## Nextra Fumadocs is highly inspired by Nextra. For example, the Routing Conventions. That is why `meta.json` also exists in Fumadocs. Nextra is more opinionated than Fumadocs. Fumadocs is accelerated by App Router. As a result, It provides many server-side functions, and you have to configure things manually compared to simply editing a configuration file. Fumadocs works great if you want more control over everything, such as adding it to an existing codebase or implementing advanced routing. ### Feature Table | Feature | Fumadocs | Nextra | | ------------------- | ------------ | ------------------------- | | Static Generation | Yes | Yes | | Cached | Yes | Yes | | Light/Dark Mode | Yes | Yes | | Syntax Highlighting | Yes | Yes | | Table of Contents | Yes | Yes | | Full-text Search | Yes | Yes | | i18n | Yes | Yes | | Last Git Edit Time | Yes | Yes | | Page Icons | Yes | Yes, via `_meta.js` files | | RSC | Yes | Yes | | Remote Source | Yes | Yes | | SEO | Via Metadata | Yes | | Built-in Components | Yes | Yes | | RTL Layout | Yes | Yes | ### Additional Features Features supported via 3rd party libraries like [TypeDoc](https://typedoc.org) will not be listed here. | Feature | Fumadocs | Nextra | | -------------------------- | -------- | ------ | | OpenAPI Integration | Yes | No | | TypeScript Docs Generation | Yes | No | | TypeScript Twoslash | Yes | Yes | ## Mintlify Mintlify is a documentation service, as compared to Fumadocs, it offers a free tier but isn't completely free and open source. Fumadocs is not as powerful as Mintlify, for example, the OpenAPI integration of Mintlify. As the creator of Fumadocs, I wouldn't recommend switching to Fumadocs from Mintlify if you're satisfied with the current way you build docs. However, I believe Fumadocs is a suitable tool for all Next.js developers who want to have elegant docs. ## Docusaurus Docusaurus is a powerful framework based on React.js. It offers many cool features with plugins and custom themes. ### Better DX Since Fumadocs is built on the top of Next.js, you'll have to start the Next.js dev server every time to review changes, and initial boilerplate code is relatively more compared to Docusaurus. For a simple docs, Docusaurus might be a better choice if you don't need any Next.js specific functionality. However, when you want to use Next.js, or seek extra customizability like tuning default UI components, Fumadocs could be a better choice. ### Plugins You can easily achieve many things with plugins, their ecosystem is indeed larger and maintained by many contributors. In comparison, the flexibility of Fumadocs allows you to implement them on your own, it may take longer to tune it to your satisfaction. # Fumadocs Framework: Overview URL: /docs/ui/customisation Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/customisation.mdx An overview of Fumadocs UI ## Architecture | | | | ------------- | ------------------------------------------------------- | | **Sidebar** | Display site title and navigation elements. | | **Page Tree** | Passed by you, mainly rendered as the items of sidebar. | | **Docs Page** | All content of the page. | | **TOC** | Navigation within the article. | ## Customisation ### Layouts You can use the exposed options of different layouts: Layout for docs Layout for docs content A more compact version of Docs Layout Layout for other pages ### Components Fumadocs UI also offers styled components for interactive examples to enhance your docs, you can customise them with exposed props like `style` and `className`. See [Components](/docs/ui/components). ### Design System Since the design system is built on Tailwind CSS, you can customise it [with CSS Variables](/docs/ui/theme#colors). ### CLI Fumadocs CLI is a tool that installs components to your codebase, similar to Shadcn UI. ```bash npx @fumadocs/cli ``` ```bash pnpm dlx @fumadocs/cli ``` ```bash yarn dlx @fumadocs/cli ``` ```bash bun x @fumadocs/cli ``` Use it to install Fumadocs UI components: ```bash npx @fumadocs/cli add ``` ```bash pnpm dlx @fumadocs/cli add ``` ```bash yarn dlx @fumadocs/cli add ``` ```bash bun x @fumadocs/cli add ``` Or customise layouts: ```bash npx @fumadocs/cli customise ``` ```bash pnpm dlx @fumadocs/cli customise ``` ```bash yarn dlx @fumadocs/cli customise ``` ```bash bun x @fumadocs/cli customise ``` # Fumadocs Framework: Quick Start URL: /docs/ui Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/index.mdx Getting Started with Fumadocs ## Introduction Fumadocs (Foo-ma docs) is a **documentation framework** based on Next.js, designed to be fast, flexible, and composes seamlessly into Next.js App Router. Fumadocs has different parts: } title="Fumadocs Core"> Handles most of the logic, including document search, content source adapters, and Markdown extensions. } title="Fumadocs UI"> The default theme of Fumadocs offers a beautiful look for documentation sites and interactive components. } title="Content Source"> The source of your content, can be a CMS or local data layers like [Fumadocs MDX](/docs/mdx) (the official content source). } title="Fumadocs CLI"> A command line tool to install UI components and automate things, useful for customizing layouts. Read our in-depth [What is Fumadocs](/docs/ui/what-is-fumadocs) introduction. ### Terminology **Markdown/MDX:** Markdown is a markup language for creating formatted text. Fumadocs supports Markdown and MDX (superset of Markdown) out-of-the-box. Although not required, some basic knowledge of Next.js App Router would be useful for further customisations. ## Automatic Installation A minimum version of Node.js 18 required, note that Node.js 23.1 might have problems with Next.js production build. ```bash tab="npm" npm create fumadocs-app ``` ```bash tab="pnpm" pnpm create fumadocs-app ``` ```bash tab="yarn" yarn create fumadocs-app ``` ```bash tab="bun" bun create fumadocs-app ``` It will ask you: * the React.js framework to use (the docs is only written for Next.js). * the content source to use. A new fumadocs app should be initialized. Now you can start hacking! You can follow the [Manual Installation](/docs/ui/manual-installation) guide to get started. ### Enjoy! Create your first MDX file in the docs folder. ```mdx title="content/docs/index.mdx" --- title: Hello World --- ## Yo what's up ``` Run the app in development mode and see [http://localhost:3000/docs](http://localhost:3000/docs). ```bash npm run dev ``` ```bash pnpm run dev ``` ```bash yarn dev ``` ```bash bun run dev ``` ## FAQ Some common questions you may encounter. You can change the base route of docs (e.g. from `/docs/page` to `/info/page`). Since Fumadocs uses Next.js App Router, you can simply rename the route: And tell Fumadocs to use the new route in `source.ts`: ```ts title="lib/source.ts" import { loader } from 'fumadocs-core/source'; export const source = loader({ baseUrl: '/info', // other options }); ``` Next.js turns dynamic route into static routes when `generateStaticParams` is configured. Hence, it is as fast as static pages. You can enable Static Exports on Next.js to get a static build output. (Notice that Route Handler doesn't work with static export, you have to configure static search) Same as managing layouts in Next.js App Router, remove the original MDX file from content directory (`/content/docs`). This ensures duplicated pages will not cause errors. Now, You can add the page to another route group, which isn't a descendant of docs layout. For example, to replace `/docs/test`: For `/docs`, you need to change the catch-all route to be non-optional: We recommend to use [Sidebar Tabs](/docs/ui/navigation/sidebar#sidebar-tabs). ## Learn More New to here? Don't worry, we are welcome for your questions. If you find anything confusing, please give your feedback on [Github Discussion](https://github.com/fuma-nama/fumadocs/discussions)! ### Writing Content For authoring docs, make sure to read: Fumadocs has some additional features for authoring content. Learn how to customise navigation links and sidebar items. Learn how to organise content. # Fumadocs Framework: Internationalization URL: /docs/ui/internationalization Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/internationalization.mdx Support multiple languages in your documentation Fumadocs is not a full-powered i18n library, it manages only its own components and utilities. You can use other libraries like [next-intl](https://github.com/amannn/next-intl) for the rest of your app. Read the [Next.js Docs](https://nextjs.org/docs/app/building-your-application/routing/internationalization) to learn more about implementing I18n in Next.js. ## Manual Setup Define the i18n configurations in a file, we will import it with `@/ilb/i18n` in this guide. ```ts title="lib/i18n.ts" import type { I18nConfig } from 'fumadocs-core/i18n'; export const i18n: I18nConfig = { defaultLanguage: 'en', languages: ['en', 'cn'], }; ``` > See [customisable options](/docs/headless/internationalization). Pass it to the source loader. ```ts title="lib/source.ts" import { i18n } from '@/lib/i18n'; import { loader } from 'fumadocs-core/source'; export const source = loader({ i18n, // [!code highlight] // other options }); ``` And update Fumadocs UI layout options. ```tsx title="app/layout.config.tsx" import { i18n } from '@/lib/i18n'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(locale: string): BaseLayoutProps { return { i18n, // different props based on `locale` }; } ``` ### Middleware Create a middleware that redirects users to appropriate locale. ```ts title="middleware.ts" import { createI18nMiddleware } from 'fumadocs-core/i18n'; import { i18n } from '@/lib/i18n'; export default createI18nMiddleware(i18n); export const config = { // Matcher ignoring `/_next/` and `/api/` matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], }; ``` The default middleware is optional, you can instead use your own middleware or the one provided by i18n libraries. When using custom middleware, make sure the locale is correctly passed to Fumadocs. You may also want to [customise page URLs](/docs/headless/source-api#url) from `loader()`. ### Routing Create a `/app/[lang]` folder, and move all files (e.g. `page.tsx`, `layout.tsx`) from `/app` to the folder. Provide UI translations and other config to ``. Note that only English translations are provided by default. ```tsx title="app/[lang]/layout.tsx" import { RootProvider } from 'fumadocs-ui/provider'; import type { Translations } from 'fumadocs-ui/i18n'; const cn: Partial = { search: 'Translated Content', // other translations }; // available languages that will be displayed on UI // make sure `locale` is consistent with your i18n config const locales = [ { name: 'English', locale: 'en', }, { name: 'Chinese', locale: 'cn', }, ]; export default async function RootLayout({ params, children, }: { params: Promise<{ lang: string }>; children: React.ReactNode; }) { const lang = (await params).lang; return ( {children} ); } ``` ### Pass Locale Pass the locale to Fumadocs in your pages and layouts. ```tsx title="/app/[lang]/(home)/layout.tsx" tab="Home Layout" import type { ReactNode } from 'react'; import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { baseOptions } from '@/app/layout.config'; export default async function Layout({ params, children, }: { params: Promise<{ lang: string }>; children: ReactNode; }) { const { lang } = await params; return {children}; } ``` ```tsx title="/app/[lang]/docs/layout.tsx" tab="Docs Layout" import type { ReactNode } from 'react'; import { source } from '@/lib/source'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/app/layout.config'; export default async function Layout({ params, children, }: { params: Promise<{ lang: string }>; children: ReactNode; }) { const { lang } = await params; return ( {children} ); } ``` ```ts title="page.tsx" tab="Docs Page" import { source } from '@/lib/source'; export default async function Page({ params, }: { params: Promise<{ lang: string; slug?: string[] }>; }) { const { slug, lang } = await params; // get page source.getPage(slug); // [!code --] source.getPage(slug, lang); // [!code ++] // get pages source.getPages(); // [!code --] source.getPages(lang); // [!code ++] } ``` Using another name for lang dynamic segment?}> If you're using another name like `app/[locale]`, you also need to update `generateStaticParams()` in docs page: ```tsx export function generateStaticParams() { return source.generateParams(); // [!code --] return source.generateParams('slug', 'locale'); // [!code ++] new param name } ``` ### Search Configure i18n on your search solution. * **Built-in Search (Orama):** For [Supported Languages](https://docs.orama.com/open-source/supported-languages#officially-supported-languages), no further changes are needed. Otherwise, additional config is required (e.g. Chinese & Japanese). See [Special Languages](/docs/headless/search/orama#special-languages). * **Cloud Solutions (e.g. Algolia):** They usually have official support for multilingual. ## Writing Documents You can add Markdown/meta files for different languages by attending `.{locale}` to your file name, like `page.cn.md` and `meta.cn.json`. Make sure to create a file for the default locale first, the locale code is optional (e.g. both `get-started.mdx` and `get-started.en.mdx` are accepted). ## Navigation Fumadocs only handles navigation for its own layouts (e.g. sidebar). For other places, you can use the `useParams` hook to get the locale from url, and attend it to `href`. ```tsx import Link from 'next/link'; import { useParams } from 'next/navigation'; const { lang } = useParams(); return This is a link; ``` In addition, the [`fumadocs-core/dynamic-link`](/docs/headless/components/link#dynamic-hrefs) component supports dynamic hrefs, you can use it to attend the locale prefix. It is useful for Markdown/MDX content. ```mdx title="content.mdx" import { DynamicLink } from 'fumadocs-core/dynamic-link'; This is a link ``` # Fumadocs Framework: Manual Installation URL: /docs/ui/manual-installation Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/manual-installation.mdx Add Fumadocs to existing projects. Before continuing, make sure: * Next.js 15 and Tailwind CSS 4 are configured. ## Getting Started ```bash npm install fumadocs-ui fumadocs-core ``` ```bash pnpm add fumadocs-ui fumadocs-core ``` ```bash yarn add fumadocs-ui fumadocs-core ``` ```bash bun add fumadocs-ui fumadocs-core ``` ### MDX Components ```tsx title="mdx-components.tsx" import defaultMdxComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, ...components, }; } ``` ### Content Source Fumadocs supports different content sources, including Fumadocs MDX and [Content Collections](/docs/headless/content-collections). Fumadocs MDX is our official content source, you can configure it with: ```bash npm install fumadocs-mdx @types/mdx ``` ```bash pnpm add fumadocs-mdx @types/mdx ``` ```bash yarn add fumadocs-mdx @types/mdx ``` ```bash bun add fumadocs-mdx @types/mdx ``` ```js tab="next.config.mjs" import { createMDX } from 'fumadocs-mdx/next'; const withMDX = createMDX(); /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, }; export default withMDX(config); ``` ```ts tab="source.config.ts" import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', }); ``` ```json tab="package.json" { "scripts": { "postinstall": "fumadocs-mdx" // [!code ++] } } ``` Finally, to access your content: ```ts title="lib/source.ts" // .source folder will be generated when you run `next dev` import { docs } from '@/.source'; import { loader } from 'fumadocs-core/source'; export const source = loader({ baseUrl: '/docs', source: docs.toFumadocsSource(), }); ``` ### Root Layout Wrap the entire application inside [Root Provider](/docs/ui/layouts/root-provider), and add required styles to `body`. ```tsx import { RootProvider } from 'fumadocs-ui/provider'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ### Styles Add the following to `global.css`. ```css title="Tailwind CSS" @import 'tailwindcss'; @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; ``` > It doesn't come with a default font, you may choose one from `next/font`. ### Layout Create a `app/layout.config.tsx` file to put the shared options for our layouts. ```tsx title="app/layout.config.tsx" import { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export const baseOptions: BaseLayoutProps = { nav: { title: 'My App', }, }; ``` Create a folder `/app/docs` for our docs, and give it a proper layout. ```tsx title="app/docs/layout.tsx" import { source } from '@/lib/source'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import type { ReactNode } from 'react'; import { baseOptions } from '@/app/layout.config'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` > `pageTree` refers to Page Tree, it should be provided by your content source. ### Page Create a catch-all route `/app/docs/[[...slug]]` for docs pages. In the page, wrap your content in the [Page](/docs/ui/layouts/page) component. ```tsx title="app/docs/\[\[...slug]]/page.tsx" tab="Fumadocs MDX" import { source } from '@/lib/source'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; import { notFound } from 'next/navigation'; import { getMDXComponents } from '@/mdx-components'; 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 ( {page.data.title} {page.data.description} ); } 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) notFound(); return { title: page.data.title, description: page.data.description, }; } ``` ```tsx title="app/docs/\[\[...slug]]/page.tsx" tab="Content Collections" import { source } from '@/lib/source'; import type { Metadata } from 'next'; import { DocsPage, DocsBody, DocsTitle, DocsDescription, } from 'fumadocs-ui/page'; import { notFound } from 'next/navigation'; import { MDXContent } from '@content-collections/mdx/react'; import { getMDXComponents } from '@/mdx-components'; export default async function Page(props: { params: Promise<{ slug?: string[] }>; }) { const params = await props.params; const page = source.getPage(params.slug); if (!page) { notFound(); } return ( {page.data.title} {page.data.description} ); } 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) notFound(); return { title: page.data.title, description: page.data.description, } satisfies Metadata; } ``` ### Search Use the default document search based on Orama. ```ts title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; export const { GET } = createFromSource(source); ``` Learn more about [Document Search](/docs/headless/search). ### Done You can start the dev server and create MDX files. ```mdx title="content/docs/index.mdx" --- title: Hello World --- ## Introduction I love Anime. ``` ## Deploying It should work out-of-the-box with Vercel & Netlify. ### Docker Deployment If you want to deploy your Fumadocs app using Docker with **Fumadocs MDX configured**, make sure to add the `source.config.ts` file to the `WORKDIR` in the Dockerfile. The following snippet is taken from the official [Next.js Dockerfile Example](https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile): ```zsh title="Dockerfile" WORKDIR /app # Install dependencies based on the preferred package manager COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* source.config.ts ./ ``` This ensures Fumadocs MDX can access your configuration file during builds. # Fumadocs Framework: Routing URL: /docs/ui/page-conventions Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/page-conventions.mdx A shared convention for organizing your documents This guide only applies for content sources that uses `loader()` API, such as Fumadocs MDX. ## Overview While Next.js handles routing, Fumadocs generates **page slugs** and **sidebar items** (page tree) from your content directory using [`loader()`](/docs/headless/source-api). You can define folders and pages similar to the file-system based routing of Next.js. ## File A [MDX](https://mdxjs.com) or Markdown file, you can customise its frontmatter. ```mdx --- title: My Page description: Best document ever icon: HomeIcon full: true --- ## Learn More ``` | name | description | | ------------- | -------------------------------------------------- | | `title` | The title of page | | `description` | The description of page | | `icon` | The name of icon, see [Icons](#icons) | | `full` | Fill all available space on the page (Fumadocs UI) | You can use the [`schema`](/docs/mdx/collections#schema-1) option to add frontmatter properties. ### Slugs The slugs of a page are generated from its file path. | path (relative to content folder) | slugs | | --------------------------------- | ----------------- | | `./dir/page.mdx` | `['dir', 'page']` | | `./dir/index.mdx` | `['dir']` | ## Folder Organize multiple pages, you can create a [Meta file](#meta) to customise folders. ### Folder Group By default, putting a file into folder will change its slugs. You can wrap the folder name in parentheses to avoid impacting the slugs of child files. | path (relative to content folder) | slugs | | --------------------------------- | ---------- | | `./(group-name)/page.mdx` | `['page']` | ## Meta Customise folders by creating a `meta.json` file in the folder. ```json title="meta.json" { "title": "Display Name", "icon": "MyIcon", "pages": ["index", "getting-started"], "defaultOpen": true } ``` | name | description | | ------------- | ------------------------------------- | | `title` | Display name | | `icon` | The name of icon, see [Icons](#icons) | | `pages` | Folder items (see below) | | `defaultOpen` | Open the folder by default | ### Pages By default, folder items are sorted alphabetically. You can add or control the order of items using `pages`, items are not included unless they are listed inside. ```json title="meta.json" { "title": "Name of Folder", "pages": ["guide", "components", "---My Separator---", "./nested/page"] } ``` #### Rest Add a `...` item to include remaining pages (sorted alphabetically), or `z...a` for descending order. ```json title="meta.json" { "pages": ["guide", "..."] } ``` You can add `!name` to prevent an item from being included. ```json title="meta.json" { "pages": ["guide", "...", "!components"] } ``` #### Extract You can extract the items from a folder with `...folder_name`. ```json title="meta.json" { "pages": ["guide", "...nested"] } ``` #### Link Use the syntax `[Text](url)` to insert links, or `[Icon][Text](url)` to add icon. ```json title="meta.json" { "pages": [ "[Vercel](https://vercel.com)", "[Triangle][Vercel](https://vercel.com)" ] } ``` ## Icons Since Fumadocs doesn't include an icon library, you have to convert the icon names to JSX elements in runtime, and render it as a component. You can add an [`icon` handler](/docs/headless/source-api#icons) to `loader()`. ## Root Folder Marks the folder as a root folder, only items in the opened root folder will be considered. ```json title="meta.json" { "title": "Name of Folder", "description": "The description of root folder (optional)", "root": true } ``` For example, when you are opening a root folder `framework`, the other folders (e.g. `headless`) are not shown on the sidebar and other navigation elements. Fumadocs UI renders root folders as [Sidebar Tabs](/docs/ui/navigation/sidebar#sidebar-tabs), which allows user to switch between them. ## Internationalization You can add Markdown/meta files for different languages by attending `.{locale}` to your file name, like `page.cn.md` and `meta.cn.json`. Make sure to create a file for the default locale first, the locale code is optional (e.g. both `get-started.mdx` and `get-started.en.mdx` are accepted). # Fumadocs Framework: Search URL: /docs/ui/search Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search.mdx Implement document search in your docs ## Search UI You can customise the search UI from root provider, such as disabling search: ```tsx title="app/layout.tsx" import { RootProvider } from 'fumadocs-ui/provider'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ### Hot Keys Customise the hot keys to trigger search dialog, by default it's ⌘ K or Ctrl K. ```tsx import { RootProvider } from 'fumadocs-ui/provider'; {children} ; ``` ### Replace Search Dialog Make a `` wrapper with `use client` directive, and use it instead of your previous root provider. ```tsx tab="provider.tsx" 'use client'; import { RootProvider } from 'fumadocs-ui/provider'; import type { ReactNode } from 'react'; // for example, using the default `fetch()` client import SearchDialog from 'fumadocs-ui/components/dialog/search-default'; export function Provider({ children }: { children: ReactNode }) { return ( {children} ); } ``` ```tsx tab="app/layout.tsx" import { Provider } from './provider'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ## Search Server The search server/backend is provided and documented on Fumadocs Core, see [available engines](/docs/headless/search). ## Search Client You can choose a search client according to your search engine, it defaults to `fetch` client. ### `fetch` It sends queries to an API endpoint, works with the built-in [Orama search engine](/docs/headless/search/orama). You can pass options to the search client: ```tsx import { RootProvider } from 'fumadocs-ui/provider'; {children} ; ``` #### Tag Filter Add UI for filtering results by tags, [configure Tag Filter](/docs/headless/search/orama#tag-filter) on search server and add the following: ```tsx import { RootProvider } from 'fumadocs-ui/provider'; {children} ; ``` ### Algolia For the setup guide, see [Integrate Algolia Search](/docs/headless/search/algolia). While generally we recommend building your own search with their client-side SDK, you can also plug the built-in dialog interface. ```tsx title="components/search.tsx" 'use client'; import algo from 'algoliasearch/lite'; import type { SharedProps } from 'fumadocs-ui/components/dialog/search'; import SearchDialog from 'fumadocs-ui/components/dialog/search-algolia'; const appId = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID; const apiKey = process.env.NEXT_PUBLIC_ALGOLIA_API_KEY; const indexName = process.env.NEXT_PUBLIC_ALGOLIA_INDEX; if (!appId || !apiKey || !indexName) throw new Error('Algolia credentials'); const client = algo(appId, apiKey); const index = client.initIndex(indexName); export default function CustomSearchDialog(props: SharedProps) { return ; } ``` 1. Replace `appId`, `apiKey` and `indexName` with your desired values. 2. [Replace the default search dialog](#replace-search-dialog) with your new component. The built-in implementation doesn't use instant search (their official javascript client). #### Tag Filter Same as default search client, you can configure [Tag Filter](/docs/headless/search/algolia#tag-filter) on the dialog. ```tsx title="components/search.tsx" import SearchDialog from 'fumadocs-ui/components/dialog/search-algolia'; ; ``` ### Orama Cloud For the setup guide, see [Integrate Orama Cloud](/docs/headless/search/orama-cloud). ```tsx title="components/search.tsx" 'use client'; import { OramaClient } from '@oramacloud/client'; import type { SharedProps } from 'fumadocs-ui/components/dialog/search'; import SearchDialog from 'fumadocs-ui/components/dialog/search-orama'; const client = new OramaClient({ endpoint: 'endpoint', api_key: 'apiKey', }); export default function CustomSearchDialog(props: SharedProps) { return ; } ``` 1. Replace `endpoint`, `apiKey` with your desired values. 2. [Replace the default search dialog](#replace-search-dialog) with your new component. ### Community Integrations A list of integrations maintained by community. * [Trieve Search](/docs/headless/search/trieve) ## Built-in UI If you want to use the built-in search dialog UI instead of building your own, you may use the `SearchDialog` component. ```tsx import { SearchDialog, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; export default function CustomSearchDialog(props: SharedProps) { return ; } ``` It is an internal API, might break during iterations # Fumadocs Framework: Static Export URL: /docs/ui/static-export Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/static-export.mdx Enable static export with Fumadocs ## Overview Fumadocs is fully compatible with Next.js static export, allowing you to export the app as a static HTML site without a Node.js server. ```js title="next.config.mjs" /** * @type {import('next').NextConfig} */ const nextConfig = { output: 'export', // Optional: Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html` // trailingSlash: true, // Optional: Prevent automatic `/me` -> `/me/`, instead preserve `href` // skipTrailingSlashRedirect: true, }; ``` See [Next.js docs](https://nextjs.org/docs/app/guides/static-exports) for limitations and details. ## Search ### Cloud Solutions Since the search functionality is powered by remote servers, static export works without configuration. ### Built-in Search You need to: 1. Build the search indexes statically using [`staticGET`](/docs/headless/search/orama#static-export). 2. Enable static mode on search client from Root Provider: ```tsx title="app/layout.tsx" import { RootProvider } from 'fumadocs-ui/provider'; import type { ReactNode } from 'react'; export default function RootLayout({ children }: { children: ReactNode }) { return ( {children} ); } ``` This allows the route handler to be statically cached into a single file, and search will be computed on browser instead. # Fumadocs Framework: Themes URL: /docs/ui/theme Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/theme.mdx Add Theme to Fumadocs UI ## Usage Only Tailwind CSS v4 is supported, the preset will also include source to Fumadocs UI itself: ```css title="Tailwind CSS" @import 'tailwindcss'; @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; ``` ### Preflight Changes By using the Tailwind CSS plugin, or the pre-built stylesheet, your default border, text and background colors will be changed. ### Light/Dark Modes Fumadocs supports light/dark modes with [`next-themes`](https://github.com/pacocoursey/next-themes), it is included in Root Provider. See [Root Provider](/docs/ui/layouts/root-provider#theme-provider) to learn more. ### RTL Layout RTL (Right-to-left) layout is supported. To enable RTL, set the `dir` prop to `rtl` in body and root provider (required for Radix UI). ```tsx import { RootProvider } from 'fumadocs-ui/provider'; import type { ReactNode } from 'react'; export default function RootLayout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ### Layout Width Customise the max width of docs layout with CSS Variables. ```css :root { --fd-layout-width: 1400px; } ``` ## Tailwind CSS Preset Fumadocs UI adds its own colors, animations, and utilities with Tailwind CSS preset. ### Colors It comes with many themes out-of-the-box, you can pick one you prefer. ```css @import 'fumadocs-ui/css/.css'; @import 'fumadocs-ui/css/preset.css'; ``` ![Neutral](/themes/neutral.png) ![Black](/themes/black.png) ![Vitepress](/themes/vitepress.png) ![Dusk](/themes/dusk.png) ![Catppuccin](/themes/catppuccin.png) ![Ocean](/themes/ocean.png) ![Purple](/themes/purple.png) The design system was inspired by [Shadcn UI](https://ui.shadcn.com), you can also customize the colors using CSS variables. ```css title="global.css" :root { --color-fd-background: hsl(0, 0%, 100%); } .dark { --color-fd-background: hsl(0, 0%, 0%); } ``` For Shadcn UI, you can use the `shadcn` preset instead. It uses colors from your Shadcn UI theme. ```css @import 'tailwindcss'; @import 'fumadocs-ui/css/shadcn.css'; @import 'fumadocs-ui/css/preset.css'; ``` ### Typography We have a built-in plugin forked from [Tailwind CSS Typography](https://tailwindcss.com/docs/typography-plugin). The plugin adds a `prose` class and variants to customise it. ```tsx

Good Heading

``` > The plugin works with and only with Fumadocs UI's MDX components, it may conflict with `@tailwindcss/typography`. > If you need to use `@tailwindcss/typography` over the default plugin, [set a class name option](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/README.md#changing-the-default-class-name) to avoid conflicts. # Fumadocs Framework: Versioning URL: /docs/ui/versioning Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/versioning.mdx Implementing multi-version in your docs. ## Overview It's common for developer tool related docs to version their docs, such as different docs for v1 and v2 of the same tool. Fumadocs provide the primitives for you to implement versioning on your own way. ## Partial Versioning When versioning only applies to part of your docs, You can separate them by folders. For example: You may want to group them with tabs rather than folders [using Sidebar Tabs](/docs/ui/navigation/sidebar#sidebar-tabs). ## Full Versioning Sometimes you want to version the entire website, such as [https://v14.fumadocs.dev](https://v14.fumadocs.dev) (Fumadocs v14) and [https://fumadocs.dev](https://fumadocs.dev) (Latest Fumadocs). You can create a Git branch for a version of docs (call it `v2` for example), and deploy it as a separate app on another subdomain like `v2.my-site.com`. Optionally, you can link to the other versions from your docs. This design allows some advantages over partial versioning: * Easy maintenance: Old docs/branches won't be affected when you iterate or upgrade dependencies. * Better consistency: Not just the docs itself, your landing page (and other pages) will also be versioned. # Fumadocs Framework: What is Fumadocs URL: /docs/ui/what-is-fumadocs Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/what-is-fumadocs.mdx Introducing Fumadocs, a docs framework that you can break. Fumadocs was created because I wanted a more customisable experience for building docs, to be a docs framework that is not opinionated, **a "framework" that you can break**. ## Philosophy **Less Abstraction:** Fumadocs expects you to write code and cooperate with the rest of your software. While most frameworks are configured with a configuration file, they usually lack flexibility when you hope to tune its details. You can’t control how they render the page nor the internal logic. Fumadocs shows you how the app works, instead of a single configuration file. **Next.js Fundamentals:** It gives you the utilities and a good-looking UI. You are still using features of Next.js App Router, like **Static Site Generation**. There is nothing new for Next.js developers, so you can use it with confidence. **Opinionated on UI:** The only thing Fumadocs UI (the default theme) offers is **User Interface**. The UI is opinionated for bringing better mobile responsiveness and user experience. Instead, we use a much more flexible approach inspired by Shadcn UI — [Fumadocs CLI](/docs/cli), so we can iterate our design quick, and welcome for more feedback about the UI. ## Why Fumadocs Fumadocs is designed with flexibility in mind. You can use `fumadocs-core` as a headless UI library and bring your own styles. Fumadocs MDX is also a useful library to handle MDX content in Next.js. It also includes: * Many built-in components. * Typescript Twoslash, OpenAPI, and Math (KaTeX) integrations. * Fast and optimized by default, natively built on App Router. * Tight integration with Next.js, you can add it to an existing Next.js project easily. You can read [Comparisons](/docs/ui/comparisons) if you're interested. ### Documentation Fumadocs focuses on **authoring experience**, it provides a beautiful theme and many docs automation tools. It helps you to iterate your codebase faster while never leaving your docs behind. You can take this site as an example of docs site built with Fumadocs. ### Blog sites Since Next.js is already a powerful framework, most features can be implemented with **just Next.js**. Fumadocs provides additional tooling for Next.js, including syntax highlighting, document search, and a default theme (Fumadocs UI). It helps you to avoid reinventing the wheels. ## When to use Fumadocs For most of the web applications, vanilla React.js is no longer enough. Nowadays, we also wish to have a blog, a showcase page, a FAQ page, etc. With a fancy UI that's breathtaking, in these cases, Fumadocs can help you build the docs easier, with less boilerplate. Fumadocs is maintained by Fuma and many contributors, with care on the maintainability of codebase. While we don't aim to offer every functionality people wanted, we're more focused on making basic features perfect and well-maintained. You can also help Fumadocs to be more useful by contributing! # Fumadocs Core (core library of framework): Breadcrumb URL: /docs/headless/components/breadcrumb Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/components/breadcrumb.mdx The navigation component at the top of screen A hook for implementing Breadcrumb in your documentation, it returns the path to a page based on the given page tree. > If present, the index page of a folder will be used as the item ## Usage it exports a `useBreadcrumb` hook: ```ts twoslash declare const tree: any; // ---cut--- import { usePathname } from 'next/navigation'; import { useBreadcrumb } from 'fumadocs-core/breadcrumb'; const pathname = usePathname(); const items = useBreadcrumb(pathname, tree); // ^? ``` ### Example A styled example. ```tsx 'use client'; import { usePathname } from 'next/navigation'; import { useBreadcrumb } from 'fumadocs-core/breadcrumb'; import type { PageTree } from 'fumadocs-core/server'; import { Fragment } from 'react'; import { ChevronRight } from 'lucide-react'; import Link from 'next/link'; export function Breadcrumb({ tree }: { tree: PageTree.Root }) { const pathname = usePathname(); const items = useBreadcrumb(pathname, tree); if (items.length === 0) return null; return (
{items.map((item, i) => ( {i !== 0 && ( )} {item.url ? ( {item.name} ) : ( {item.name} )} ))}
); } ``` You can use it by passing the page tree via `tree` in a server component. ### Breadcrumb Item # Fumadocs Core (core library of framework): Components URL: /docs/headless/components Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/components/index.mdx Blocks for your docs # Fumadocs Core (core library of framework): Link URL: /docs/headless/components/link Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/components/link.mdx A Link component that handles external links A component that wraps `next/link` and handles external links in the document. When an external URL is detected, it uses `` instead of the Next.js Link Component. `rel` property is automatically generated. ## Usage Same as using ``. ```mdx import Link from 'fumadocs-core/link'; Click Me ``` ### External You can force an URL to be external by passing an `external` prop. ### Dynamic hrefs Dynamic hrefs are no longer supported in Next.js App Router. You can enable dynamic hrefs by importing `dynamic-link` instead. ```mdx import { DynamicLink } from 'fumadocs-core/dynamic-link'; Click Me ``` # Fumadocs Core (core library of framework): Sidebar URL: /docs/headless/components/sidebar Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/components/sidebar.mdx The navigation bar at aside of viewport A sidebar component which handles device resizing and remove scroll bar automatically. ## Usage ```tsx import * as Base from 'fumadocs-core/sidebar'; return ( ); ``` ### Sidebar Provider ### Sidebar Trigger Opens the sidebar on click. ### Sidebar List | Data Attribute | Values | Description | | -------------- | ------------- | ------------------ | | `data-open` | `true, false` | Is sidebar opening | # Fumadocs Core (core library of framework): TOC URL: /docs/headless/components/toc Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/components/toc.mdx Table of Content A Table of Contents with active anchor observer and auto scroll. ## Usage ```tsx import * as Base from 'fumadocs-core/toc'; return ( ); ``` ### Anchor Provider Watch for the active anchor using the Intersection API. ### Scroll Provider Scrolls (the given scroll container) to the active anchor. ### TOC Item The anchor item to jump to the anchor. | Data Attribute | Values | Description | | -------------- | ------------- | ---------------- | | `data-active` | `true, false` | Is anchor active | ## Example ```tsx import { AnchorProvider, ScrollProvider, TOCItem } from 'fumadocs-core/toc'; import { type ReactNode, useRef } from 'react'; import type { TOCItemType } from 'fumadocs-core/server'; export function Page({ items, children, }: { items: TOCItemType[]; children: ReactNode; }) { const viewRef = useRef(null); return (
{items.map((item) => ( {item.title} ))}
{children}
); } ``` # Fumadocs Core (core library of framework): Content Collections URL: /docs/headless/content-collections Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/content-collections/index.mdx Use Content Collections for Fumadocs [Content Collections](https://www.content-collections.dev) is a library that transforms your content into type-safe data collections. ## Setup Install the required packages. ```bash npm install @fumadocs/content-collections @content-collections/core @content-collections/mdx @content-collections/next ``` ```bash pnpm add @fumadocs/content-collections @content-collections/core @content-collections/mdx @content-collections/next ``` ```bash yarn add @fumadocs/content-collections @content-collections/core @content-collections/mdx @content-collections/next ``` ```bash bun add @fumadocs/content-collections @content-collections/core @content-collections/mdx @content-collections/next ``` After the installation, add a path alias for the generated collections to the `tsconfig.json`. ```json { "compilerOptions": { "paths": { "@/*": ["./*"], "content-collections": ["./.content-collections/generated"] } } } ``` In the Next.js configuration file, apply the plugin. ```js title="next.config.mjs" import { withContentCollections } from '@content-collections/next'; /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, }; export default withContentCollections(config); ``` To integrate with Fumadocs, add the following to your `content-collections.ts`. ```ts title="content-collections.ts" import { defineCollection, defineConfig } from '@content-collections/core'; import { createMetaSchema, createDocSchema, transformMDX, } from '@fumadocs/content-collections/configuration'; const docs = defineCollection({ name: 'docs', directory: 'content/docs', include: '**/*.mdx', schema: createDocSchema, transform: transformMDX, }); const metas = defineCollection({ name: 'meta', directory: 'content/docs', include: '**/meta.json', parser: 'json', schema: createMetaSchema, }); export default defineConfig({ collections: [docs, metas], }); ``` And pass it to Source API. ```ts title="lib/source.ts" import { allDocs, allMetas } from 'content-collections'; import { loader } from 'fumadocs-core/source'; import { createMDXSource } from '@fumadocs/content-collections'; export const source = loader({ baseUrl: '/docs', source: createMDXSource(allDocs, allMetas), }); ``` Done! You can access the pages and generated page tree from Source API. ```ts import { getPage } from '@/lib/source'; const page = getPage(slugs); // MDX output page?.data.body; // Table of contents page?.data.toc; // Structured Data, for Search API page?.data.structuredData; ``` ### MDX Options You can customise MDX options in the `transformMDX` function. ```ts import { defineCollection } from '@content-collections/core'; import { transformMDX } from '@fumadocs/content-collections/configuration'; const docs = defineCollection({ transform: (document, context) => transformMDX(document, context, { // options here }), }); ``` ### Import Components To use components from other packages like Fumadocs UI, pass them to your `` component. ```tsx import { MDXContent } from '@content-collections/mdx/react'; import { getMDXComponents } from '@/mdx-components'; ; ``` You can also import them in MDX Files, but it is not recommended. Content Collections uses `mdx-bundler` to bundle MDX files. To support importing a package from node modules, Fumadocs added a default value to the `cwd` option of MDX Bundler. It works good, but we still **do not** recommend to import components in MDX files. Reasons: * It requires esbuild to bundle these components, while it should be done by the Next.js bundler (for features of Server Components) * You can refactor the import path of components without changing your MDX files. * With Remote Sources, it doesn't make sense to add an import in MDX files. # Fumadocs Core (core library of framework): Headings URL: /docs/headless/mdx/headings Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/headings.mdx Process headings from your document ## Remark Heading Apply ids to headings. ```ts title="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkHeading } from 'fumadocs-core/mdx-plugins'; await compile('...', { remarkPlugins: [remarkHeading], }); ``` > This plugin is included by default on Fumadocs MDX. ### Extract TOC By default, it extracts the headings (table of contents) of a document to `vfile.data.toc`. You can disable it with: ```ts import { remarkHeading } from 'fumadocs-core/mdx-plugins'; export default { remarkPlugins: [[remarkHeading, { generateToc: false }]], }; ``` ### Custom Ids \[#custom-heading-id] You can customise the heading id with `[#slug]`. ```md # heading [#slug] ``` ### Output An array of `TOCItemType`. ## Rehype TOC Export table of contents (an array of `TOCItemType`), it allows JSX nodes which is not possible with a Remark plugin. > It requires MDX.js. ### Usage ```ts import { rehypeToc } from 'fumadocs-core/mdx-plugins'; export default { rehypePlugins: [rehypeToc], }; ``` ### Output For a Markdown document: ```md ## Hello `code` ``` An export will be created: ```jsx export const toc = [ { title: ( <> Hello code ), depth: 2, url: '#hello-code', }, ]; ``` # Fumadocs Core (core library of framework): MDX Plugins URL: /docs/headless/mdx Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/index.mdx Useful remark & rehype plugins for your docs. # Fumadocs Core (core library of framework): Package Install URL: /docs/headless/mdx/install Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/install.mdx Generate code blocks for installing packages ## Usage ```bash npm install fumadocs-docgen ``` ```bash pnpm add fumadocs-docgen ``` ```bash yarn add fumadocs-docgen ``` ```bash bun add fumadocs-docgen ``` Add the remark plugin. ```ts title="source.config.ts" tab="Fumadocs MDX" import { remarkInstall } from 'fumadocs-docgen'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkInstall], }, }); ``` ```ts tab="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkInstall } from 'fumadocs-docgen'; await compile('...', { remarkPlugins: [remarkInstall], }); ``` Define the required components. ```tsx title="mdx-components.tsx (Fumadocs UI)" import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, Tab, Tabs, ...components, }; } ``` | Component | | | --------- | --------------------------------- | | Tabs | Accept an array of item (`items`) | | Tab | Accept the name of item (`value`) | Create code blocks with `package-install` as language. ````mdx ```package-install my-package ``` ```package-install npm i my-package -D ``` ```` ### Output The following structure should be generated by the plugin. ```mdx ... ... ... ... ``` ```bash npm install my-package ``` ```bash pnpm add my-package ``` ```bash yarn add my-package ``` ```bash bun add my-package ``` ## Options ### Persistent When using with Fumadocs UI, you can enable persistent with the `persist` option. ```ts title="source.config.ts" tab="Fumadocs MDX" import { remarkInstall } from 'fumadocs-docgen'; import { defineConfig } from 'fumadocs-mdx/config'; const remarkInstallOptions = { persist: { id: 'some-id', }, }; export default defineConfig({ mdxOptions: { remarkPlugins: [[remarkInstall, remarkInstallOptions]], }, }); ``` ```ts tab="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkInstall } from 'fumadocs-docgen'; const remarkInstallOptions = { persist: { id: 'some-id', }, }; await compile('...', { remarkPlugins: [[remarkInstall, remarkInstallOptions]], }); ``` This will instead generate: ```mdx ... ``` # Fumadocs Core (core library of framework): Rehype Code URL: /docs/headless/mdx/rehype-code Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/rehype-code.mdx Code syntax highlighter A wrapper of [Shiki](https://shiki.style), the built-in syntax highlighter. ## Usage Add the rehype plugin. ```ts title="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { rehypeCode } from 'fumadocs-core/mdx-plugins'; await compile('...', { rehypePlugins: [rehypeCode], }); ``` > This plugin is included by default on Fumadocs MDX. ### Output A codeblock wrapped in `
`.

```html
...
``` ### Meta It parses the `title` meta string, and add it to the `pre` element via attribute. ````mdx ```js title="Title" console.log('Hello'); ``` ```` You may filter the meta string before processing it with the `filterMetaString` option. ### Inline Code `console.log("hello world"){:js}` works. See [https://shiki.style/packages/rehype#inline-code](https://shiki.style/packages/rehype#inline-code). ### Icon Add an icon according to the language of codeblock. It outputs HTML, you might need to render it with React `dangerouslySetInnerHTML`. ```jsx
...
``` Disable or customise icons with the `icon` option. ### More Options see [Shiki](https://shiki.style). # Fumadocs Core (core library of framework): Remark Admonition URL: /docs/headless/mdx/remark-admonition Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/remark-admonition.mdx Use Admonition in Fumadocs In Docusaurus, there's an [Admonition syntax](https://docusaurus.io/docs/markdown-features/admonitions). For people migrating from Docusaurus, you can enable this remark plugin to support the Admonition syntax. ## Usage ```ts title="source.config.ts" tab="Fumadocs MDX" import { remarkAdmonition } from 'fumadocs-core/mdx-plugins'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkAdmonition], }, }); ``` ```ts tab="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkAdmonition } from 'fumadocs-core/mdx-plugins'; await compile('...', { remarkPlugins: [remarkAdmonition], }); ``` ### Input ```md :::warning Hello World ::: ``` ### Output ```mdx Hello World ``` ### When to use We highly recommend to use the JSX syntax of MDX instead. It's more flexible, some editors support intellisense in MDX files. ```mdx Hello World ``` # Fumadocs Core (core library of framework): Remark Image URL: /docs/headless/mdx/remark-image Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/remark-image.mdx Make images compatible with Next.js Image Optimization ## Usage Add it to your Remark plugins. ```ts title="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkImage } from 'fumadocs-core/mdx-plugins'; await compile('...', { remarkPlugins: [remarkImage], }); ``` > This plugin is included by default on Fumadocs MDX. Supported: * Local Images * External URLs * Next.js static imports ### How It Works It transforms your `![image](/test.png)` into Next.js Image usage, and add required props like `width` and `height`. By default, it uses **static imports** to import local images, which supports the `placeholder` option of Next.js Image. Next.js can handle image imports with its built-in image loader. Otherwise, it uses the file system or an HTTP request to download the image and obtain its size. ### Options ### Example: With Imports ```mdx ![Hello](/hello.png) ![Test](https://example.com/image.png) ``` Yields: ```mdx import HelloImage from './public/hello.png'; Hello Test ``` Where `./public/hello.png` points to the image in public directory. ### Example: Without Imports You can disable Next.js static imports on local images. ```ts import { remarkImage } from 'fumadocs-core/mdx-plugins'; export default { remarkPlugins: [[remarkImage, { useImport: false }]], }; ``` ```mdx ![Hello](/hello.png) ![Test](https://example.com/image.png) ``` Yields: ```mdx Hello Test ``` ### Example: Relative Paths When `useImport` is enabled, you can reference local images using relative paths. ```mdx ![Hello](./hello.png) ``` Be careful that using it with `useImport` disabled **doesn't work**. Next.js will not add the image to public assets unless you have imported it in code. For images in public directory, you can just reference them without relative paths. ### Example: Public Directory Customise the path of public directory ```ts import { remarkImage } from 'fumadocs-core/mdx-plugins'; import path from 'node:path'; export default { remarkPlugins: [ remarkImage, { publicDir: path.join(process.cwd(), 'dir'), }, ], }; ``` You can pass a URL too. ```ts import { remarkImage } from 'fumadocs-core/mdx-plugins'; export default { remarkPlugins: [ remarkImage, { publicDir: 'http://localhost:3000/images', }, ], }; ``` # Fumadocs Core (core library of framework): Remark TS to JS URL: /docs/headless/mdx/remark-ts2js Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/remark-ts2js.mdx A remark plugin to transform TypeScript codeblocks into two tabs of codeblock with its JavaScript variant. ## Usage Install dependencies: ```bash npm install fumadocs-docgen oxc-transform ``` ```bash pnpm add fumadocs-docgen oxc-transform ``` ```bash yarn add fumadocs-docgen oxc-transform ``` ```bash bun add fumadocs-docgen oxc-transform ``` Add `oxc-transform` to `serverExternalPackages` in `next.config.mjs`: ```js title="next.config.mjs" import { createMDX } from 'fumadocs-mdx/next'; /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, serverExternalPackages: ['oxc-transform'], }; const withMDX = createMDX(); export default withMDX(config); ``` Add the remark plugin: ```ts title="source.config.ts" tab="Fumadocs MDX" import { remarkTypeScriptToJavaScript } from 'fumadocs-docgen/remark-ts2js'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkTypeScriptToJavaScript], }, }); ``` ```ts tab="MDX Compiler" import { remarkTypeScriptToJavaScript } from 'fumadocs-docgen/remark-ts2js'; import { compile } from '@mdx-js/mdx'; await compile('...', { remarkPlugins: [remarkTypeScriptToJavaScript], }); ``` Finally, make sure to define the required MDX components: `Tabs` and `Tab`. ```tsx title="mdx-components.tsx (Fumadocs UI)" import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, Tab, Tabs, ...components, }; } ``` You can now enable it on TypeScript/TSX codeblocks, like: ````md ```tsx ts2js import { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return
{children}
; } ``` ```` ```tsx ts2js import { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return
{children}
; } ``` # Fumadocs Core (core library of framework): Remark Structure URL: /docs/headless/mdx/structure Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/structure.mdx Extract information from your documents, useful for implementing document search ## Usage Add it as a remark plugin. ```ts title="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkStructure } from 'fumadocs-core/mdx-plugins'; const vfile = await compile('...', { remarkPlugins: [remarkStructure], }); ``` > This plugin is included by default on Fumadocs MDX. Extracted information could be found in `vfile.data.structuredData`, you may write your own plugin to convert it into a MDX export. ### Options ### Output A list of headings and contents. Paragraphs will be extracted to the `contents` array, each item contains a `heading` prop indicating the heading of paragraph. A heading can have multiple paragraphs. #### Heading | Prop | | | --------- | ------------------------------------ | | `id` | unique identifier or slug of heading | | `content` | Text content | #### Content | Prop | | | --------- | ------------------------------- | | `heading` | Heading of paragraph (nullable) | | `content` | Text content | ## As a Function Accepts MDX/markdown content and return structurized data. ```ts import { structure } from 'fumadocs-core/mdx-plugins'; structure(page.body.raw); ``` If you have custom remark plugins enabled, such as `remark-math`, you have to pass these plugins to the function. This avoids unreadable content on paragraphs. ```ts import { structure } from 'fumadocs-core/mdx-plugins'; import remarkMath from 'remark-math'; structure(page.body.raw, [remarkMath]); ``` ### Parameters | Parameter | | | --------------- | ---------------------- | | `content` | MDX/markdown content | | `remarkPlugins` | List of remark plugins | | `options` | Custom options | # Fumadocs Core (core library of framework): Algolia Search URL: /docs/headless/search/algolia Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/algolia.mdx Integrate Algolia Search with Fumadocs If you're using Algolia's free tier, you have to [display their logo on your search dialog](https://algolia.com/policies/free-services-terms). ## Introduction The Algolia Integration automatically configures Algolia Search for document search. It creates a record for **each paragraph** in your document, it is also recommended by Algolia. Each record contains searchable attributes: | Attribute | Description | | --------- | --------------------- | | `title` | Page Title | | `section` | Heading ID (nullable) | | `content` | Paragraph content | The `section` field only exists in paragraphs under a heading. Headings and paragraphs are indexed as an individual record, grouped by their page ID. Notice that it expects the `url` property of a page to be unique, you shouldn't have two pages with the same url. ## Setup ### Install Dependencies ```bash npm install algoliasearch ``` ```bash pnpm add algoliasearch ``` ```bash yarn add algoliasearch ``` ```bash bun add algoliasearch ``` ### Sign up on Algolia Sign up and obtain the app id and API keys for your search. Store these credentials in environment variables. ### Sync Search Indexes Export the search indexes from Next.js using a route handler, this way we can access the search indexes after production build: ```ts title="app/static.json/route.ts" import { NextResponse } from 'next/server'; import { type DocumentRecord } from 'fumadocs-core/search/algolia'; import { source } from '@/lib/source'; export const revalidate = false; export function GET() { const results: DocumentRecord[] = []; for (const page of source.getPages()) { results.push({ _id: page.url, structured: page.data.structuredData, url: page.url, title: page.data.title, description: page.data.description, }); } return NextResponse.json(results); } ``` Make a script to sync search indexes: ```js title="update-index.mjs" import algosearch from 'algoliasearch'; import { sync } from 'fumadocs-core/search/algolia'; import * as fs from 'node:fs'; const content = fs.readFileSync('.next/server/app/static.json.body'); /** @type {import('fumadocs-core/search/algolia').DocumentRecord[]} **/ const indexes = JSON.parse(content.toString()); const client = algosearch('id', 'key'); sync(client, { documents: indexes, // search indexes, can be provided by your content source too [!code highlight] }); ``` The `sync` function will update the index settings and sync search indexes. Now run the script after build: ```json title="package.json" { "scripts": { "build": "next build && node ./update-index.mjs" } } ``` ### Workflow You may make it a script and manually sync with `node ./update-index.mjs`, or integrate it with your CI/CD pipeline. If you are running the script with [TSX](https://github.com/privatenumber/tsx) or other similar Typescript executors, ensure to name it `.mts` for best ESM compatibility. ### Search UI You can consider different options for implementing the UI: * [Fumadocs UI Usage](/docs/ui/search#algolia) * Build your own using the built-in search client hook: ```ts import algosearch from 'algoliasearch'; import { useDocsSearch } from 'fumadocs-core/search/client'; const index = algosearch('id', 'key').initIndex('document'); const { search, setSearch, query } = useDocsSearch({ type: 'algolia', index, distinct: 5, hitsPerPage: 10, }); ``` ## Options ### Tag Filter To configure tag filtering, add a `tag` value to indexes. ```js import algosearch from 'algoliasearch'; import { sync } from 'fumadocs-core/search/algolia'; const client = algosearch('id', 'key'); sync(client, { documents: indexes.map((index) => ({ ...index, tag: 'value', // [!code highlight] })), }); ``` And update your search client: * **Fumadocs UI**: Enable [Tag Filter](/docs/ui/search#tag-filter-1) on Search Dialog. * **Search Client**: You can add the tag filter like: ```ts import algosearch from 'algoliasearch'; import { useDocsSearch } from 'fumadocs-core/search/client'; const index = algosearch('id', 'key').initIndex('document'); const { search, setSearch, query } = useDocsSearch( { type: 'algolia', index, }, undefined, '', ); ``` The `tag` field is an attribute for faceting. You can also use the filter `tag:value` on Algolia search clients. ### Customise Attributes & Settings While the default attributes might not suit your case, you can pass `extra_data` to index options for adding extra fields to each record. ```js import { sync } from 'fumadocs-core/search/algolia'; sync(client, { documents: indexes.map((docs) => ({ ...docs, extra_data: { value: 'hello world', }, })), }); ``` To customize the default index settings, set index settings, and update documents with `updateDocuments(...)` separately. # Fumadocs Core (core library of framework): Search URL: /docs/headless/search Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/index.mdx Configure Search in Fumadocs # Fumadocs Core (core library of framework): Orama Cloud URL: /docs/headless/search/orama-cloud Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/orama-cloud.mdx Integrate with Orama Cloud To begin, create an account on Orama Cloud. ## REST API REST API integration requires your docs to upload the indexes. 1. Create a new REST API index from Dashboard. 2. Use the following schema: ```json { "id": "string", "title": "string", "url": "string", "tag": "string", "page_id": "string", "section": "string", "section_id": "string", "content": "string" } ``` 3. Then, using the private API key and index ID from dashboard, create a script to sync search indexes. ```js title="sync-index.mjs" import { sync } from 'fumadocs-core/search/orama-cloud'; import * as fs from 'node:fs/promises'; import { CloudManager } from '@oramacloud/client'; export async function updateSearchIndexes() { const apiKey = process.env.ORAMA_PRIVATE_API_KEY; // private API key [!code highlight] if (!apiKey) { console.log('no api key for Orama found, skipping'); return; } const content = await fs.readFile('.next/server/app/static.json.body'); const records = JSON.parse(content.toString()); const manager = new CloudManager({ api_key: apiKey }); await sync(manager, { index: '', documents: records, }); console.log(`search updated: ${records.length} records`); } void updateSearchIndexes(); ``` 4. Create a route handler in your Next.js app to export search indexes. ```ts title="app/static.json/route.ts" import { NextResponse } from 'next/server'; import { type OramaDocument } from 'fumadocs-core/search/orama-cloud'; import { source } from '@/lib/source'; export const revalidate = false; export function GET() { const results: OramaDocument[] = []; for (const page of source.getPages()) { results.push({ id: page.url, structured: page.data.structuredData, url: page.url, title: page.data.title, description: page.data.description, }); } return NextResponse.json(results); } ``` 5. Run the script after `next build`. ### Search Client To search documents on the client side, use [Fumadocs UI Search Dialog](/docs/ui/search#orama-cloud), or make your own implementation. In addition, the headless search client of Fumadocs can handle state management for React. ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; import { OramaClient } from '@oramacloud/client'; const client = new OramaClient(); const { search, setSearch, query } = useDocsSearch({ type: 'orama-cloud', client, params: { // search params }, }); ``` ## Web Crawler 1. Create a Crawler index from dashboard, and configure it correctly with the "Documentation" preset. 2. Copy the public API key and index ID from dashboard ### Search Client Same as REST API integration, but make sure to set `index` to `crawler`. ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; import { OramaClient } from '@oramacloud/client'; const client = new OramaClient({ endpoint: '', api_key: '', }); const { search, setSearch, query } = useDocsSearch({ type: 'orama-cloud', index: 'crawler', client, params: { // optional search params }, }); ``` It's same for Fumadocs UI: ```tsx 'use client'; import { OramaClient } from '@oramacloud/client'; import type { SharedProps } from 'fumadocs-ui/components/dialog/search'; import SearchDialog from 'fumadocs-ui/components/dialog/search-orama'; const client = new OramaClient({ endpoint: '', api_key: '', }); export default function CustomSearchDialog(props: SharedProps) { return ; } ``` # Fumadocs Core (core library of framework): Built-in Search URL: /docs/headless/search/orama Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/orama.mdx Built-in document search of Fumadocs Fumadocs supports searching document based on Orama. As the built-in search of Fumadocs, It is the default but also recommended option since it's easier to setup and totally free. ## Search Server You can create the search route handler from the source object, or search indexes. ### From Source Create a route handler from source object. ```ts title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; export const { GET } = createFromSource(source); ``` ### From Search Indexes Pass search indexes to the function. Each index needs a `structuredData` field. Usually, it is provided by your content source (e.g. Fumadocs MDX). You can also extract it from Markdown/MDX document using the [Remark Structure](/docs/headless/mdx/structure) plugin. ```ts title="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, })), }); ``` Index with the raw content of document (unrecommended). ```ts title="app/api/search/route.ts" import { allDocs } from 'content-collections'; import { createSearchAPI } from 'fumadocs-core/search/server'; export const { GET } = createSearchAPI('simple', { indexes: allDocs.map((docs) => ({ title: docs.title, content: docs.content, // Raw Content url: docs.url, })), }); ``` ### Special Languages If your language is not on the Orama [Supported Languages](https://docs.orama.com/open-source/supported-languages#officially-supported-languages) list, you have to configure them manually: ```ts title="app/api/search/route.ts" tab="With I18n" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; import { createTokenizer } from '@orama/tokenizers/mandarin'; export const { GET } = createFromSource(source, { localeMap: { // you can customise search configs for specific locales, like: // [locale]: Orama options cn: { components: { tokenizer: createTokenizer(), }, search: { threshold: 0, tolerance: 0, }, }, }, }); ``` ```ts title="app/api/search/route.ts" tab="Without I18n" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; import { createTokenizer } from '@orama/tokenizers/mandarin'; // example for Mandarin export const { GET } = createFromSource(source, { components: { tokenizer: createTokenizer(), }, search: { threshold: 0, tolerance: 0, }, }); ``` See [Orama Docs](https://docs.orama.com/open-source/supported-languages/using-chinese-with-orama) for more details. ## Search Client You can search documents using: * **Fumadocs UI**: The built-in [Search UI](/docs/ui/search) supports it out-of-the-box. * **Search Client**: ```ts twoslash import { useDocsSearch } from 'fumadocs-core/search/client'; const client = useDocsSearch({ type: 'fetch', }); ``` ### Tag Filter Support filtering by tag, it's useful for implementing multi-docs similar to this documentation. ```ts title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; export const { GET } = createFromSource(source, { buildIndex(page) { return { title: page.data.title, description: page.data.description, url: page.url, id: page.url, structuredData: page.data.structuredData, // use your desired value, like page.slugs[0] tag: '', }; }, }); ``` and update your search client: * **Fumadocs UI**: Configure [Tag Filter](/docs/ui/search#tag-filter) on Search UI. * **Search Client**: pass a tag to the hook. ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; // Pass `tag` in your custom search dialog const client = useDocsSearch( { type: 'fetch', }, undefined, // locale code, can be `undefined` 'tag', ); ``` ## Internationalization ```ts title="lib/source.ts" tab="createFromSource" import { i18n } from '@/lib/i18n'; import { loader } from 'fumadocs-core/source'; // You only need i18n option on source object. export const source = loader({ i18n, // [!code highlight] }); ``` ```ts title="app/api/search/route.ts" tab="createI18nSearchAPI" import { source } from '@/lib/source'; import { createI18nSearchAPI } from 'fumadocs-core/search/server'; import { i18n } from '@/lib/i18n'; export const { GET } = createI18nSearchAPI('advanced', { i18n, indexes: source.getLanguages().flatMap(({ language, pages }) => pages.map((page) => ({ title: page.data.title, description: page.data.description, structuredData: page.data.structuredData, id: page.url, url: page.url, locale: language, })), ), }); ``` ### Update Search Client You can ignore this, Fumadocs UI handles this when you have i18n configured correctly. Add `locale` to the search client, this will only allow pages with specified locale to be searchable by the user. ```ts const { search, setSearch, query } = useDocsSearch( { type: 'fetch', }, locale, ); ``` ## Static Export To work with Next.js static export, use `staticGET` from search server. ```ts title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; // it should be cached forever export const revalidate = false; export const { staticGET: GET } = createFromSource(source); ``` > `staticGET` is also available on `createSearchAPI`. and update your search clients: * **Fumadocs UI**: See [Static Export](/docs/ui/static-export#built-in-search) guide. * **Search Client**: On your search client, use `static` instead of `fetch`. ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; const client = useDocsSearch({ type: 'static', }); ``` Static Search requires clients to download the exported search indexes. For large docs sites, its size can be really big. Especially with i18n (e.g. Chinese tokenizers), the bundle size of tokenizers can exceed \~500MB. You should use 3rd party solutions like Algolia for these cases. ## Headless You can host the search server on other backend such as Express and Elysia. ```ts import { initAdvancedSearch } from 'fumadocs-core/search/server'; const server = initAdvancedSearch({ // you still have to pass indexes }); server.search('query', { // you can specify `locale` and `tag` here }); ``` # Fumadocs Core (core library of framework): Trieve Search URL: /docs/headless/search/trieve Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/trieve.mdx Integrate Trieve Search with Fumadocs > This is a community maintained integration. ## Introduction The Trieve Integration automatically configures Trieve Search for site search. By default, it creates a chunk for **each paragraph** in your document, it is officially recommended by Trieve. ## Setup ### Install Dependencies ```bash npm install trieve-ts-sdk trieve-fumadocs-adapter ``` ```bash pnpm add trieve-ts-sdk trieve-fumadocs-adapter ``` ```bash yarn add trieve-ts-sdk trieve-fumadocs-adapter ``` ```bash bun add trieve-ts-sdk trieve-fumadocs-adapter ``` ### Sign up on Trieve Sign up and create a dataset. Then obtain 2 API keys where one has only read access and the other has admin access to create and delete chunks. Store these credentials in environment variables. One API Key should have only read access for the public facing search and the other should have admin access to create and delete chunks. ### Sync Dataset You can export the search indexes from Next.js using a route handler: ```ts title="app/static.json/route.ts" import { NextResponse } from 'next/server'; import { source } from '@/lib/source'; import { type TrieveDocument } from 'trieve-fumadocs-adapter/search/sync'; export const revalidate = false; export function GET() { const results: TrieveDocument[] = []; for (const page of source.getPages()) { results.push({ _id: page.url, structured: page.data.structuredData, url: page.url, title: page.data.title, description: page.data.description, }); } return NextResponse.json(results); } ``` Create a script, the `sync` function will sync search indexes. ```js title="update-index.mjs" import * as fs from 'node:fs'; import { sync } from 'trieve-fumadocs-adapter/search/sync'; import { TrieveSDK } from 'trieve-ts-sdk'; const content = fs.readFileSync('.next/server/app/static.json.body'); // now you can pass it to `sync` /** @type {import('trieve-fumadocs-adapter/search/sync').TrieveDocument[]} **/ const records = JSON.parse(content.toString()); const client = new TrieveSDK({ apiKey: 'adminApiKey', datasetId: 'datasetId', }); sync(client, records); ``` Make sure to run the script after build: ```json title="package.json" { "scripts": { "build": "next build && node ./update-index.mjs" } } ``` ### Workflow You may manually sync with `node ./update-index.mjs`, or integrate it with your CI/CD pipeline. You can use Bun or other JavaScript runtimes that supports TypeScript and ESM. ### Search UI On Fumadocs UI, you can use the `SearchDialog` component: ```tsx title="components/search.tsx" 'use client'; import type { SharedProps } from 'fumadocs-ui/components/dialog/search'; import SearchDialog from 'trieve-fumadocs-adapter/components/dialog/search'; import { TrieveSDK } from 'trieve-ts-sdk'; const trieveClient = new TrieveSDK({ apiKey: 'readOnlyApiKey', datasetId: 'datasetId', }); export default function CustomSearchDialog(props: SharedProps) { return ; } ``` 1. Replace `apiKey` and `datasetId` with your desired values. 2. [Replace the default search dialog](/docs/ui/search#replace-search-dialog) with your new component. ### Search Client Add the `useTrieveSearch` hook: ```ts import { TrieveSDK } from 'trieve-ts-sdk'; import { useTrieveSearch } from 'trieve-fumadocs-adapter/search/trieve'; const client = new TrieveSDK({ apiKey: 'readOnlyApiKey', datasetId: 'datasetId', }); const { search, setSearch, query } = useTrieveSearch(client); ``` ## Options ### Tag Filter To configure tag filtering, add a `tag` value to indexes. ```js import { sync } from 'trieve-fumadocs-adapter/search/sync'; import { TrieveSDK } from 'trieve-ts-sdk'; const client = new TrieveSDK({ apiKey: 'adminApiKey', datasetId: 'datasetId', }); const documents = records.map((index) => ({ ...index, tag: 'value', // [!code highlight] })); sync(client, documents); ``` #### Search UI Enable Tag Filter. ```tsx title="components/search.tsx" import SearchDialog from 'trieve-fumadocs-adapter/components/dialog/search'; ; ``` #### Search Client The `tag_set` field is an attribute for filtering. To filter indexes by tag, use the filter on Trieve search clients. ```json { "must": [ { "field": "tag_set", "match": ["value"] } ] } ``` Or with `useTrieveSearch` hook: ```ts import { TrieveSDK } from 'trieve-ts-sdk'; import { useTrieveSearch } from 'trieve-fumadocs-adapter/search/trieve'; const client = new TrieveSDK({ apiKey: 'readOnlyApiKey', datasetId: 'datasetId', }); const { search, setSearch, query } = useTrieveSearch( client, undefined, '', ); ``` # Fumadocs Core (core library of framework): Find Neighbours URL: /docs/headless/utils/find-neighbour Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/utils/find-neighbour.mdx Find the neighbours of a page from the page tree Find the neighbours of a page from the page tree, it returns the next and previous page of a given page. It is useful for implementing a footer. ## Usage It requires a page tree and the url of page. ```ts import { findNeighbour } from 'fumadocs-core/server'; import { pageTree } from '@/lib/source'; const neighbours = findNeighbour(pageTree, '/url/to/page'); ``` | Parameter | Type | Description | | --------- | ---------- | --------------- | | tree | `PageTree` | The page tree | | url | `string` | The url of page | # Fumadocs Core (core library of framework): Get TOC URL: /docs/headless/utils/get-toc Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/utils/get-toc.mdx Parse Table of contents from markdown/mdx content Parse Table of contents from markdown/mdx content. > [You can use the remark plugin directly](/docs/headless/mdx/headings) ## Usage Note: If you're using a CMS, you should use the API provided by the CMS instead. ```ts import { getTableOfContents } from 'fumadocs-core/server'; const toc = getTableOfContents('## markdown content'); ``` ### Output An array of [`TOCItemType`](/docs/headless/mdx/headings#output) is returned. # Fumadocs Core (core library of framework): Last Modified Time URL: /docs/headless/utils/git-last-edit Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/utils/git-last-edit.mdx Get the last edit time of a file in Github repository ## Usage Pass your repository name, and the path to file. ```ts import { getGithubLastEdit } from 'fumadocs-core/server'; const time = await getGithubLastEdit({ owner: 'fuma-nama', repo: 'fumadocs', // example: "content/docs/index.mdx" path: `content/docs/${page.file.path}`, }); ``` ### Github Token Notice that you may easily reach the rate limit in development mode. Hence, you should pass a Github token for a higher rate limit. Learn more about [Authenticating to the REST API](https://docs.github.com/en/rest/overview/authenticating-to-the-rest-api). ```ts import { getGithubLastEdit } from 'fumadocs-core/server' const time = await getGithubLastEdit({ ..., token: `Bearer ${process.env.GIT_TOKEN}` }) ``` Also, you can skip this in development mode if you don't need that functionality. ```ts process.env.NODE_ENV === 'development'? null : getGithubLastEdit(...) ``` # Fumadocs Core (core library of framework): Utilities URL: /docs/headless/utils Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/utils/index.mdx Utilities to provide extra functionality to your docs # Fumadocs Framework: Feedback URL: /docs/ui/feedback Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/feedback.mdx Receive feedback from your users ## Overview Feedback is crucial for knowing what your reader thinks, and help you to further improve documentation content. ## Installation Add dependencies: ```bash npm install class-variance-authority lucide-react ``` ```bash pnpm add class-variance-authority lucide-react ``` ```bash yarn add class-variance-authority lucide-react ``` ```bash bun add class-variance-authority lucide-react ``` Copy the component: ```tsx title="components/rate.tsx" 'use client'; import { cn } from '@/lib/cn'; import { buttonVariants } from 'fumadocs-ui/components/ui/button'; import { ThumbsDown, ThumbsUp } from 'lucide-react'; import { type SyntheticEvent, useEffect, useState } from 'react'; import { Collapsible, CollapsibleContent, } from 'fumadocs-ui/components/ui/collapsible'; import { cva } from 'class-variance-authority'; import { usePathname } from 'next/navigation'; const rateButtonVariants = cva( 'inline-flex items-center gap-2 px-3 py-2 rounded-full font-medium border text-sm [&_svg]:size-4 disabled:cursor-not-allowed', { variants: { active: { true: 'bg-fd-accent text-fd-accent-foreground [&_svg]:fill-current', false: 'text-fd-muted-foreground', }, }, }, ); export interface Feedback { opinion: 'good' | 'bad'; message: string; } function get(url: string): Feedback | null { const item = localStorage.getItem(`docs-feedback-${url}`); if (item === null) return null; return JSON.parse(item) as Feedback; } function set(url: string, feedback: Feedback | null) { const key = `docs-feedback-${url}`; if (feedback) localStorage.setItem(key, JSON.stringify(feedback)); else localStorage.removeItem(key); } export function Rate({ onRateAction, }: { onRateAction: (url: string, feedback: Feedback) => Promise; }) { const url = usePathname(); const [previous, setPrevious] = useState(null); const [opinion, setOpinion] = useState<'good' | 'bad' | null>(null); const [message, setMessage] = useState(''); useEffect(() => { setPrevious(get(url)); }, [url]); function submit(e?: SyntheticEvent) { e?.preventDefault(); if (opinion == null) return; const feedback: Feedback = { opinion, message, }; void onRateAction(url, feedback); set(url, feedback); setPrevious(feedback); setMessage(''); setOpinion(null); } return ( { if (!v) setOpinion(null); }} className="border-y py-3" >

How is this guide?

{previous ? (

Thank you for your feedback!

) : (