# 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 *** title: Custom Source description: Build your own content source ------------------------------------------ ## Introduction **Fumadocs is very flexible.** You can integrate with any content source, even without an official adapter. ### 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) * [Payload CMS](https://github.com/MFarabi619/fumadocs-payloadcms) 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 For docs page, it's the same logic: * Define path params (`slugs`). * Fetch page content from path params. * Render the content. * (Optional) Configure pre-rendering for your framework. #### 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()} ); } ``` #### Pre-rendering (Next.js only) 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. ### 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 Orama or 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 npm pnpm yarn bun ```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 serverless platforms like Vercel, the original `public` folder (including static assets like images) will be removed after production 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 *** title: Introduction description: Getting started with core library icon: Album ----------- ## 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. npm pnpm yarn bun ```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}; } ``` ```tsx tab="Waku" import type { ReactNode } from 'react'; import { WakuProvider } from 'fumadocs-core/framework/waku'; 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 *** title: Internationalization description: Support multiple languages in your documentation ------------------------------------------------------------- ## Define Config 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 { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['en', 'cn'], }); ``` ### Hide Locale Prefix To hide the locale prefix (e.g. `/en/page` -> `/page`), use the `hideLocale` option. ```ts import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ 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. ### Fallback Language The fallback language to use when translations are missing for a page, default to your `defaultLanguage`. Supported: * A language in your `languages` array. * When set to `null`, no fallback will be used. ```ts import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ languages: ['en', 'cn'], defaultLanguage: 'en', fallbackLanguage: 'cn', }); ``` ## Middleware Redirects users to appropriate locale, it can be customised from `i18n.ts`. ```ts title="middleware.ts" import { createI18nMiddleware } from 'fumadocs-core/i18n/middleware'; import { i18n } from '@/lib/i18n'; export default createI18nMiddleware(i18n); export const config = { // Matcher ignoring `/_next/` and `/api/` // You may need to adjust it to ignore static assets in `/public` folder 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): Page Slugs & Page Tree URL: /docs/headless/page-conventions Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/page-conventions.mdx Customise generated page slugs and page tree. *** title: Page Slugs & Page Tree description: Customise generated page slugs and page tree. ---------------------------------------------------------- > This guide only applies for content sources that uses `loader()` API, such as **Fumadocs MDX**. ## Overview Fumadocs generates **page slugs** and **page tree** (sidebar items) from your content directory using [`loader()`](/docs/headless/source-api), the routing functionality will be handled by your React framework. You can define folders and pages similar to file-system based routing. ## File For [MDX](https://mdxjs.com) & Markdown file, you can customise page information from frontmatter. ```mdx --- title: My Page description: Best document ever icon: HomeIcon --- ## Learn More ``` Fumadocs detects from the following properties to construct page trees. | name | description | | ------------- | ------------------------------------- | | `title` | The title of page | | `description` | The description of page | | `icon` | The name of icon, see [Icons](#icons) | Page information is supplied by the content source such as **Fumadocs MDX**. On Fumadocs MDX, you can specify a [`schema`](/docs/mdx/collections#schema-1) option to customise frontmatter schema. ### 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']` | ### Root Folder Marks the folder as a root folder, only items in the opened root folder will be visible. ```json title="meta.json" { "title": "Name of Folder", "description": "The description of root folder (optional)", "root": true } ``` For example, when you are opening 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. ## 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 Folder items are sorted alphabetically by default, you can add or control the order of items using `pages`. ```json title="meta.json" { "pages": ["index", "getting-started"] } ``` > When specified, items are not included unless they are listed in `pages`. * **Path**: a path to page or folder, like`./nested/page`. * **Separator** (`---Label---`): a separator between two sections, use `---[Icon]Label---` to include icons. * **Link** (`[Text](url)`): insert links, use `[Icon][Text](url)` to include icons. Special Items: * **Rest** (`...`): include remaining pages (sorted alphabetically). * **Reversed Rest** (`z...a`): reversed **Rest** item. * **Extract** (`...folder`): extract the items from a folder. * **Except** (`!item`): Exclude an item from `...` or `z...a`. ```json title="meta.json" { "pages": [ "components", "---My Separator---", "...folder", "...", "!file", "!otherFolder", "[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()`. ## i18n Routing You can define different style for i18n routing. ```ts title="lib/i18n.ts" import type { I18nConfig } from 'fumadocs-core/i18n'; export const i18n: I18nConfig = { // default parser: 'dot', // or parser: 'dir', }; ``` Add Markdown/meta files for different languages by attending `.{locale}` to your file name, like: For the default locale, the locale code is optional. All content files are grouped by language folders: # 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. *** title: Page Tree description: 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 *** title: loader() description: 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 slugs and page tree](/docs/headless/page-conventions). * Assign URL 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. If translations are missing for a page, it fallbacks to [`fallbackLanguage`](/docs/headless/internationalization#fallback-language). ## 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: { transformers: [ { file(node, file) { // you can access its file information if (file) console.log(file, this.storage.read(file)); // JSX nodes are allowed node.name = <>Some JSX Nodes here; return node; }, }, ], }, }); ``` ### Custom Source To plug your own content source, create a `Source` object. Since Source API doesn't rely on file system, file paths only allow special paths like `file.mdx` and `content/file.mdx`. Paths like `./file.mdx` and `D://content/file.mdx` are not allowed ```ts twoslash 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: [ { type: 'page', path: 'folder/index.mdx', data: { title: 'Hello World', // ... }, }, { type: 'meta', path: 'meta.json', data: { title: 'Docs', pages: ['folder'], // ... }, }, ], }; } ``` # 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 installs components. *** title: User Guide description: The CLI tool that automates setups and installs components. ------------------------------------------------------------------------ ## Installation Initialize a config for CLI: npm pnpm yarn bun ```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. npm pnpm yarn bun ```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. npm pnpm yarn bun ```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 a 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. npm pnpm yarn bun ```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. npm pnpm yarn bun ```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 files too: npm pnpm yarn bun ```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: npm pnpm yarn bun ```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 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. *** title: Async Mode description: Runtime compilation of content files. -------------------------------------------------- ## Introduction By default, all Markdown and MDX files need to be pre-compiled first. The same constraint also applies to the development server. This may result in longer dev server start times for large docs sites. You can enable Async Mode on docs collections to improve this. ## Setup Enable Async Mode. ```ts tab="Docs Collection" import { defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', docs: { // [!code ++] async: true, }, }); ``` ```ts tab="Doc Collection" import { defineCollections } from 'fumadocs-mdx/config'; export const doc = defineCollections({ type: 'doc', dir: 'content/docs', // [!code ++] async: true, }); ``` ### Next.js Turbopack doesn't support lazy bundling at the moment, Async Mode works by performing on-demand compilation with MDX Remote. You need to install extra dependencies: npm pnpm yarn bun ```bash npm i @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 ``` Async Mode comes with some limitations on MDX features for Next.js. * **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 the `public` folder and reference them with URLs. ### Vite Vite has native support for lazy bundling, you only need to update `loader()` after enabling it. ```tsx title="lib/source.ts" import { loader } from 'fumadocs-core/source'; import { docs, create } from '@/.source'; export const source = loader({ baseUrl: '/docs', // [!code --] source: await create.sourceAsync(docs.doc, docs.meta), // [!code ++] source: await create.sourceLazy(docs.doc, docs.meta), }); ``` ## Usage Frontmatter properties are still available on `page` object, but you need to invoke the `load()` async function to load the compiled content (and its exports). For example: ```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 third-party services to implement search, which usually have better capability to handle massive amount of content to index. # 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 *** title: Collections description: 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 for 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 at build time, hence the output must be serializable. You can also pass a function that 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 at the 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 is 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 *** title: Global Options description: 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 default MDX processor options, it accepts the [extended MDX options](/docs/mdx/mdx#extended). ```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, you can also disable them: ```ts title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { preset: 'minimal', // now it accepts only MDX processor 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. *** title: Include description: Reuse content from other files. -------------------------------------------- ## Usage ### Markdown To display content from another Markdown/MDX file, specify its path relative to the file itself in the `` tag. ```mdx title="page.mdx" ./another.mdx ``` For Markdown files, you don't need to escape for MDX syntax but note that you'll need `rehypeRaw` for HTML comments & content. ### CodeBlock For other types of files, it will become a codeblock: ```mdx title="page.mdx" ./script.ts page.md ``` ### `cwd` Resolve relative paths from cwd instead of the Markdown file: ```mdx ./script.ts ``` ### Section For Markdown files, you can include a section of it: ```mdx title="a.mdx" ## Hello World
This is included
This is not included. ``` ```mdx title="b.mdx" a.mdx#test ``` Or with heading id: ```mdx title="a.mdx" ## Included Section I'm here! ## Not Included Some random text. ``` ```mdx title="b.mdx" a.mdx#included-section ``` # Fumadocs MDX (the built-in content source): Getting Started URL: /docs/mdx Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/index.mdx Introducing Fumadocs MDX, the official content source of Fumadocs. *** title: Getting Started description: Introducing Fumadocs MDX, the official content source of Fumadocs. icon: Album ----------- ## Quick Start Get started with Fumadocs MDX: Using Fumadocs MDX with Next.js. Using Fumadocs MDX with React Router 7 or above. Using Fumadocs MDX with Tanstack Start/Router. ## Introduction Fumadocs MDX is a tool to transform content into type-safe data, similar to Content Collections. It is not a full CMS but rather a content compiler + validator, you can use it to handle blog posts and other contents. ### Defining Collections **Collection** refers to a collection containing a certain type of files. You can define collections in the config file (`source.config.ts`). Fumadocs MDX transforms collections into 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: ### Accessing Collections Collections will be compiled into JavaScript files that your app can access, the output format varies according to the framework you use. [Get started](#quickstart) with your framework to learn more. ## 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 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). # 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 *** title: Last Modified Time description: Output the last modified time of a document -------------------------------------------------------- ## Usage This feature is not enabled by default, you can enable this from the config file. Note 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): Default MDX Config URL: /docs/mdx/mdx Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/mdx.mdx Customise the default configurations for MDX processor. *** title: Default MDX Config description: Customise the default configurations for MDX processor. -------------------------------------------------------------------- ## 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 supports **Extended MDX Options** on top of [`ProcessorOptions`](https://mdxjs.com/packages/mdx/#processoroptions). 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 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` (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 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 *** title: Use as Page description: Use MDX file as a page ----------------------------------- ## Introduction MDX files will be compiled in JS scripts with exports like: * `default`: the main component. * `frontmatter`: frontmatter data. * other properties generated by remark/rehype plugins. Hence, they can also be used as a page, or components that you can import. ### Next.js You can use `page.mdx` instead of `page.tsx` for creating a new page under the app directory. ```mdx title="app/my-page/page.mdx" --- title: My Page --- {/* this will enable Typography styles of Fumadocs UI */} export { withArticle as default } from 'fumadocs-ui/page'; # Hello World This is my page. ``` #### MDX Components It doesn't have MDX components by default, you have to provide them: ```tsx tab="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 tab="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', }, }); ``` ### Other Frameworks Some frameworks like Tanstack Start require explicit declaration of loaders, it's recommended to use them as components in your pages instead. ```tsx title="app/my-page.tsx" import MyPage from '@/content/page.mdx'; import { getMDXComponents } from '@/mdx-components'; export default function Page() { return (
{/* pass MDX components and render it */}
); } ``` # 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 *** title: Performance description: The performance of Fumadocs MDX icon: Rocket ------------ ## 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 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 for your React framework (e.g. 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 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 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? *** title: Comparisons description: How is Fumadocs different from other existing frameworks? icon: GitCompareArrows ---------------------- ## 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, as a side effect, 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 React.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. ### Lower Complexity As Fumadocs is designed to integrate with React frameworks, you may need more knowledge of React.js to get started. In return, Fumadocs have better customizability. For a simple docs, Docusaurus might be a better choice if you don't need any framework-specific functionality. ### 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: Customise UI URL: /docs/ui/customisation Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/customisation.mdx An overview for customising Fumadocs UI *** title: Customise UI description: An overview for customising Fumadocs UI ---------------------------------------------------- ## Overview ### 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. npm pnpm yarn bun ```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: npm pnpm yarn bun ```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: npm pnpm yarn bun ```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 *** title: Quick Start description: Getting Started with Fumadocs icon: Album ----------- ## Introduction Fumadocs (Foo-ma docs) is a **documentation framework**, designed to be fast, flexible, and composes seamlessly into your React framework. 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 natively supports Markdown and MDX (superset of Markdown). **[Bun](https://bun.sh):** A JavaScript runtime, we use it for running scripts. Some basic knowledge of React.js would be useful for further customisations. ## Automatic Installation A minimum version of Node.js 20 required. ```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 built-in template to use: * **React.js framework**: Next.js, Waku, React Router, Tanstack Start. * **Content source**: Fumadocs MDX. 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). npm pnpm yarn bun ```bash npm run dev ``` ```bash pnpm run dev ``` ```bash yarn dev ``` ```bash bun run dev ``` ## FAQ Some common questions you may encounter. Make sure to upgrade Fumadocs when you've encountered any problems or trying out new features: ```bash title="pnpm" pnpm update -i -r --latest ``` Routing is handled by your React framework, you need to change the routing structure first. For example, in Next.js, rename the route (`/docs/*` -> `/info/*`): Or rename from `/docs/*` to `/*` using a route group: Finally, update the base URL of pages in `source.ts`: ```ts title="lib/source.ts" import { loader } from 'fumadocs-core/source'; export const source = loader({ baseUrl: '/info', // to the new value [!code highlight] }); ``` We recommend to use [Sidebar Tabs](/docs/ui/navigation/sidebar#sidebar-tabs). ### For Vite There's some weird pre-bundling problems with Vite: [#3910](https://github.com/vitejs/vite/issues/3910). Make sure to exclude Fumadocs from pre-bundling and add it to `noExternal`: ```ts title="vite.config.ts" import { defineConfig } from 'vite'; // add other Fumadocs deps as needed const FumadocsDeps = ['fumadocs-core', 'fumadocs-ui']; export default defineConfig({ resolve: { noExternal: FumadocsDeps, }, optimizeDeps: { exclude: FumadocsDeps, }, }); ``` ### For Next.js Node.js 23.1 might have problems with Next.js, see [#1021](https://github.com/fuma-nama/fumadocs/issues/1021). Make sure to change your Node.js version. 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](/docs/ui/static-export) on Next.js to get a static build output. 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: ## 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. ### Special Needs # Fumadocs Framework: Page Slugs & Page Tree 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 *** title: Page Slugs & Page Tree description: A shared convention for organizing your documents -------------------------------------------------------------- > This guide only applies for content sources that uses `loader()` API, such as **Fumadocs MDX**. ## Overview Fumadocs generates **page slugs** and **page tree** (sidebar items) from your content directory using [`loader()`](/docs/headless/source-api), the routing functionality will be handled by your React framework. You can define folders and pages similar to file-system based routing. ## File For [MDX](https://mdxjs.com) & Markdown file, you can customise page information from frontmatter. ```mdx --- title: My Page description: Best document ever icon: HomeIcon --- ## Learn More ``` Fumadocs detects from the following properties to construct page trees. \| name | description | \| ------------- | ------------------------------------- | \| `title` | The title of page | \| `description` | The description of page | \| `icon` | The name of icon, see [Icons](#icons) | Page information is supplied by the content source such as **Fumadocs MDX**. On Fumadocs MDX, you can specify a [`schema`](/docs/mdx/collections#schema-1) option to customise frontmatter schema. ### 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']` | ### Root Folder Marks the folder as a root folder, only items in the opened root folder will be visible. ```json title="meta.json" { "title": "Name of Folder", "description": "The description of root folder (optional)", "root": true } ``` For example, when you are opening 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. ## 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 Folder items are sorted alphabetically by default, you can add or control the order of items using `pages`. ```json title="meta.json" { "pages": ["index", "getting-started"] } ``` > When specified, items are not included unless they are listed in `pages`. * **Path**: a path to page or folder, like`./nested/page`. * **Separator** (`---Label---`): a separator between two sections, use `---[Icon]Label---` to include icons. * **Link** (`[Text](url)`): insert links, use `[Icon][Text](url)` to include icons. Special Items: * **Rest** (`...`): include remaining pages (sorted alphabetically). * **Reversed Rest** (`z...a`): reversed **Rest** item. * **Extract** (`...folder`): extract the items from a folder. * **Except** (`!item`): Exclude an item from `...` or `z...a`. ```json title="meta.json" { "pages": [ "components", "---My Separator---", "...folder", "...", "!file", "!otherFolder", "[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()`. ## i18n Routing You can define different style for i18n routing. ```ts title="lib/i18n.ts" import type { I18nConfig } from 'fumadocs-core/i18n'; export const i18n: I18nConfig = { // default parser: 'dot', // or parser: 'dir', }; ``` Add Markdown/meta files for different languages by attending `.{locale}` to your file name, like: For the default locale, the locale code is optional. All content files are grouped by language folders: # Fumadocs Framework: Static Build URL: /docs/ui/static-export Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/static-export.mdx Output static website with Fumadocs. *** title: Static Build description: Output static website with Fumadocs. ------------------------------------------------- ## Setup By default, Fumadocs use a server-first approach which always requires a running server to serve. You can also output a static build by enabling it on your React framework. ## Search ### Built-in Search You will need extra configurations to statically store the search indexes, and search will be computed on browser instead: 1. **Search Client:** [enable static mode](/docs/ui/search/orama#static). 2. **Search Server:** [output static indexes](/docs/headless/search/orama#static-export). ### Cloud Solutions Since the search functionality is powered by remote servers, static export works without configuration. ## Deployment ### Next.js You can enable Next.js static export, it allows 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. ### React Router By configuring pre-rendering on all pages, you can serve the `build/client` directory as a static website. ```ts title="react-router.config.ts" import type { Config } from '@react-router/dev/config'; import { glob } from 'node:fs/promises'; import { createGetUrl, getSlugs } from 'fumadocs-core/source'; const getUrl = createGetUrl('/docs'); export default { ssr: true, // [!code ++:9] async prerender({ getStaticPaths }) { const paths: string[] = [...getStaticPaths()]; for await (const entry of glob('**/*.mdx', { cwd: 'content/docs' })) { paths.push(getUrl(getSlugs(entry))); } return paths; }, } satisfies Config; ``` ### Tanstack Start By configuring pre-rendering, you can serve the statically pre-rendered HTML pages as a static website. ```ts title="vite.config.ts" import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ // ...other plugins tanstackStart({ // Tanstack Router will automatically crawl your pages [!code ++:4] target: 'static', prerender: { enabled: true, }, // if you have any hidden paths that's not visible on UI, you can add them explicitly. [!code highlight:5] pages: [ { path: '/docs/test', }, ], }), ], }); ``` ### Waku Waku can serve your site statically when all pages are configured with `static` render mode. See [Deployment](https://waku.gg/#deployment) for details. # 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 *** title: Themes description: 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) ![Solar](/themes/solar.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. *** title: Versioning description: 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. *** title: What is Fumadocs description: Introducing Fumadocs, a docs framework that you can break. icon: CircleQuestionMark ------------------------ Fumadocs was created because the creator Fuma 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 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 expects you to write code and cooperate with the rest of your software, it shows you how the app works and fully customisable, instead of a configuration file. ### Seamless Integration Fumadocs integrates tightly with your React framework, bringing useful utilities and a good-looking UI. For Next.js developers, you are still using features of App Router, like **Static Site Generation**. There is nothing new for Next.js developers, that everything is familiar to you. ### Composable 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 follow a much more flexible approach inspired by Shadcn UI — [Fumadocs CLI](/docs/cli), allowing you to "fork" a part of Fumadocs UI, and fully customise it. ### Server-first Approach Traditionally, static site generators are **static**. However, Fumadocs introduce a server-first approach powered by React Server Component. With perfect server-client boundary, content becomes dynamic and interactive. You can fetch data from server to display content, or integrate with CMS receiving realtime updates. It is always up-to-date and easy to maintain. ### Minimal 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! ## When to use Fumadocs Fumadocs is designed with flexibility in mind, it is not limited to certain usages. * `fumadocs-core` is a headless UI library for building docs. * `fumadocs-mdx` is a useful library to handle MDX content. 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. In these cases, Fumadocs can help you build the docs easier, with less boilerplate. 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 Most React.js frameworks can already suffice the needs of a blog site. Fumadocs provides additional tooling, including syntax highlighting, document search, and a default theme (Fumadocs UI). It helps you to avoid reinventing the wheels. # 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 the screen *** title: Breadcrumb description: The navigation component at the top of the screen -------------------------------------------------------------- A hook for implementing Breadcrumb in your documentation. It returns breadcrumb items for 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; import { usePathname } from 'next/navigation'; // ---cut--- import { useBreadcrumb } from 'fumadocs-core/breadcrumb'; // obtain `pathname` using the hook provided by your React framework. const pathname = usePathname(); const items = useBreadcrumb(pathname, tree); // ^? ``` ### Example A styled example for Next.js. ```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` prop 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 *** title: Components index: true description: 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 *** title: Link description: A Link component that handles external links --------------------------------------------------------- A component that wraps the Link component of your React framework (e.g. `next/link`) and automatically handles external links in the document.\~ When an external URL is detected, it uses `` instead of the Link Component. The `rel` property is automatically generated. ## Usage Usage is the same as using ``. ```mdx import Link from 'fumadocs-core/link'; Click Me ``` ### External You can force a URL to be external by passing an `external` prop. ### Dynamic hrefs You can enable dynamic hrefs by importing `dynamic-link`. ```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 the side of the viewport *** title: Sidebar description: The navigation bar at the side of the viewport ----------------------------------------------------------- A sidebar component which handles device resizing and removes 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 open | # 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 Contents *** title: TOC description: Table of Contents ------------------------------ A Table of Contents with active anchor observer and auto scroll. ## Usage ```tsx import * as Base from 'fumadocs-core/toc'; return ( ); ``` ### Anchor Provider Watches for the active anchor using the Intersection Observer API. ### Scroll Provider Scrolls the scroll container to the active anchor. ### TOC Item An anchor item for jumping to the target anchor. | Data Attribute | Values | Description | | -------------- | ------------- | -------------------- | | `data-active` | `true, false` | Is the 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 *** title: Content Collections description: 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 Follow their [official guide](https://content-collections.dev/docs) to setup Content Collections for your React framework. Make sure you have MDX configured: npm pnpm yarn bun ```bash npm i @content-collections/mdx ``` ```bash pnpm add @content-collections/mdx ``` ```bash yarn add @content-collections/mdx ``` ```bash bun add @content-collections/mdx ``` 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 { frontmatterSchema, metaSchema, transformMDX, } from '@fumadocs/content-collections/configuration'; const docs = defineCollection({ name: 'docs', directory: 'content/docs', include: '**/*.mdx', schema: frontmatterSchema, transform: transformMDX, }); const metas = defineCollection({ name: 'meta', directory: 'content/docs', include: '**/meta.json', parser: 'json', schema: metaSchema, }); export default defineConfig({ collections: [docs, metas], }); ``` And pass it to `loader()`. ```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'; return ; ``` 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 importing components in MDX files. Reasons: * It requires esbuild to bundle these components, while it should be done by the framework's bundler (e.g. Vite or Turbopack) * 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 *** title: Headings description: Process headings from your document ------------------------------------------------ ## Remark Heading Applies 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 Exports 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. *** title: MDX Plugins index: true description: 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 *** title: Package Install description: Generate code blocks for installing packages --------------------------------------------------------- For Fumadocs MDX, it is now enabled by default. You can use the `remarkNpm` plugin from `fumadocs-core/mdx-plugins` instead. ## Usage npm pnpm yarn bun ```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 | Accepts an array of item (`items`) | | Tab | Accepts 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 ... ... ... ... ``` npm pnpm yarn bun ```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 persistence 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 *** title: Rehype Code description: 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 `
` element.

```html
...
``` ### Meta It parses the `title` meta string, and adds it to the `pre` element as an 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 Adds an icon according to the language of the 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 *** title: Remark Admonition description: 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 using 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 Adding size attributes to images. *** title: Remark Image description: Adding size attributes to images. ---------------------------------------------- This plugin adds `width` and `height` attributes to your image elements, which is needed for Image Optimization on Next.js and some other frameworks. ## 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 For Next.js, 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 For Next.js, you can disable 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: 'https://my-cdn.com/images', }, ], }; ``` # Fumadocs Core (core library of framework): Remark Files URL: /docs/headless/mdx/remark-mdx-files Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/mdx/remark-mdx-files.mdx Generate files from codeblocks. *** title: Remark Files description: Generate files from codeblocks. -------------------------------------------- ## Introduction This plugin takes a codeblock like: ````md ```files project ├── src │ ├── index.js │ └── utils │ └── helper.js ├── package.json ``` ```` and convert into: ```mdx ``` ## Setup Add the remark plugin: ```tsx tab="Fumadocs MDX" title="source.config.ts" import { remarkMdxFiles } from 'fumadocs-core/mdx-plugins'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkMdxFiles], }, }); ``` ```ts tab="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkMdxFiles } from 'fumadocs-core/mdx-plugins'; await compile('...', { remarkPlugins: [remarkMdxFiles], }); ``` And make sure you have defined ``, ``, `` MDX components. # 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. *** title: Remark TS to JS description: A remark plugin to transform TypeScript codeblocks into two tabs of codeblock with its JavaScript variant. ----------------------------------------------------------------------------------------------------------------------- ## Usage Install dependencies: npm pnpm yarn bun ```bash npm i 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 ``` For Next.js, externalize `oxc-transform`: ```js title="next.config.mjs (Next.js)" 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 *** title: Remark Structure description: 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 *** title: Algolia Search description: 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: npm pnpm yarn bun ```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 Pre-render a static route `/static.json` to export search indexes into production build: ```ts title="lib/export-search-indexes.ts" import { source } from '@/lib/source'; import type { DocumentRecord } from 'fumadocs-core/search/algolia'; export async function exportSearchIndexes() { 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 results; } ``` ```ts tab="Next.js" title="app/static.json/route.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const revalidate = false; export async function GET() { return Response.json(await exportSearchIndexes()); } ``` ```ts tab="React Router" title="app/routes/static.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function loader() { return Response.json(await exportSearchIndexes()); } ``` ```ts tab="React Router" title="app/routes.ts" import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code ++] route('static.json', 'routes/static.ts'), ] satisfies RouteConfig; ``` ```ts tab="Tanstack Start" title="src/routes/static[.]json.ts" import { createServerFileRoute } from '@tanstack/react-start/server'; import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const ServerRoute = createServerFileRoute('/static.json').methods({ async GET() { return Response.json(await exportSearchIndexes()); }, }); ``` ```ts tab="Tanstack Start" title="vite.config.ts" import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ // ... tanstackStart({ prerender: { enabled: true, }, // [!code ++] pre-render the static file pages: [{ path: '/static.json' }], }), ], }); ``` ```ts tab="Waku" title="pages/api/static.json.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function GET() { return Response.json(await exportSearchIndexes()); } export const getConfig = () => ({ render: 'static', }); ``` Make a script to sync search indexes: ```ts title="scripts/sync-content.ts" twoslash import { algoliasearch } from 'algoliasearch'; import { sync, DocumentRecord } from 'fumadocs-core/search/algolia'; import * as fs from 'node:fs'; // the path of pre-rendered `static.json`, choose one according to your React framework const filePath = { next: '.next/server/app/static.json.body', 'tanstack-start': '.output/public/static.json', 'react-router': 'build/client/static.json', waku: 'dist/public/static.json', }['next']; const content = fs.readFileSync(filePath); const records = JSON.parse(content.toString()) as DocumentRecord[]; const client = algoliasearch('id', 'key'); // update the index settings and sync search indexes void sync(client, { indexName: 'document', documents: records, }); ``` Now run the script after build: ```json title="package.json" { "scripts": { "build": "... && bun ./scripts/sync-content.ts" } } ``` ### Workflow You may manually upload search indexes with the script, or integrate it with your CI/CD pipeline. ### Search UI You can consider different options for implementing the UI: * Using [Fumadocs UI search dialog](/docs/ui/search/algolia). * Build your own using the built-in search client hook: ```ts twoslash import { liteClient } from 'algoliasearch/lite'; import { useDocsSearch } from 'fumadocs-core/search/client'; const client = liteClient('id', 'key'); const { search, setSearch, query } = useDocsSearch({ type: 'algolia', indexName: 'document', client, }); ``` * Use their official clients directly. ## Options ### Tag Filter To configure tag filtering, add a `tag` value to indexes. ```ts title="lib/export-search-indexes.ts" import { source } from '@/lib/source'; import type { DocumentRecord } from 'fumadocs-core/search/algolia'; export async function exportSearchIndexes() { 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, // [!code ++] tag: '', }); } return results; } ``` And update your search client: * **Fumadocs UI**: Enable [Tag Filter](/docs/ui/search/algolia#tag-filter) on Search UI. * **Search Client**: You can add the tag filter like: ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; const { search, setSearch, query } = useDocsSearch({ tag: '', // ... }); ``` The `tag` field is an attribute for faceting. You can also use the filter `tag:value` on Algolia search clients. # 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 *** title: Search description: Configure Search in Fumadocs icon: Search index: true ----------- # Fumadocs Core (core library of framework): Mixedbread URL: /docs/headless/search/mixedbread Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/headless/search/mixedbread.mdx Integrate Mixedbread Search with Fumadocs *** title: Mixedbread description: Integrate Mixedbread Search with Fumadocs ------------------------------------------------------ ## Introduction The Mixedbread Integration uses vector search to provide semantic search capabilities for your documentation. It indexes your documentation content into a vector store, enabling users to search using natural language queries and find relevant content based on meaning rather than just keyword matching. ## Setup ### Get your API Key 1. Sign up at [Mixedbread](https://platform.mixedbread.com) 2. Navigate to [API Keys](https://platform.mixedbread.com/platform?next=api-keys) 3. Create a new API key and store it in your environment variables ### Create a Vector Store To sync your documentation, you'll need to create a vector store: 1. Go to the [Vector Stores](https://platform.mixedbread.com/platform?next=vector-stores) in your Mixedbread dashboard 2. Create a new vector store for your documentation 3. Copy the vector store ID ### Sync Documentation Use the [Mixedbread CLI](https://www.mixedbread.com/cli) to sync your documentation: Install the CLI: npm pnpm yarn bun ```bash npm install @mixedbread/cli -D ``` ```bash pnpm add @mixedbread/cli -D ``` ```bash yarn add @mixedbread/cli --dev ``` ```bash bun add @mixedbread/cli --dev ``` Configure authentication and sync your documentation: ```bash # Configure authentication mxbai config keys add YOUR_API_KEY # Sync your documentation mxbai vs sync YOUR_VECTOR_STORE_ID "./content/docs" ``` The CLI will automatically detect changes in your documentation and update the vector store accordingly. ### Workflow You can automatically sync your documentation by adding a sync script to your `package.json`: ```json { "scripts": { "build": "... && mxbai vs sync YOUR_VECTOR_STORE_ID './content/docs' --ci" } } ``` ## Options ### Tag Filter To filter search results by tags, add a tag field to your document metadata: ```md --- title: Mixedbread description: Integrate Mixedbread Search with Fumadocs url: /docs/headless/search/mixedbread // [!code ++] tag: docs --- ``` And update your search client: * **Fumadocs UI**: Enable [Tag Filter](/docs/ui/search/orama#tag-filter) on Search UI. * **Search Client**: You can add the tag filter like: ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; const { search, setSearch, query } = useDocsSearch({ tag: '', // ... }); ``` This allows you to scope searches to specific sections of your documentation. # 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 *** title: Orama Cloud description: Integrate with Orama Cloud --------------------------------------- To begin, create an account on Orama Cloud. ## REST API REST API integration requires your docs to upload the indexes. Create a new REST API index from Dashboard using the following schema: ```json { "id": "string", "title": "string", "url": "string", "tag": "string", "page_id": "string", "section": "string", "section_id": "string", "content": "string" } ``` Export the search indexes by pre-rendering a static route. ```ts title="lib/export-search-indexes.ts" import { source } from '@/lib/source'; import type { OramaDocument } from 'fumadocs-core/search/orama-cloud'; export async function exportSearchIndexes() { return source.getPages().map((page) => { return { id: page.url, structured: page.data.structuredData, url: page.url, title: page.data.title, description: page.data.description, } satisfies OramaDocument; }); } ``` ```ts tab="Next.js" title="app/static.json/route.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const revalidate = false; export async function GET() { return Response.json(await exportSearchIndexes()); } ``` ```ts tab="React Router" title="app/routes/static.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function loader() { return Response.json(await exportSearchIndexes()); } ``` ```ts tab="React Router" title="app/routes.ts" import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code ++] route('static.json', 'routes/static.ts'), ] satisfies RouteConfig; ``` ```ts tab="Tanstack Start" title="src/routes/static[.]json.ts" import { createServerFileRoute } from '@tanstack/react-start/server'; import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const ServerRoute = createServerFileRoute('/static.json').methods({ async GET() { return Response.json(await exportSearchIndexes()); }, }); ``` ```ts tab="Tanstack Start" title="vite.config.ts" import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ // ... tanstackStart({ prerender: { enabled: true, }, // [!code ++] pre-render the static file pages: [{ path: '/static.json' }], }), ], }); ``` ```ts tab="Waku" title="pages/api/static.json.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function GET() { return Response.json(await exportSearchIndexes()); } export const getConfig = () => ({ render: 'static', }); ``` Then, using the private API key and index ID from dashboard, create a script to sync search indexes. ```ts title="scripts/sync-content.ts" import { sync, type OramaDocument } from 'fumadocs-core/search/orama-cloud'; import * as fs from 'node:fs/promises'; import { CloudManager } from '@oramacloud/client'; // the path of pre-rendered `static.json`, choose one according to your React framework const filePath = { next: '.next/server/app/static.json.body', 'tanstack-start': '.output/public/static.json', 'react-router': 'build/client/static.json', waku: 'dist/public/static.json', }['next']; async function main() { // private API key [!code highlight] const apiKey = process.env.ORAMA_PRIVATE_API_KEY; if (!apiKey) { console.log('no api key for Orama found, skipping'); return; } const content = await fs.readFile(filePath); const records = JSON.parse(content.toString()) as OramaDocument[]; const manager = new CloudManager({ api_key: apiKey }); await sync(manager, { index: '', documents: records, }); console.log(`search updated: ${records.length} records`); } void main(); ``` Run the script after production build: ```json title="package.json" { "scripts": { "build": "... && bun scripts/sync-content.ts" } } ``` ### Search Client To search documents on the client side, consider: * Using [Fumadocs UI search dialog](/docs/ui/search/orama-cloud). * Custom search UI using the built-in hook of Fumadocs: ```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 }, }); ``` * Use their search client directly. ## 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. # 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 *** title: Built-in Search description: Built-in document search of Fumadocs ------------------------------------------------- Fumadocs supports document search with Orama, It is the default but also the recommended option since it can be self-hosted and totally free. ## Setup Host the server for handling search requests. ### From Source Create the server from source object. ```ts tab="Next.js" title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; export const { GET } = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); ``` ```ts tab="React Router" title="app/docs/search.ts" import type { Route } from './+types/search'; import { createFromSource } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; const server = createFromSource(source, { language: 'english', }); export async function loader({ request }: Route.LoaderArgs) { return server.GET(request); } ``` ```ts tab="Tanstack Start" title="src/routes/api/search.ts" import { createServerFileRoute } from '@tanstack/react-start/server'; import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; const server = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); export const ServerRoute = createServerFileRoute('/api/search').methods({ GET: async ({ request }) => server.GET(request), }); ``` ```ts tab="Waku" title="src/pages/api/search.ts" import { createFromSource } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; export const { GET } = createFromSource(source); ``` ### From Search Indexes Create the server from search indexes, 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 tab="Next.js" title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createSearchAPI } from 'fumadocs-core/search/server'; export const { GET } = createSearchAPI('advanced', { language: 'english', indexes: source.getPages().map((page) => ({ title: page.data.title, description: page.data.description, url: page.url, id: page.url, structuredData: page.data.structuredData, })), }); ``` ```ts tab="React Router" title="app/docs/search.ts" import type { Route } from './+types/search'; import { createSearchAPI } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; const server = createSearchAPI('advanced', { language: 'english', indexes: source.getPages().map((page) => ({ title: page.data.title, description: page.data.description, url: page.url, id: page.url, structuredData: page.data.structuredData, })), }); export async function loader({ request }: Route.LoaderArgs) { return server.GET(request); } ``` ```ts tab="Tanstack Start" title="src/routes/api/search.ts" import { createServerFileRoute } from '@tanstack/react-start/server'; import { source } from '@/lib/source'; import { createSearchAPI } from 'fumadocs-core/search/server'; const server = createSearchAPI('advanced', { language: 'english', indexes: source.getPages().map((page) => ({ title: page.data.title, description: page.data.description, url: page.url, id: page.url, structuredData: page.data.structuredData, })), }); export const ServerRoute = createServerFileRoute('/api/search').methods({ GET: async ({ request }) => server.GET(request), }); ``` ```ts tab="Waku" title="src/pages/api/search.ts" import { source } from '@/lib/source'; import { createSearchAPI } from 'fumadocs-core/search/server'; export const { GET } = createSearchAPI('advanced', { language: 'english', indexes: source.getPages().map((page) => ({ title: page.data.title, description: page.data.description, url: page.url, id: page.url, structuredData: page.data.structuredData, })), }); ``` ### Searching Documents You can search documents using: * **Fumadocs UI**: Supported out-of-the-box, see [Search UI](/docs/ui/search/orama) for details. * **Search Client**: ```ts twoslash import { useDocsSearch } from 'fumadocs-core/search/client'; const client = useDocsSearch({ type: 'fetch', }); ``` ## Configurations ### Tag Filter Support filtering results by tag, it's useful for implementing multi-docs similar to this documentation. ```ts import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; const server = 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] [!code ++] tag: '', }; }, }); ``` and update your search client: * **Fumadocs UI**: Configure [Tag Filter](/docs/ui/search/orama#tag-filter) on Search UI. * **Search Client**: pass a tag to the hook. ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; const client = useDocsSearch({ type: 'fetch', tag: '', // [!code ++] }); ``` ### Static Mode \[#static-export] To support usage with static site, use `staticGET` from search server and make the route static or pre-rendered. ```ts tab="Next.js" title="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; // statically cached [!code highlight:2] export const revalidate = false; export const { staticGET: GET } = createFromSource(source); ``` ```ts tab="React Router" title="app/docs/search.ts" import type { Route } from './+types/search'; // make sure this route is pre-rendered in `react-router.config.ts`. export async function loader({ request }: Route.LoaderArgs) { // [!code highlight] return server.staticGET(request); } ``` ```ts tab="Tanstack Start" title="src/routes/api/search.ts" import { createServerFileRoute } from '@tanstack/react-start/server'; export const ServerRoute = createServerFileRoute('/api/search').methods({ // [!code highlight] GET: async ({ request }) => server.staticGET(request), }); ``` ```ts tab="Tanstack Start" title="vite.config.ts" import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ tanstackStart({ prerender: { enabled: true, }, // [!code ++] pre-render the index file pages: [{ path: '/api/search' }], }), ], }); ``` ```ts tab="Waku" title="src/pages/api/search.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; // [!code highlight] export const { staticGET: GET } = createFromSource(source); // statically cached [!code highlight:3] export const getConfig = async () => ({ render: 'static', }); ``` > `staticGET` is also available on `createSearchAPI`. and update your search clients: * **Fumadocs UI**: use [static client](/docs/ui/search/orama#static) on Search UI. * **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, it can be expensive. You should use cloud solutions like Orama Cloud or Algolia for these cases. ## Internationalization Make sure your language is on the Orama [Supported Languages](https://docs.orama.com/docs/orama-js/supported-languages) list. ```ts title="app/api/search/route.ts" tab="From Source" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; const server = createFromSource(source, { localeMap: { // [locale]: Orama options [!code ++:2] ru: { language: 'russian' }, en: { language: 'english' }, }, }); ``` ```ts tab="From Search Indexes" import { source } from '@/lib/source'; import { createI18nSearchAPI } from 'fumadocs-core/search/server'; import { i18n } from '@/lib/i18n'; const server = createI18nSearchAPI('advanced', { i18n, // [!code ++] 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 === 'ru' ? 'russian' : 'english', // [!code ++] })), ), }); ``` For **Static Mode**, you should configure from client-side instead: ```tsx title="components/search.tsx" import { useDocsSearch } from 'fumadocs-core/search/client'; import { create } from '@orama/orama'; function initOrama(locale?: string) { return create({ schema: { _: 'string' }, // [!code ++] language: locale === 'ru' ? 'russian' : 'english', }); } function Search() { const client = useDocsSearch({ type: 'static', initOrama, }); // ... } ``` ### Special Languages Chinese and Japanese require additional configurations: npm pnpm yarn bun ```bash npm i @orama/tokenizers ``` ```bash pnpm add @orama/tokenizers ``` ```bash yarn add @orama/tokenizers ``` ```bash bun add @orama/tokenizers ``` ```ts title="app/api/search/route.ts" tab="createFromSource" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; import { createTokenizer } from '@orama/tokenizers/mandarin'; export const { GET } = createFromSource(source, { localeMap: { // [locale]: Orama options cn: { components: { tokenizer: createTokenizer(), }, search: { threshold: 0, tolerance: 0, }, }, }, }); ``` ```tsx tab="Static mode" title="components/search.tsx" import { useDocsSearch } from 'fumadocs-core/search/client'; import { createTokenizer } from '@orama/tokenizers/mandarin'; import { create } from '@orama/orama'; // [!code focus:8] function initOrama(locale?: string) { return create({ schema: { _: 'string' }, components: { tokenizer: locale === 'cn' ? createTokenizer() : undefined, }, }); } function Search() { const client = useDocsSearch({ type: 'static', initOrama, }); // ... } ``` and update your search clients: * **Fumadocs UI**: No changes needed, Fumadocs UI handles this when you have i18n configured correctly. * **Search Client**: Add `locale` to the search client, this will only allow pages with specified locale to be searchable by the user. ```ts import { useDocsSearch } from 'fumadocs-core/search/client'; const { search, setSearch, query } = useDocsSearch({ type: 'fetch', locale: 'cn', }); ``` ## 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 *** title: Trieve Search description: 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 npm pnpm yarn bun ```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 Export the search indexes by pre-rendering a static route. ```ts title="lib/export-search-indexes.ts" import { source } from '@/lib/source'; import { type TrieveDocument } from 'trieve-fumadocs-adapter/search/sync'; export async function exportSearchIndexes() { 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 results; } ``` ```ts tab="Next.js" title="app/static.json/route.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const revalidate = false; export async function GET() { return Response.json(await exportSearchIndexes()); } ``` ```ts tab="React Router" title="app/routes/static.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function loader() { return Response.json(await exportSearchIndexes()); } ``` ```ts tab="React Router" title="app/routes.ts" import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code ++] route('static.json', 'routes/static.ts'), ] satisfies RouteConfig; ``` ```ts tab="Tanstack Start" title="src/routes/static[.]json.ts" import { createServerFileRoute } from '@tanstack/react-start/server'; import { exportSearchIndexes } from '@/lib/export-search-indexes'; export const ServerRoute = createServerFileRoute('/static.json').methods({ async GET() { return Response.json(await exportSearchIndexes()); }, }); ``` ```ts tab="Tanstack Start" title="vite.config.ts" import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ // ... tanstackStart({ prerender: { enabled: true, }, // [!code ++] pre-render the static file pages: [{ path: '/static.json' }], }), ], }); ``` ```ts tab="Waku" title="pages/api/static.json.ts" import { exportSearchIndexes } from '@/lib/export-search-indexes'; export async function GET() { return Response.json(await exportSearchIndexes()); } export const getConfig = () => ({ render: 'static', }); ``` Create a script, the `sync` function will sync search indexes. ```ts title="scripts/sync-content.ts" import * as fs from 'node:fs'; import { sync, type TrieveDocument } from 'trieve-fumadocs-adapter/search/sync'; import { TrieveSDK } from 'trieve-ts-sdk'; // the path of pre-rendered `static.json`, choose one according to your React framework const filePath = { next: '.next/server/app/static.json.body', 'tanstack-start': '.output/public/static.json', 'react-router': 'build/client/static.json', waku: 'dist/public/static.json', }['next']; const content = fs.readFileSync(filePath); const records: TrieveDocument[] = 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": "... && bun scripts/sync-content.ts" } } ``` > You can also integrate it with your CI/CD pipeline. ### Search UI You can use their `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 with your new one. ### 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 *** title: Find Neighbours description: 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 *** title: Get TOC description: 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 *** title: Last Modified Time description: 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.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 *** title: Utilities index: true description: Utilities to provide extra functionality to your docs ------------------------------------------------------------------ # Fumadocs MDX (the built-in content source): Next.js URL: /docs/mdx/next Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/next.mdx Use Fumadocs MDX with Next.js *** title: Next.js description: Use Fumadocs MDX with Next.js ------------------------------------------ ## Setup Set up Fumadocs MDX for your Next.js application. npm pnpm yarn bun ```bash npm i 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 ``` Create the configuration file: ```ts title="source.config.ts" import { defineDocs, defineConfig } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', }); export default defineConfig(); ``` Add the plugin to Next.js config: ```js title="next.config.mjs" import { createMDX } from 'fumadocs-mdx/next'; const withMDX = createMDX({ // customise the config file path // configPath: "source.config.ts" }); /** @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. ### Integrate with Fumadocs You can create a `lib/source.ts` file and obtain Fumadocs source from the `docs` collection 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(), }); ``` > `.source` will be generated when you run `next dev` or `next build`. > > Make sure you are importing the `.source` rather than `source.config.ts`. ### Integrate with CI (Optional) You can run `fumadocs-mdx` to generate the `.source` folder. Optionally, you can run it in post install to ensure types are generated when initializing the project. ```json title="package.json" { "scripts": { "postinstall": "fumadocs-mdx" } } ``` ### Done Have fun! ## Examples ### Accessing Content Generally, you'll interact with the collections through [`loader()`](/docs/headless/source-api#output), same for multiple `docs` collections. ```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 ; ``` Without using `loader()`, you can also import the `.source` folder using its collection 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); ``` # Fumadocs Framework: Accordion URL: /docs/ui/components/accordion Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/accordion.mdx Add Accordions to your documentation *** title: Accordion description: Add Accordions to your documentation preview: accordion ------------------ ## Usage Based on [Radix UI Accordion](https://www.radix-ui.com/primitives/docs/components/accordion), useful for FAQ sections. ```mdx import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; My Content ``` ### Accordions ### Accordion ### Linking to Accordion You can specify an `id` for accordion. The accordion will automatically open when the user is navigating to the page with the specified `id` in hash parameter. ```mdx My Content ``` > The value of accordion is same as title by default. When an id presents, it will be used as the value instead. # Fumadocs Framework: Auto Type Table URL: /docs/ui/components/auto-type-table Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/auto-type-table.mdx Auto-generated type table *** title: Auto Type Table description: Auto-generated type table -------------------------------------- string /** * We love Shiki. * * \`\`\`ts * console.log("Hello World, powered by Shiki"); * \`\`\` * @default { a: "test" } */ options?: Partial<{ a: unknown }>; }`} /> You cannot use this in a client component. It generates a table for your docs based on TypeScript definitions. ## Usage npm pnpm yarn bun ```bash npm i fumadocs-typescript ``` ```bash pnpm add fumadocs-typescript ``` ```bash yarn add fumadocs-typescript ``` ```bash bun add fumadocs-typescript ``` Initialize the TypeScript compiler and add it as a MDX component. ```tsx title="mdx-components.tsx" import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; import { createGenerator } from 'fumadocs-typescript'; import { AutoTypeTable } from 'fumadocs-typescript/ui'; const generator = createGenerator(); export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, AutoTypeTable: (props) => ( ), ...components, }; } ``` ### From File It accepts a `path` prop that points to a typescript file, and `name` for the exported type name. ```ts title="path/to/file.ts" export interface MyInterface { name: string; } ``` ```mdx ``` The path is relative to your project directory (`cwd`), because `AutoTypeTable` is a React Server Component, it cannot access build-time information like MDX file path. ### From Type You can specify the type to generate, without an actual TypeScript file. ```mdx import { AutoTypeTable } from 'fumadocs-typescript/ui'; ``` When a `path` is given, it shares the same context as the TypeScript file. ```ts title="file.ts" export type A = { hello: string }; ``` ```mdx ``` When `type` has multiple lines, the export statement and `name` prop are required. ```mdx ``` ### Functions Notice that only object type is allowed. For functions, you should wrap them into an object instead. ```ts export interface MyInterface { myFn: (input: string) => void; } ``` ## TypeScript Compiler Under the hood, it uses the [Typescript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) to extract type information. Your `tsconfig.json` file in the current working directory will be loaded. You can change the compiler settings from [`createGenerator()`](/docs/ui/typescript). ```ts import { createGenerator } from 'fumadocs-typescript'; const generator = createGenerator({ tsconfigPath: './tsconfig.json', // where to resolve relative paths (normally cwd) basePath: './', // disable file system cache cache: false, }); ``` ### File System It relies on the file system, hence, the page referencing this component must be built in **build time**. Rendering the component on serverless runtime may cause problems. ## References \n```", "tags": [], "type": "string | undefined", "simplifiedType": "string", "required": false, "deprecated": false }, { "name": "generator", "description": "", "tags": [], "type": "{ generateDocumentation(file: { path: string; content?: string | undefined; }, name: string | undefined, options?: GenerateOptions | undefined): GeneratedDoc[]; generateTypeTable(props: BaseTypeTableProps, options?: GenerateTypeTableOptions | undefined): Promise; }", "simplifiedType": "object", "required": true, "deprecated": false }, { "name": "renderMarkdown", "description": "", "tags": [], "type": "((md: string) => Promise) | undefined", "simplifiedType": "function", "required": false, "deprecated": false }, { "name": "renderType", "description": "", "tags": [], "type": "((type: string) => Promise) | undefined", "simplifiedType": "function", "required": false, "deprecated": false }, { "name": "options", "description": "", "tags": [], "type": "GenerateTypeTableOptions | undefined", "simplifiedType": "object", "required": false, "deprecated": false } ] }} /> # Fumadocs Framework: Banner URL: /docs/ui/components/banner Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/banner.mdx Add a banner to your site *** title: Banner description: Add a banner to your site preview: banner --------------- ## Usage Put the element at the top of your root layout, you can use it for displaying announcements. ```tsx import { Banner } from 'fumadocs-ui/components/banner'; export default function RootLayout({ children, }: { children: React.ReactNode; }): React.ReactElement { return ( Hello World {children} ); } ``` ### Variant Change the default variant. ```tsx import { Banner } from 'fumadocs-ui/components/banner'; Hello World; // customise colors Hello World ; ``` ### Change Layout By default, the banner uses a `style` tag to modify Fumadocs layouts (e.g. reduce the sidebar height). You can disable it with: ```tsx import { Banner } from 'fumadocs-ui/components/banner'; Hello World; ``` ### Close To allow users to close the banner, give the banner an ID. ```tsx import { Banner } from 'fumadocs-ui/components/banner'; Hello World; ``` The state will be automatically persisted. # Fumadocs Framework: Code Block (Dynamic) URL: /docs/ui/components/dynamic-codeblock Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/dynamic-codeblock.mdx A codeblock that also highlights code *** title: Code Block (Dynamic) description: A codeblock that also highlights code preview: dynamicCodeBlock ------------------------- ## Usage ### Client Component ```tsx import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'; ; ``` Unlike the MDX [`CodeBlock`](/docs/ui/mdx/codeblock) component, this is a client component that can be used without MDX. It highlights the code with Shiki and use the default component to render it. Features: * Can be pre-rendered on server * load languages and themes on browser lazily #### Options ```tsx import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'; ; ``` ### Server Component For a server component equivalent, you can use the built-in utility from core: ```tsx import * as Base from 'fumadocs-ui/components/codeblock'; import { highlight } from 'fumadocs-core/highlight'; import { type HTMLAttributes } from 'react'; export async function CodeBlock({ code, lang, ...rest }: HTMLAttributes & { code: string; lang: string; }) { const rendered = await highlight(code, { lang, components: { pre: (props) => , }, // other Shiki options }); return {rendered}; } ``` # Fumadocs Framework: Files URL: /docs/ui/components/files Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/files.mdx Display file structure in your documentation *** title: Files description: Display file structure in your documentation preview: 'files' ---------------- ## Usage Wrap file components in `Files`. ```mdx import { File, Folder, Files } from 'fumadocs-ui/components/files'; ``` ### As CodeBlock You can enable [`remark-mdx-files`](/docs/headless/mdx/remark-mdx-files) to define file structure with codeblocks. ```tsx title="source.config.ts (Fumadocs MDX)" import { remarkMdxFiles } from 'fumadocs-core/mdx-plugins'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { // [!code ++] remarkPlugins: [remarkMdxFiles], }, }); ``` it will convert `files` codeblocks into MDX usage, like: ````md ```files project ├── src │ ├── index.js │ └── utils │ └── helper.js ├── package.json ``` ```` ### File ### Folder # Fumadocs Framework: GitHub Info URL: /docs/ui/components/github-info Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/github-info.mdx Display your GitHub repository information *** title: GitHub Info description: Display your GitHub repository information preview: githubInfo ------------------- ## Usage ```tsx import { GithubInfo } from 'fumadocs-ui/components/github-info'; ; ``` It's recommended to add it to your docs layout with `links` option: ```tsx title="app/docs/layout.tsx" import { DocsLayout, type DocsLayoutProps } from 'fumadocs-ui/layouts/notebook'; import type { ReactNode } from 'react'; import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; import { GithubInfo } from 'fumadocs-ui/components/github-info'; const docsOptions: DocsLayoutProps = { ...baseOptions(), tree: source.pageTree, links: [ { type: 'custom', children: ( ), }, ], }; export default function Layout({ children }: { children: ReactNode }) { return {children}; } ``` # Fumadocs Framework: Graph View URL: /docs/ui/components/graph-view Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/graph-view.mdx A graph of all pages. *** title: Graph View description: A graph of all pages. ---------------------------------- import { GraphView } from '@/components/preview/lazy'; import { buildGraph } from '@/lib/build-graph'; ## Installation You can install this from CLI: ## Usage You can use it in MDX files or the layout components (e.g. in `page.tsx`): ```tsx title="page.tsx" import { GraphView } from '@/components/graph-view'; import { buildGraph } from '@/lib/build-graph'; export function PageBody() { return (
{/* ... */}
); } ``` # Fumadocs Framework: Zoomable Image URL: /docs/ui/components/image-zoom Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/image-zoom.mdx Allow zoom-in images in your documentation *** title: Zoomable Image description: Allow zoom-in images in your documentation preview: zoomImage ------------------ ## Usage Replace `img` with `ImageZoom` in your MDX components. ```tsx title="mdx-components.tsx" import { ImageZoom } from 'fumadocs-ui/components/image-zoom'; import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, img: (props) => , ...components, }; } ``` Now image zoom will be automatically enabled on all images. ```mdx ![Test](/banner.png) ``` ### Image Optimization On Next.js, a default [`sizes` property](https://nextjs.org/docs/app/api-reference/components/image#sizes) will be defined for `` component if not specified. # Fumadocs Framework: Components URL: /docs/ui/components Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/index.mdx Additional components to improve your docs *** title: Components description: Additional components to improve your docs index: true ----------- # Fumadocs Framework: Inline TOC URL: /docs/ui/components/inline-toc Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/inline-toc.mdx Add Inline TOC into your documentation *** title: Inline TOC description: Add Inline TOC into your documentation preview: inlineTOC ------------------ ## Usage Pass TOC items to the component. ```mdx import { InlineTOC } from 'fumadocs-ui/components/inline-toc'; ``` ### Use in Pages You can add inline TOC into every page. ```tsx ... ... ``` ## Reference # Fumadocs Framework: Steps URL: /docs/ui/components/steps Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/steps.mdx Adding steps to your docs *** title: Steps description: Adding steps to your docs preview: steps -------------- ## Usage Put your steps into the `Steps` container. ```mdx import { Step, Steps } from 'fumadocs-ui/components/steps'; ### Hello World ### Hello World ``` > We recommend using Tailwind CSS utility classes directly on Tailwind CSS projects. ### Without imports You can use the Tailwind CSS utilities without importing it. ```mdx
``` It supports adding step styles to only headings with arbitrary variants. ```mdx
### Hello World
```
### Hello World You no longer need to use the step component anymore.
# Fumadocs Framework: Tabs URL: /docs/ui/components/tabs Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/tabs.mdx A Tabs component built with Radix UI, with additional features such as persistent and shared value. *** title: Tabs description: A Tabs component built with Radix UI, with additional features such as persistent and shared value. preview: tabs ------------- ## Usage Add MDX components. ```tsx title="mdx-components.tsx" import defaultMdxComponents from 'fumadocs-ui/mdx'; import * as TabsComponents from 'fumadocs-ui/components/tabs'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, ...TabsComponents, // [!code ++] ...components, }; } ``` And use it like: ```mdx Javascript is weird Rust is fast ``` Javascript is weird Rust is fast ### Without `value` Without a `value`, it detects from the children index. Note that it might cause errors on re-renders, it's not encouraged if the tabs might change. ```mdx import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Javascript is weird Rust is fast ``` Javascript is weird Rust is fast ### Shared Value By passing an `groupId` property, you can share a value across all tabs with the same id. ```mdx Javascript is weird Rust is fast ``` ### Persistent You can enable persistent by passing a `persist` property. The value will be stored in `localStorage`, with its id as the key. ```mdx Javascript is weird Rust is fast ``` > Persistent only works if you have passed an `id`. ### Default Value Set a default value by passing `defaultIndex`. ```mdx Javascript is weird Rust is fast ``` ### Link to Tab Use HTML `id` attribute to link to a specific tab. ```mdx Javascript is weird Rust is fast `Hello World` ``` You can add the hash `#tab-cpp` to your URL and reload, the C++ tab will be activated. Javascript is weird Rust is fast `Hello World` Additionally, the `updateAnchor` property can be set to `true` in the `Tabs` component to automatically update the URL hash whenever time a new tab is selected: ```mdx Javascript is weird Rust is fast `Hello World` ``` Hello! World! ## Advanced Usage Use it in the Radix UI primitive way, see [Radix UI](https://radix-ui.com/primitives/docs/components/tabs) for more details. ```mdx npm Hello World ``` npm npm Hello World # Fumadocs Framework: Type Table URL: /docs/ui/components/type-table Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/components/type-table.mdx A table for documenting types *** title: Type Table description: A table for documenting types preview: typeTable ------------------ ## Usage It accepts a `type` property. ```mdx import { TypeTable } from 'fumadocs-ui/components/type-table'; ``` ## References ### Type Table ### Object Type # Fumadocs Framework: Internationalization URL: /docs/ui/internationalization Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/internationalization/index.mdx Support multiple languages in your documentation *** title: Internationalization description: Support multiple languages in your documentation ------------------------------------------------------------- ## Overview You'll have to configure i18n routing on your React framework and Fumadocs. Fumadocs is not a full-powered i18n library, it's up to you when internationalizing the rest of your app. You can also use other libraries with Fumadocs like [next-intl](https://github.com/amannn/next-intl) on Next.js. # Fumadocs Framework: Next.js URL: /docs/ui/internationalization/next Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/internationalization/next.mdx Support i18n routing on your Next.js + Fumadocs app *** title: Next.js description: Support i18n routing on your Next.js + Fumadocs app ---------------------------------------------------------------- You can [learn more about i18n in Next.js](https://nextjs.org/docs/app/building-your-application/routing/internationalization). ## Setup Define the i18n configurations in a file, we will import it with `@/lib/i18n` in this guide. ```ts title="lib/i18n.ts" import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['en', 'cn'], }); ``` > See [available options](/docs/headless/internationalization) for i18n config. ### Middleware Create a middleware that redirects users to appropriate locale. ```ts title="middleware.ts" import { createI18nMiddleware } from 'fumadocs-core/i18n/middleware'; import { i18n } from '@/lib/i18n'; export default createI18nMiddleware(i18n); export const config = { // Matcher ignoring `/_next/` and `/api/` // You may need to adjust it to ignore static assets in `/public` folder matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], }; ``` The default middleware is optional, you can also use your own middleware or the one provided by i18n libraries. Make sure its behaviour aligns with the [`hidePrefix`](/docs/headless/internationalization#hide-locale-prefix) option you set in your i18n config. ### Routing Create a `/app/[lang]` folder, and move your pages/layouts into it, except route handlers. Did you accidentally find your styles lost? Make sure the import path to `global.css` is still correct! Provide UI translations and other config to ``, the English translations are used when `translations` is not specified. ```tsx title="app/[lang]/layout.tsx" import { RootProvider } from 'fumadocs-ui/provider'; import { defineI18nUI } from 'fumadocs-ui/i18n'; import { i18n } from '@/lib/i18n'; // [!code ++:11] const { provider } = defineI18nUI(i18n, { translations: { en: { displayName: 'English', }, cn: { displayName: 'Chinese', search: '搜尋文檔', }, }, }); export default async function RootLayout({ params, children, }: { params: Promise<{ lang: string }>; children: React.ReactNode; }) { const lang = (await params).lang; return ( {children} ); } ``` ### Pass Locale Add `locale` parameter to `baseOptions()` and add `i18n` into it: ```tsx title="lib/layout.shared.tsx" import { i18n } from '@/lib/i18n'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(locale: string): BaseLayoutProps { return { i18n, // [!code ++] // different props based on `locale` }; } ``` Pass the locale to Fumadocs in your pages and layouts. ```ts tab="lib/source.ts" import { i18n } from '@/lib/i18n'; import { loader } from 'fumadocs-core/source'; export const source = loader({ i18n, // [!code ++] // other options }); ``` ```tsx tab="Home Layout" title="app/[lang]/(home)/layout.tsx" import type { ReactNode } from 'react'; import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { baseOptions } from '@/lib/layout.shared'; export default async function Layout({ params, children, }: { params: Promise<{ lang: string }>; children: ReactNode; }) { const { lang } = await params; return {children}; // [!code highlight] } ``` ```tsx tab="Docs Layout" title="app/[lang]/docs/layout.tsx" import type { ReactNode } from 'react'; import { source } from '@/lib/source'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; export default async function Layout({ params, children, }: { params: Promise<{ lang: string }>; children: ReactNode; }) { const { lang } = await params; return ( // [!code highlight] {children} ); } ``` ```ts tab="Docs Page" title="app/[lang]/docs/[[...slug]]/page.tsx" 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):** See [Internationalization](/docs/headless/search/orama#internationalization). * **Cloud Solutions (e.g. Algolia):** They usually have official support for multilingual. ## Writing Documents See [i18n routing](/docs/ui/page-conventions#i18n-routing) to learn how to create pages for specific locales. ### 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: React Router URL: /docs/ui/internationalization/react-router Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/internationalization/react-router.mdx Support i18n routing on your React Router + Fumadocs app. *** title: React Router description: Support i18n routing on your React Router + Fumadocs app. ---------------------------------------------------------------------- ## Setup Define the i18n configurations in a file, we will import it with `@/lib/i18n` in this guide. ```ts title="app/lib/i18n.ts" import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['cn', 'en'], }); ``` > See [available options](/docs/headless/internationalization) for i18n config. ### Routing Add `:lang` prefix to all your pages. ```ts title="app/routes.ts" import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code highlight:2] route(':lang', 'routes/home.tsx'), route(':lang/docs/*', 'docs/page.tsx'), route('api/search', 'docs/search.ts'), ] satisfies RouteConfig; ``` #### Make locale optional You can also use `:lang?` prefix, and update the i18n config: ```ts tab="app/lib/i18n.ts" import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['cn', 'en'], // [!code ++] hideLocale: 'default-locale', }); ``` ```ts tab="app/routes.ts" import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // [!code highlight:2] route(':lang?', 'routes/home.tsx'), route(':lang?/docs/*', 'docs/page.tsx'), route('api/search', 'docs/search.ts'), ] satisfies RouteConfig; ``` ### Pages Provide UI translations and other config to ``, the English translations are used as fallbacks. ```tsx title="app/root.tsx" import { Links, Meta, Scripts, ScrollRestoration, useParams, } from 'react-router'; import { RootProvider } from 'fumadocs-ui/provider/base'; import { ReactRouterProvider } from 'fumadocs-core/framework/react-router'; import { defineI18nUI } from 'fumadocs-ui/i18n'; import { i18n } from '@/lib/i18n'; import './app.css'; // [!code ++:11] const { provider } = defineI18nUI(i18n, { translations: { en: { displayName: 'English', }, cn: { displayName: 'Chinese', search: '搜尋文檔', }, }, }); export function Layout({ children }: { children: React.ReactNode }) { const { lang = i18n.defaultLanguage } = useParams(); return ( {children} ); } ``` ### Pass Locale Add `locale` parameter to `baseOptions()` and include `i18n` in props: ```tsx title="app/lib/layout.shared.tsx" import { i18n } from '@/lib/i18n'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(locale: string): BaseLayoutProps { return { i18n, // [!code ++] // different props based on `locale` }; } ``` Pass the locale to Fumadocs in your pages and layouts. ```ts tab="app/lib/source.ts" import { i18n } from '@/lib/i18n'; import { loader } from 'fumadocs-core/source'; export const source = loader({ i18n, // [!code ++] // other options }); ``` ```tsx tab="Home Layout" title="app/routes/home.tsx" import type { Route } from './+types/home'; import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { baseOptions } from '@/lib/layout.shared'; export default function Home({ params }: Route.ComponentProps) { return ( // [!code highlight] ); } ``` ```tsx tab="Docs Page" title="app/docs/page.tsx" import type { Route } from './+types/page'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { source } from '@/lib/source'; import { type PageTree } from 'fumadocs-core/server'; import { baseOptions } from '@/lib/layout.shared'; export async function loader({ params }: Route.LoaderArgs) { const slugs = params['*'].split('/').filter((v) => v.length > 0); // [!code --] const page = source.getPage(slugs); // [!code ++] const page = source.getPage(slugs, params.lang); if (!page) throw new Response('Not found', { status: 404 }); return { path: page.path, // [!code --] tree: source.pageTree, // [!code ++] tree: source.getPageTree(params.lang), }; } export default function Page({ loaderData, params }: Route.ComponentProps) { const { tree, path } = loaderData; return ( ); } ``` ### Search Configure i18n on your search solution. * **Built-in Search (Orama):** See [Internationalization](/docs/headless/search/orama#internationalization). * **Cloud Solutions (e.g. Algolia):** They usually have official support for multilingual. ## Writing Documents See [i18n routing](/docs/ui/page-conventions#i18n-routing) to learn how to create pages for specific locales. ### 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. ```tsx import { Link, useParams } from 'react-router'; const { lang } = useParams(); About Us; ``` 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: Tanstack Start URL: /docs/ui/internationalization/tanstack-start Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/internationalization/tanstack-start.mdx Support i18n routing on your Tanstack Start + Fumadocs app. *** title: Tanstack Start description: Support i18n routing on your Tanstack Start + Fumadocs app. ------------------------------------------------------------------------ ## Setup Define the i18n configurations in a file, we will import it with `@/lib/i18n` in this guide. ```ts title="src/lib/i18n.ts" import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['cn', 'en'], }); ``` > See [available options](/docs/headless/internationalization) for i18n config. ### Routing Create a `$lang` directory under `routes`, and move all pages into it. Provide UI translations and other config to ``, the English translations are used as fallbacks. ```tsx title="src/routes/__root.tsx" import { HeadContent, Scripts, useParams } from '@tanstack/react-router'; import * as React from 'react'; import { RootProvider } from 'fumadocs-ui/provider/base'; import { TanstackProvider } from 'fumadocs-core/framework/tanstack'; import { defineI18nUI } from 'fumadocs-ui/i18n'; import { i18n } from '@/lib/i18n'; // [!code ++:11] const { provider } = defineI18nUI(i18n, { translations: { cn: { displayName: 'Chinese', search: 'Translated Content', }, en: { displayName: 'English', }, }, }); function RootDocument({ children }: { children: React.ReactNode }) { const { lang } = useParams({ strict: false }); return ( {children} ); } ``` ### Pass Locale Add `locale` parameter to `baseOptions()` and include `i18n` in props: ```tsx title="src/lib/layout.shared.tsx" import { i18n } from '@/lib/i18n'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(locale: string): BaseLayoutProps { return { i18n, // [!code ++] // different props based on `locale` }; } ``` Pass the locale to Fumadocs in your pages and layouts. ```ts tab="src/lib/source.ts" import { i18n } from '@/lib/i18n'; import { loader } from 'fumadocs-core/source'; export const source = loader({ i18n, // [!code ++] // other options }); ``` ```tsx tab="Home Layout" title="src/routes/$lang/index.tsx" import { createFileRoute } from '@tanstack/react-router'; import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { baseOptions } from '@/lib/layout.shared'; export const Route = createFileRoute('/$lang/')({ component: Home, }); function Home() { const { lang } = Route.useParams(); // [!code highlight] return ; } ``` ```tsx tab="Docs Page" title="src/routes/$lang/docs/$.tsx" import { createFileRoute, notFound } from '@tanstack/react-router'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { createServerFn } from '@tanstack/react-start'; import { source } from '@/lib/source'; import { baseOptions } from '@/lib/layout.shared'; export const Route = createFileRoute('/$lang/docs/$')({ component: Page, loader: async ({ params }) => { const data = await loader({ // [!code ++:4] data: { slugs: params._splat?.split('/') ?? [], lang: params.lang, }, }); await clientLoader.preload(data.path); return data; }, }); // [!code highlight:13] const loader = createServerFn({ method: 'GET', }) .validator((params: { slugs: string[]; lang?: string }) => params) .handler(async ({ data: { slugs, lang } }) => { const page = source.getPage(slugs, lang); if (!page) throw notFound(); return { tree: source.getPageTree(lang) as object, path: page.path, }; }); function Page() { const { lang } = Route.useParams(); // ... return ( // [!code highlight] ); } ``` ### Search Configure i18n on your search solution. * **Built-in Search (Orama):** See [Internationalization](/docs/headless/search/orama#internationalization). * **Cloud Solutions (e.g. Algolia):** They usually have official support for multilingual. ## Writing Documents See [i18n routing](/docs/ui/page-conventions#i18n-routing) to learn how to create pages for specific locales. ### 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. ```tsx import { Link, useParams } from '@tanstack/react-router'; const { lang } = useParams({ from: '/$lang/' }); Open Docs ; ``` 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: 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 *** title: Feedback description: Receive feedback from your users --------------------------------------------- ## Overview Feedback is crucial for knowing what your reader thinks, and help you to further improve documentation content. ## Installation **Fumadocs CLI** npm pnpm yarn bun ```bash npx @fumadocs/cli@latest add feedback ``` ```bash pnpm dlx @fumadocs/cli@latest add feedback ``` ```bash yarn dlx @fumadocs/cli@latest add feedback ``` ```bash bun x @fumadocs/cli@latest add feedback ``` **Shadcn CLI** npm pnpm yarn bun ```bash npx shadcn@latest add https://fumadocs.dev/r/feedback.json ``` ```bash pnpm dlx shadcn@latest add https://fumadocs.dev/r/feedback.json ``` ```bash yarn dlx shadcn@latest add https://fumadocs.dev/r/feedback.json ``` ```bash bun x shadcn@latest add https://fumadocs.dev/r/feedback.json ``` ## Usage Now add the `` component to your docs page: ```tsx import { DocsPage } from 'fumadocs-ui/page'; import { Feedback } from '@/components/feedback'; import posthog from 'posthog-js'; export default async function Page() { return ( {/* at the bottom of page */} { 'use server'; await posthog.capture('on_rate_docs', feedback); }} /> ); } ``` * `onRateAction`: fired when user submit feedback. You can specify a server action, or any function (in client component). Such as reporting user feedback as a `on_rate_docs` event on PostHog. ### Integrating with GitHub Discussion To report your feedback to GitHub Discussion, make a custom `onRateAction`. You can copy this server action as a starting point: ```ts lib/github.ts import { App, Octokit } from 'octokit'; import type { ActionResponse, Feedback } from '@/components/feedback'; export const repo = 'fumadocs'; export const owner = 'fuma-nama'; export const DocsCategory = 'Docs Feedback'; let instance: Octokit | undefined; async function getOctokit(): Promise { if (instance) return instance; const appId = process.env.GITHUB_APP_ID; const privateKey = process.env.GITHUB_APP_PRIVATE_KEY; if (!appId || !privateKey) { throw new Error( 'No GitHub keys provided for Github app, docs feedback feature will not work.', ); } const app = new App({ appId, privateKey, }); const { data } = await app.octokit.request( 'GET /repos/{owner}/{repo}/installation', { owner, repo, headers: { 'X-GitHub-Api-Version': '2022-11-28', }, }, ); instance = await app.getInstallationOctokit(data.id); return instance; } interface RepositoryInfo { id: string; discussionCategories: { nodes: { id: string; name: string; }[]; }; } let cachedDestination: RepositoryInfo | undefined; async function getFeedbackDestination() { if (cachedDestination) return cachedDestination; const octokit = await getOctokit(); const { repository, }: { repository: RepositoryInfo; } = await octokit.graphql(` query { repository(owner: "${owner}", name: "${repo}") { id discussionCategories(first: 25) { nodes { id name } } } } `); return (cachedDestination = repository); } export async function onRateAction( url: string, feedback: Feedback, ): Promise { 'use server'; const octokit = await getOctokit(); const destination = await getFeedbackDestination(); if (!octokit || !destination) throw new Error('GitHub comment integration is not configured.'); const category = destination.discussionCategories.nodes.find( (category) => category.name === DocsCategory, ); if (!category) throw new Error( `Please create a "${DocsCategory}" category in GitHub Discussion`, ); const title = `Feedback for ${url}`; const body = `[${feedback.opinion}] ${feedback.message}\n\n> Forwarded from user feedback.`; let { search: { nodes: [discussion], }, }: { search: { nodes: { id: string; url: string }[]; }; } = await octokit.graphql(` query { search(type: DISCUSSION, query: ${JSON.stringify(`${title} in:title repo:${owner}/${repo} author:@me`)}, first: 1) { nodes { ... on Discussion { id, url } } } }`); if (discussion) { await octokit.graphql(` mutation { addDiscussionComment(input: { body: ${JSON.stringify(body)}, discussionId: "${discussion.id}" }) { comment { id } } }`); } else { const result: { discussion: { id: string; url: string }; } = await octokit.graphql(` mutation { createDiscussion(input: { repositoryId: "${destination.id}", categoryId: "${category!.id}", body: ${JSON.stringify(body)}, title: ${JSON.stringify(title)} }) { discussion { id, url } } }`); discussion = result.discussion; } return { githubUrl: discussion.url, }; } ``` * Create your own GitHub App and obtain its app ID and private key. * Fill required environment variables. * Replace constants like `owner`, `repo`, and `DocsCategory`. # Fumadocs Framework: AI URL: /docs/ui/llms Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/llms.mdx Integrate AI functionality to Fumadocs. *** title: AI description: Integrate AI functionality to Fumadocs. ---------------------------------------------------- ## Docs for LLM You can make your docs site more AI-friendly with dedicated docs content for large language models. First, make a `getLLMText` function that converts pages into static MDX content: ```ts title="lib/get-llm-text.ts" import { remark } from 'remark'; import remarkGfm from 'remark-gfm'; import remarkMdx from 'remark-mdx'; import { remarkInclude } from 'fumadocs-mdx/config'; import { source } from '@/lib/source'; import type { InferPageType } from 'fumadocs-core/source'; import fs from 'node:fs/promises'; const processor = remark() .use(remarkMdx) // needed for Fumadocs MDX .use(remarkInclude) .use(remarkGfm); export async function getLLMText(page: InferPageType) { const processed = await processor.process({ path: page.absolutePath, value: await fs.readFile(page.absolutePath), }); // note: it doesn't escape frontmatter, it's up to you. return `# ${page.data.title} URL: ${page.url} ${processed.value}`; } ``` > Modify it to include other remark plugins. ### `llms-full.txt` A version of docs for AIs to read. ```ts tab="Next.js" title="app/llms-full.txt/route.ts" import { source } from '@/lib/source'; import { getLLMText } from '@/lib/get-llm-text'; // cached forever export const revalidate = false; export async function GET() { const scan = source.getPages().map(getLLMText); const scanned = await Promise.all(scan); return new Response(scanned.join('\n\n')); } ``` ```ts tab="React Router" title="app/routes/llms-full.ts" // make sure to include this route in `routes.ts` & pre-rendering! import { source } from '@/lib/source'; import { getLLMText } from '@/lib/get-llm-text'; export async function loader() { const scan = source.getPages().map(getLLMText); const scanned = await Promise.all(scan); return new Response(scanned.join('\n\n')); } ``` ```ts tab="Tanstack Start" title="src/routes/llms-full[.]txt.ts" import { createServerFileRoute } from '@tanstack/react-start/server'; import { source } from '@/lib/source'; import { getLLMText } from '@/lib/get-llm-text'; export const ServerRoute = createServerFileRoute('/llms-full.txt').methods({ GET: async () => { const scan = source.getPages().map(getLLMText); const scanned = await Promise.all(scan); return new Response(scanned.join('\n\n')); }, }); ``` ### `*.mdx` Allow people to append `.mdx` to a page to get its Markdown/MDX content. On Next.js, you can make a route handler to return page content, and redirect users to that route. ```ts tab="app/llms.mdx/[[...slug]]/route.ts" import { type NextRequest, NextResponse } from 'next/server'; import { getLLMText } from '@/lib/get-llm-text'; import { source } from '@/lib/source'; import { notFound } from 'next/navigation'; export const revalidate = false; export async function GET( _req: NextRequest, { params }: { params: Promise<{ slug?: string[] }> }, ) { const { slug } = await params; const page = source.getPage(slug); if (!page) notFound(); return new NextResponse(await getLLMText(page)); } export function generateStaticParams() { return source.generateParams(); } ``` ```ts tab="next.config.ts" import type { NextConfig } from 'next'; const config: NextConfig = { async rewrites() { return [ { source: '/docs/:path*.mdx', destination: '/llms.mdx/:path*', }, ]; }, }; ``` ### Page Actions Common page actions for AI, require `*.mdx` to be implemented first. ![AI Page Actions](/docs/ai-page-actions.png) npm pnpm yarn bun ```bash npx @fumadocs/cli add ai/page-actions ``` ```bash pnpm dlx @fumadocs/cli add ai/page-actions ``` ```bash yarn dlx @fumadocs/cli add ai/page-actions ``` ```bash bun x @fumadocs/cli add ai/page-actions ``` Use it in your docs page like: ```tsx title="app/docs/[[...slug]]/page.tsx"
``` ## AI Search ![AI Search](/docs/ai-search.png) You can install the AI search dialog using Fumadocs CLI: npm pnpm yarn bun ```bash npx @fumadocs/cli add ai/search ``` ```bash pnpm dlx @fumadocs/cli add ai/search ``` ```bash yarn dlx @fumadocs/cli add ai/search ``` ```bash bun x @fumadocs/cli add ai/search ``` By default, it's configured for Inkeep AI. Since it's connected via Vercel AI SDK, you can connect to your own AI models easily. > Note that Fumadocs doesn't provide the AI model, it's up to you. # Fumadocs Framework: Obsidian URL: /docs/ui/obsidian Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/obsidian.mdx Render your Obsidian vaults in Fumadocs. *** title: Obsidian description: Render your Obsidian vaults in Fumadocs. ----------------------------------------------------- Might have bugs or breaking changes, use it at your own risk. ## Setup npm pnpm yarn bun ```bash npm i fumadocs-obsidian ``` ```bash pnpm add fumadocs-obsidian ``` ```bash yarn add fumadocs-obsidian ``` ```bash bun add fumadocs-obsidian ``` You can copy your vault folder to the project (e.g. root directory): Create a script to generate docs & assets: ```ts title="scripts/generate.ts" import { fromVault } from 'fumadocs-obsidian'; await fromVault({ dir: 'Obsidian Vault', out: { // you can specify the locations of `/public` & `/content/docs` folder }, }); ``` Run the script to generate docs: ```bash bun scripts/generate.ts ``` Finally, include necessary MDX components: ```tsx title="mdx-components.tsx" import defaultMdxComponents from 'fumadocs-ui/mdx'; import * as ObsidianComponents from 'fumadocs-obsidian/ui'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents) { return { ...defaultMdxComponents, ...ObsidianComponents, ...components, }; } ``` ### Additions Some syntax features need to be enabled separately: * [Mermaid](/docs/ui/markdown/mermaid). * [Math](/docs/ui/markdown/math). # Fumadocs Framework: Metadata Image (Next.js) URL: /docs/ui/open-graph Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/open-graph.mdx Usage with Next.js Metadata API. *** title: Metadata Image (Next.js) description: Usage with Next.js Metadata API. --------------------------------------------- ## Introduction > Make sure to read their [Metadata section](https://nextjs.org/docs/app/building-your-application/optimizing/metadata) for the fundamentals of Metadata API. For docs pages, Fumadocs has a built-in metadata image generator. You will need a route handler to get started. ```tsx title="app/docs-og/[...slug]/route.tsx" import { generateOGImage } from 'fumadocs-ui/og'; import { source } from '@/lib/source'; import { notFound } from 'next/navigation'; export async function GET( _req: Request, { params }: RouteContext<'/docs-og/[...slug]'>, ) { const { slug } = await params; const page = source.getPage(slug.slice(0, -1)); if (!page) notFound(); return generateOGImage({ title: page.data.title, description: page.data.description, site: 'My App', }); } export function generateStaticParams() { return source.generateParams().map((page) => ({ ...page, slug: [...page.slug, 'image.png'], })); } ``` > We need to append `image.png` to the end of slugs so that we can access it via `/docs-og/my-page/image.png`. In your docs page, add the image to metadata. ```tsx title="app/docs/[[...slug]]/page.tsx" import { notFound } from 'next/navigation'; import { source } from '@/lib/source'; export async function generateMetadata({ params, }: { params: Promise<{ slug?: string[] }>; }) { const { slug = [] } = await params; const page = source.getPage(slug); if (!page) notFound(); const image = ['/docs-og', ...slug, 'image.png'].join('/'); return { title: page.data.title, description: page.data.description, openGraph: { images: image, }, twitter: { card: 'summary_large_image', images: image, }, }; } ``` ### Other Presets There's other available styles on Fumadocs CLI, such as `mono`: npm pnpm yarn bun ```bash npx @fumadocs/cli@latest add og/mono ``` ```bash pnpm dlx @fumadocs/cli@latest add og/mono ``` ```bash yarn dlx @fumadocs/cli@latest add og/mono ``` ```bash bun x @fumadocs/cli@latest add og/mono ``` Replaced your old `generateOGImage` with the installed one. ### Customisations You can specify options for Satori, like adding fonts: ```ts import { generateOGImage } from 'fumadocs-ui/og'; generateOGImage({ fonts: [ { name: 'Roboto', // Use `fs` (Node.js only) or `fetch` to read the font as Buffer/ArrayBuffer and provide `data` here. data: robotoArrayBuffer, weight: 400, style: 'normal', }, ], }); ``` # Fumadocs Framework: Python URL: /docs/ui/python Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/python.mdx Generate docs from Python *** title: Python description: Generate docs from Python -------------------------------------- Support for Python docgen is still experimental, please use it in caution. ## Setup npm pnpm yarn bun ```bash npm install fumadocs-python shiki ``` ```bash pnpm add fumadocs-python shiki ``` ```bash yarn add fumadocs-python shiki ``` ```bash bun add fumadocs-python shiki ``` ### Generate Docs Install the Python command first, we need it to collect docs from your Python package. ```bash pip install ./node_modules/fumadocs-python ``` Generate the docs as a JSON: ```bash fumapy-generate your-package-name # for example fumapy-generate httpx ``` Use the following script to convert JSON into MDX: ```js title="scripts/generate-docs.mjs" import { rimraf } from 'rimraf'; import * as Python from 'fumadocs-python'; import * as fs from 'node:fs/promises'; // output JSON file path const jsonPath = './httpx.json'; async function generate() { const out = 'content/docs/(api)'; // clean previous output await rimraf(out); const content = JSON.parse((await fs.readFile(jsonPath)).toString()); const converted = Python.convert(content, { baseUrl: '/docs', }); await Python.write(converted, { outDir: out, }); } void generate(); ``` While most docgens use Markdown or reStructuredText, Fumadocs uses **MDX**. Make sure your doc is valid in MDX syntax before running. ### MDX Components Add the components. ```tsx import defaultMdxComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; import * as Python from 'fumadocs-python/components'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, ...Python, ...components, }; } ``` Add styles: ```css title="Tailwind CSS" @import 'tailwindcss'; @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; /* [!code ++] */ @import 'fumadocs-python/preset.css'; ``` # Fumadocs Framework: RSS URL: /docs/ui/rss Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/rss.mdx Generate a RSS feed for your docs/blog. *** title: RSS description: Generate a RSS feed for your docs/blog. ---------------------------------------------------- ## Setup You can create the feed object: ```ts title="lib/rss.ts" import { Feed } from 'feed'; import { source } from '@/lib/source'; const baseUrl = 'https://fumadocs.dev'; export function getRSS() { const feed = new Feed({ title: 'Fumadocs Blog', id: `${baseUrl}/blog`, link: `${baseUrl}/blog`, language: 'en', image: `${baseUrl}/banner.png`, favicon: `${baseUrl}/icon.png`, copyright: 'All rights reserved 2025, Fuma Nama', }); for (const page of source.getPages()) { feed.addItem({ id: page.url, title: page.data.title, description: page.data.description, link: `${baseUrl}${page.url}`, date: new Date(page.data.lastModified), author: [ { name: 'Fuma', }, ], }); } return feed.rss2(); } ``` And expose it using a server loader/route handler like: ```ts tab="Next.js" title="app/rss.xml/route.ts" import { getRSS } from '@/lib/rss'; export const revalidate = false; export function GET() { return new Response(getRSS()); } ``` ```ts tab="React Router" // app/routes/rss.ts import { getRSS } from '@/lib/rss'; export async function loader() { return new Response(getRSS()); } // app/routes.ts import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ // ... route('rss.xml', 'routes/rss.ts'), ] satisfies RouteConfig; ``` ```ts tab="Tanstack Start" title="src/routes/rss[.]xml.ts" import { createServerFileRoute } from '@tanstack/react-start/server'; import { getRSS } from '@/lib/rss'; export const ServerRoute = createServerFileRoute('/rss.xml').methods({ async GET() { return new Response(getRSS()); }, }); ``` ### Next.js Metadata You can add an alternates object to the metadata object with your feed’s title and URL. ```ts title="app/layout.tsx" import type { Metadata } from 'next'; export const metadata: Metadata = { alternates: { types: { 'application/rss+xml': [ { title: 'Fumadocs Blog', url: 'https://fumadocs.dev/blog/index.xml', }, ], }, }, }; ``` # Fumadocs Framework: Typescript URL: /docs/ui/typescript Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/typescript.mdx Generate docs from Typescript definitions *** title: Typescript description: Generate docs from Typescript definitions ------------------------------------------------------ ## Usage npm pnpm yarn bun ```bash npm install fumadocs-typescript ``` ```bash pnpm add fumadocs-typescript ``` ```bash yarn add fumadocs-typescript ``` ```bash bun add fumadocs-typescript ``` ### UI Integration It comes with the `AutoTypeTable` component. Learn more about [Auto Type Table](/docs/ui/components/auto-type-table). ### MDX Integration You can use it as a remark plugin: ```ts title="source.config.ts" tab="Fumadocs MDX" import { remarkAutoTypeTable, createGenerator } from 'fumadocs-typescript'; import { defineConfig } from 'fumadocs-mdx/config'; const generator = createGenerator(); export default defineConfig({ mdxOptions: { remarkPlugins: [[remarkAutoTypeTable, { generator }]], }, }); ``` ```ts tab="MDX Compiler" import { remarkAutoTypeTable, createGenerator } from 'fumadocs-typescript'; import { compile } from '@mdx-js/mdx'; const generator = createGenerator(); await compile('...', { remarkPlugins: [[remarkAutoTypeTable, { generator }]], }); ``` It gives you a `auto-type-table` component. You can use it like [Auto Type Table](/docs/ui/components/auto-type-table), but with additional rules: * The value of attributes must be string. * `path` accepts a path relative to the MDX file itself. * You also need to add [`TypeTable`](/docs/ui/components/type-table) to MDX components. ```ts title="path/to/file.ts" export interface MyInterface { name: string; } ``` ```mdx title="page.mdx" ``` ## Annotations ### Hide Hide a field by adding `@internal` tsdoc tag. ```ts interface MyInterface { /** * @internal */ cache: number; } ``` ### Specify Type Name You can specify the name of a type with the `@remarks` tsdoc tag. ```ts interface MyInterface { /** * @remarks `timestamp` Returned by API. // [!code highlight] */ time: number; } ``` This will make the type of `time` property to be shown as `timestamp`. # Fumadocs Framework: Validate Links URL: /docs/ui/validate-links Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/validate-links.mdx Ensure your links are correct. *** title: Validate Links description: Ensure your links are correct. ------------------------------------------- ## Setup You can use [`next-validate-link`](https://next-validate-link.vercel.app) to validate your links in content files. > This guide is mainly for **Fumadocs MDX**, see the docs of `next-validate-link` for other setups. Create a script for Bun: ```ts title="scripts/lint.ts" import { type FileObject, printErrors, scanURLs, validateFiles, } from 'next-validate-link'; import type { InferPageType } from 'fumadocs-core/source'; import { source } from '@/lib/source'; async function checkLinks() { const scanned = await scanURLs({ // pick a preset for your React framework preset: 'next', populate: { 'docs/[[...slug]]': source.getPages().map((page) => { return { value: { slug: page.slugs, }, hashes: getHeadings(page), }; }), }, }); printErrors( await validateFiles(source.getPages().map(toFile), { scanned, // check `href` attributes in different MDX components markdown: { components: { Card: { attributes: ['href'] }, }, }, // check relative paths checkRelativePaths: 'as-url', }), true, ); } function getHeadings({ data }: InferPageType): string[] { return data.toc.map((item) => item.url.slice(1)); } function toFile(page: InferPageType): FileObject { return { data: page.data, url: page.url, path: page.absolutePath, content: page.data.content, }; } void checkLinks(); ``` To access the `source` object outside of app runtime, you'll need a runtime loader: ```toml tab="bunfig.toml" preload = ["./scripts/preload.ts"] ``` ```ts tab="scripts/preload.ts" import { createMdxPlugin } from 'fumadocs-mdx/bun'; Bun.plugin(createMdxPlugin()); ``` Run the script to validate links: ```bash bun ./scripts/lint.ts ``` # Fumadocs Framework: Docs Layout URL: /docs/ui/layouts/docs Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/layouts/docs.mdx The layout of documentation *** title: Docs Layout description: The layout of documentation ---------------------------------------- The layout of documentation pages, it includes a sidebar and mobile-only navbar. ## Usage Pass your page tree to the component. ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ## Sidebar ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; ; ``` > See [Sidebar Links](/docs/ui/navigation/sidebar) for customising sidebar items. ## Nav A mobile-only navbar, the `nav` prop is also reusable on other Fumadocs layouts.
![Docs Nav](/docs/docs-nav.png)
```tsx import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { const base = baseOptions(); return ( {children} ); } ``` ### Transparent Mode To make the navbar background transparent, you can configure transparent mode. ```tsx import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export const baseOptions: BaseLayoutProps = { nav: { transparentMode: 'top', }, }; ``` | Mode | Description | | -------- | ---------------------------------------- | | `always` | Always use a transparent background | | `top` | When at the top of page | | `none` | Disable transparent background (default) | ### Replace Navbar To replace the navbar in Docs Layout, set `nav.component` to your own component. ```tsx title="layout.tsx" import { baseOptions } from '@/lib/layout.shared'; import { DocsLayout } from 'fumadocs-ui/layouts/notebook'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( , }} > {children} ); } ``` Fumadocs uses **CSS Variables** to share the size of layout components, and fit each layout component into appropriate position. You need to override `--fd-nav-height` to the exact height of your custom navbar, this can be done with a CSS stylesheet (e.g. in `global.css`): ```css :root { --fd-nav-height: 80px !important; } ``` ## Advanced ### Disable Prefetching By default, it uses the `` component of your React framework with prefetch enabled. On Vercel, prefetch requests may cause a higher usage of serverless functions and Data Cache. It can also hit the limits of some other hosting platforms. You can disable prefetching to reduce the amount of prefetch requests. ```tsx import { DocsLayout } from 'fumadocs-ui/layouts/docs'; ; ``` # Fumadocs Framework: Home Layout URL: /docs/ui/layouts/home-layout Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/layouts/home-layout.mdx Shared layout for other pages *** title: Home Layout description: Shared layout for other pages ------------------------------------------ ## Usage Add a navbar and search dialog across other pages. ```tsx title="/app/(home)/layout.tsx" import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { baseOptions } from '@/lib/layout.shared'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return {children}; } ``` We recommend to customise it from [`baseOptions`](/docs/ui/navigation/links). # Fumadocs Framework: Notebook URL: /docs/ui/layouts/notebook Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/layouts/notebook.mdx A more compact version of Docs Layout *** title: Notebook description: A more compact version of Docs Layout -------------------------------------------------- ![Notebook](/docs/notebook.png) ## Usage Enable the notebook layout with `fumadocs-ui/layouts/notebook`, it's a more compact layout than the default one. ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/notebook'; // [!code highlight] import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ## Configurations The options are inherited from [Docs Layout](/docs/ui/layouts/docs), with minor differences: * sidebar/navbar cannot be replaced, Notebook layout is more opinionated than the default one. * additional options (see below). ### Tab Mode Configure the style of sidebar tabs. ![Notebook](/docs/notebook-tab-mode.png) ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/notebook'; import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ### Nav Mode Configure the style of navbar. ![Notebook](/docs/notebook-nav-mode.png) ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/notebook'; import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { const { nav, ...base } = baseOptions(); return ( {children} ); } ``` # Fumadocs Framework: Docs Page URL: /docs/ui/layouts/page Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/layouts/page.mdx A page in your documentation *** title: Docs Page description: A page in your documentation ----------------------------------------- Page is the base element of a documentation, it includes Table of contents, Footer, and Breadcrumb. ## Usage ```tsx title="page.tsx" import { DocsPage, DocsDescription, DocsTitle, DocsBody, } from 'fumadocs-ui/page'; title description

This heading looks good!

It applies the Typography styles, wrap your content here.
; ``` Instead of rendering the title with `DocsTitle` in `page.tsx`, you can put the title into MDX file. This will render the title in the MDX body. ### Edit on GitHub You can also add your own component. ```tsx import { DocsBody } from 'fumadocs-ui/page';
Edit on GitHub ; ``` ## Configurations ### Full Mode To extend the page to fill up all available space, pass `full` to the page component. This will force TOC to be shown as a popover. ```tsx import { DocsPage } from 'fumadocs-ui/page'; ...; ``` ### Table of Contents An overview of all the headings in your article, it requires an array of headings. For Markdown and MDX documents, You can obtain it using the [TOC Utility](/docs/headless/utils/get-toc). Content sources like Fumadocs MDX offer this out-of-the-box. ```tsx import { DocsPage } from 'fumadocs-ui/page'; ...; ``` You can customise or disable it with the `tableOfContent` option, or with `tableOfContentPopover` on smaller devices. ```tsx import { DocsPage } from 'fumadocs-ui/page'; ... ; ``` #### Style You can choose another style for TOC, like `clerk` inspired by [https://clerk.com](https://clerk.com): ```tsx import { DocsPage } from 'fumadocs-ui/page'; ... ; ``` ### Last Updated Time Display last updated time of the page. ```tsx import { DocsPage } from 'fumadocs-ui/page'; ; ``` Since you might use different version controls (e.g. Github) or CMS like Sanity, Fumadocs UI doesn't display the last updated time by default. You can enable [`lastModifiedTime`](/docs/mdx/last-modified). ```tsx import { DocsPage } from 'fumadocs-ui/page'; import { source } from '@/lib/source'; const page = source.getPage(['...']); ; ``` For Github hosted documents, you can use the [`getGithubLastEdit`](/docs/headless/utils/git-last-edit) utility. ```tsx import { DocsPage } from 'fumadocs-ui/page'; import { getGithubLastEdit } from 'fumadocs-core/server'; const time = await getGithubLastEdit({ owner: 'fuma-nama', repo: 'fumadocs', path: `content/docs/${page.path}`, }); ; ``` ### Footer Footer is a navigation element that has two buttons to jump to the next and previous pages. When not specified, it shows the neighbour pages found from page tree. Customise the footer with the `footer` option. ```tsx import { DocsPage, DocsBody } from 'fumadocs-ui/page'; ... ; ``` ### Breadcrumb A navigation element, shown only when user is navigating in folders. # Fumadocs Framework: Root Provider URL: /docs/ui/layouts/root-provider Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/layouts/root-provider.mdx The context provider of Fumadocs UI. *** title: Root Provider description: The context provider of Fumadocs UI. ------------------------------------------------- The context provider of all the components, including `next-themes` and context for search dialog. It should be located at the root layout. ## Usage ```jsx tab="Next.js" import { RootProvider } from 'fumadocs-ui/provider'; export default function Layout({ children }) { return ( {children} ); } ``` ```jsx tab="Others" import { RootProvider } from 'fumadocs-ui/provider/base'; export default function Layout({ children }) { return ( {children} ); } ``` ### Search Dialog Customize or disable the search dialog with `search` option. ```jsx {children} ``` Learn more from [Search](/docs/ui/search). ### Theme Provider Fumadocs supports light/dark modes with [`next-themes`](https://github.com/pacocoursey/next-themes). Customise or disable it with `theme` option. ```jsx {children} ``` # 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/index.mdx Add Fumadocs to existing projects. *** title: Manual Installation description: Add Fumadocs to existing projects. index: true ----------- # Fumadocs Framework: Next.js URL: /docs/ui/manual-installation/next Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/manual-installation/next.mdx Setup Fumadocs on Next.js. *** title: Next.js description: Setup Fumadocs on Next.js. --------------------------------------- Before continuing, make sure you have configured: * Next.js 15. * Tailwind CSS 4. * Fumadocs MDX: follow the [setup guide](/docs/mdx/next) and create essential files like `lib/source.ts`. Fumadocs also supports other content sources, including [Content Collections](/docs/headless/content-collections) and headless CMS. ## Getting Started npm pnpm yarn bun ```bash npm i 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 ``` Create a file for 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, }; } ``` ### Root Layout Wrap the entire application inside [Root Provider](/docs/ui/layouts/root-provider), and add required styles to `body`. ```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} ); } ``` ### Styles Add the following Tailwind CSS styles to `global.css`. ```css title="global.css" @import 'tailwindcss'; /* [!code ++:2] */ @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`. ### Routes Create a `lib/layout.shared.tsx` file to put the shared options for our layouts. ```tsx title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { nav: { title: 'My App', }, }; } ``` Create the following routes: ```tsx tab="app/docs/layout.tsx" import { source } from '@/lib/source'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; export default function Layout({ children }: LayoutProps<'/docs'>) { return ( {children} ); } ``` ```tsx tab="app/docs/[[...slug]]/page.tsx" import { source } from '@/lib/source'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; import { notFound } from 'next/navigation'; import { getMDXComponents } from '@/mdx-components'; import type { Metadata } from 'next'; export default async function Page(props: PageProps<'/docs/[[...slug]]'>) { 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: PageProps<'/docs/[[...slug]]'>, ): Promise { const params = await props.params; const page = source.getPage(params.slug); if (!page) notFound(); return { title: page.data.title, description: page.data.description, }; } ``` ```ts tab="app/api/search/route.ts" import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; export const { GET } = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); ``` > The search is powered by Orama, 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. ### Cloudflare Use [https://opennext.js.org/cloudflare](https://opennext.js.org/cloudflare), Fumadocs doesn't work on Edge runtime. ### 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" # syntax=docker.io/docker/dockerfile:1 FROM node:18-alpine AS base # Install dependencies only when needed FROM base AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat WORKDIR /app # Install dependencies based on the preferred package manager [!code highlight] COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* source.config.ts ./ RUN \ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ else echo "Lockfile not found." && exit 1; \ fi # Rebuild the source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Next.js collects completely anonymous telemetry data about general usage. # Learn more here: https://nextjs.org/telemetry # Uncomment the following line in case you want to disable telemetry during the build. # ENV NEXT_TELEMETRY_DISABLED=1 RUN \ if [ -f yarn.lock ]; then yarn run build; \ elif [ -f package-lock.json ]; then npm run build; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ else echo "Lockfile not found." && exit 1; \ fi # Production image, copy all the files and run next FROM base AS runner WORKDIR /app ENV NODE_ENV=production # Uncomment the following line in case you want to disable telemetry during runtime. # ENV NEXT_TELEMETRY_DISABLED=1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT=3000 # server.js is created by next build from the standalone output # https://nextjs.org/docs/pages/api-reference/config/next-config-js/output ENV HOSTNAME="0.0.0.0" CMD ["node", "server.js"] ``` This ensures Fumadocs MDX can access your configuration file during builds. # Fumadocs Framework: React Router URL: /docs/ui/manual-installation/react-router Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/manual-installation/react-router.mdx Setup Fumadocs on React Router. *** title: React Router description: Setup Fumadocs on React Router. -------------------------------------------- ## Getting Started Before continuing, make sure to configure: * Tailwind CSS 4. * Fumadocs MDX: follow the [setup guide](/docs/mdx/vite/react-router) and create essential files like `lib/source.ts`. ### Installation npm pnpm yarn bun ```bash npm i fumadocs-core fumadocs-ui ``` ```bash pnpm add fumadocs-core fumadocs-ui ``` ```bash yarn add fumadocs-core fumadocs-ui ``` ```bash bun add fumadocs-core fumadocs-ui ``` ### Styles Add the following to your Tailwind CSS file: ```css title="app/app.css" @import 'tailwindcss'; /* [!code ++:2] */ @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; ``` ### Create Pages Update your routes: ```ts title="routes.ts" import { type RouteConfig, index, route } from '@react-router/dev/routes'; export default [ index('routes/home.tsx'), // [!code ++:2] route('docs/*', 'docs/page.tsx'), route('api/search', 'docs/search.ts'), ] satisfies RouteConfig; ``` Create the following files: ```tsx tab="app/lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { nav: { title: 'React Router', }, }; } ``` ```tsx tab="app/docs/page.tsx" import type { Route } from './+types/page'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; import { source } from '@/lib/source'; import { type PageTree } from 'fumadocs-core/server'; import defaultMdxComponents from 'fumadocs-ui/mdx'; import { docs } from '../../source.generated'; import { toClientRenderer } from 'fumadocs-mdx/runtime/vite'; import { baseOptions } from '@/lib/layout.shared'; export async function loader({ params }: Route.LoaderArgs) { const slugs = params['*'].split('/').filter((v) => v.length > 0); const page = source.getPage(slugs); if (!page) throw new Response('Not found', { status: 404 }); return { path: page.path, tree: source.pageTree, }; } const renderer = toClientRenderer( docs.doc, ({ toc, default: Mdx, frontmatter }) => { return ( {frontmatter.title} {frontmatter.title} {frontmatter.description} ); }, ); export default function Page(props: Route.ComponentProps) { const { tree, path } = props.loaderData; const Content = renderer[path]; return ( ); } ``` ```ts tab="app/docs/search.ts" import type { Route } from './+types/search'; import { createFromSource } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; const server = createFromSource(source, { language: 'english', }); export async function loader({ request }: Route.LoaderArgs) { return server.GET(request); } ``` Wrap your entire app under Fumadocs providers: ```tsx title="root.tsx" import { Links, Meta, Scripts, ScrollRestoration } from 'react-router'; import { RootProvider } from 'fumadocs-ui/provider/base'; import { ReactRouterProvider } from 'fumadocs-core/framework/react-router'; import './app.css'; export function Layout({ children }: { children: React.ReactNode }) { return ( {/* [!code ++:3] */} {children} ); } ``` ### Done You can start writing documents at `content/docs`: ```mdx title="content/docs/index.mdx" --- title: Hello World --- I love Fumadocs ``` # Fumadocs Framework: Tanstack Start URL: /docs/ui/manual-installation/tanstack-start Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/manual-installation/tanstack-start.mdx Setup Fumadocs on Tanstack Start. *** title: Tanstack Start description: Setup Fumadocs on Tanstack Start. ---------------------------------------------- ## Getting Started Before continuing, make sure to configure: * Tailwind CSS 4. * Fumadocs MDX: follow the [setup guide](/docs/mdx/vite/tanstack) and create essential files like `lib/source.ts`. ### Installation npm pnpm yarn bun ```bash npm i fumadocs-core fumadocs-ui ``` ```bash pnpm add fumadocs-core fumadocs-ui ``` ```bash yarn add fumadocs-core fumadocs-ui ``` ```bash bun add fumadocs-core fumadocs-ui ``` ### Styles Add the following to your Tailwind CSS file: ```css title="styles/app.css" @import 'tailwindcss'; /* [!code ++:2] */ @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; ``` ### Create Pages Create the following routes: ```tsx tab="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { nav: { title: 'Tanstack Start', }, }; } ``` ```tsx tab="routes/docs/$.tsx" import { createFileRoute, notFound } from '@tanstack/react-router'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { createServerFn } from '@tanstack/react-start'; import { source } from '@/lib/source'; import type { PageTree } from 'fumadocs-core/server'; import { useMemo } from 'react'; import { docs } from '../../../source.generated'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; import defaultMdxComponents from 'fumadocs-ui/mdx'; import { createClientLoader } from 'fumadocs-mdx/runtime/vite'; import { baseOptions } from '@/lib/layout.shared'; export const Route = createFileRoute('/docs/$')({ component: Page, loader: async ({ params }) => { const data = await loader({ data: params._splat?.split('/') ?? [] }); await clientLoader.preload(data.path); return data; }, }); const loader = createServerFn({ method: 'GET', }) .validator((slugs: string[]) => slugs) .handler(async ({ data: slugs }) => { const page = source.getPage(slugs); if (!page) throw notFound(); return { tree: source.pageTree as object, path: page.path, }; }); const clientLoader = createClientLoader(docs.doc, { id: 'docs', component({ toc, frontmatter, default: MDX }) { return ( {frontmatter.title} {frontmatter.description} ); }, }); function Page() { const data = Route.useLoaderData(); const Content = clientLoader.getComponent(data.path); const tree = useMemo( () => transformPageTree(data.tree as PageTree.Folder), [data.tree], ); return ( ); } function transformPageTree(tree: PageTree.Folder): PageTree.Folder { function transform(item: T) { if (typeof item.icon !== 'string') return item; return { ...item, icon: ( ), }; } return { ...tree, index: tree.index ? transform(tree.index) : undefined, children: tree.children.map((item) => { if (item.type === 'folder') return transformPageTree(item); return transform(item); }), }; } ``` ```ts tab="routes/api/search.ts" import { createServerFileRoute } from '@tanstack/react-start/server'; import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; const server = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); export const ServerRoute = createServerFileRoute('/api/search').methods({ GET: async ({ request }) => server.GET(request), }); ``` Wrap your entire app under Fumadocs providers: ```tsx title="__root.tsx" import { createRootRoute, HeadContent, Outlet, Scripts, } from '@tanstack/react-router'; import * as React from 'react'; import { RootProvider } from 'fumadocs-ui/provider/base'; import { TanstackProvider } from 'fumadocs-core/framework/tanstack'; export const Route = createRootRoute({ component: RootComponent, }); function RootComponent() { return ( ); } function RootDocument({ children }: { children: React.ReactNode }) { return ( {/* [!code ++:3] */} {children} ); } ``` ### Done You can start writing documents at `content/docs`: ```mdx title="content/docs/index.mdx" --- title: Hello World --- I love Fumadocs ``` # Fumadocs Framework: Waku URL: /docs/ui/manual-installation/waku Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/manual-installation/waku.mdx Setup Fumadocs on Waku. *** title: Waku description: Setup Fumadocs on Waku. ------------------------------------ ## Getting Started Before continuing, make sure to configure: * Tailwind CSS 4. * Fumadocs MDX: follow the [setup guide](/docs/mdx/vite/waku) and create essential files like `lib/source.ts`. ### Installation npm pnpm yarn bun ```bash npm i fumadocs-core fumadocs-ui ``` ```bash pnpm add fumadocs-core fumadocs-ui ``` ```bash yarn add fumadocs-core fumadocs-ui ``` ```bash bun add fumadocs-core fumadocs-ui ``` ### Styles Add the following to your Tailwind CSS file: ```css title="src/styles/globals.css" @import 'tailwindcss'; /* [!code ++:2] */ @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; ``` ### Create Pages Create a file for sharing layout props: ```tsx title="src/lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { nav: { title: 'Waku', }, }; } ``` Create the following routes: ```tsx tab="src/pages/docs/_layout.tsx" import type { ReactNode } from 'react'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { source } from '@/lib/source'; import { baseOptions } from '@/lib/layout.shared'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ```tsx tab="src/pages/docs/[...slugs].tsx" import { source } from '@/lib/source'; import { PageProps } from 'waku/router'; import defaultMdxComponents from 'fumadocs-ui/mdx'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; export default function DocPage({ slugs }: PageProps<'/docs/[...slugs]'>) { const page = source.getPage(slugs); if (!page) { return (

Page Not Found

The page you are looking for does not exist.

); } const MDX = page.data.body; return ( {page.data.title} {page.data.description} ); } export async function getConfig() { const pages = source .generateParams() .map((item) => (item.lang ? [item.lang, ...item.slug] : item.slug)); return { render: 'static' as const, staticPaths: pages, } as const; } ``` ```ts tab="src/pages/api/search.ts" import { createFromSource } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; export const { GET } = createFromSource(source); ``` Finally, wrap your entire app under Fumadocs providers: ```tsx tab="src/components/provider.tsx" 'use client'; import type { ReactNode } from 'react'; import { WakuProvider } from 'fumadocs-core/framework/waku'; import { RootProvider } from 'fumadocs-ui/provider/base'; export function Provider({ children }: { children: ReactNode }) { return ( {children} ); } ``` ```tsx tab="src/pages/_layout.tsx" import '@/styles/globals.css'; import type { ReactNode } from 'react'; import { Provider } from '@/components/provider'; // [!code ++:3] export default function RootLayout({ children }: { children: ReactNode }) { return {children}; } export const getConfig = async () => { return { render: 'static', }; }; ``` ### Done You can start writing documents at `content/docs`: ```mdx title="content/docs/index.mdx" --- title: Hello World --- I love Fumadocs ``` # Fumadocs Framework: Markdown URL: /docs/ui/markdown Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/markdown/index.mdx How to write documents *** title: Markdown description: How to write documents ----------------------------------- ## Introduction Fumadocs provides many useful extensions to MDX, a markup language. Here is a brief introduction to the default MDX syntax of Fumadocs. > MDX is not the only supported format of Fumadocs. In fact, you can use any renderers such as headless CMS. ## MDX We recommend MDX, a superset of Markdown with JSX syntax. It allows you to import components, and use them in the document, or even writing JavaScript. See: * [MDX Syntax](https://mdxjs.com/docs/what-is-mdx/#mdx-syntax). * GFM (GitHub Flavored Markdown) is also supported, see [GFM Specification](https://github.github.com/gfm). ```mdx import { Component } from './component'; ## Heading ### Heading #### Heading Hello World, **Bold**, _Italic_, ~~Hidden~~ 1. First 2. Second 3. Third - Item 1 - Item 2 > Quote here ![alt](/image.png) | Table | Description | | ----- | ----------- | | Hello | World | ``` ### Frontmatter Fumadocs support YAML-based frontmatter by default, `title` represents the page title (`h1`) in Fumadocs UI. ```mdx --- title: This is a document --- ... ``` You can use the [`schema`](/docs/mdx/collections#schema-1) option to add frontmatter properties. ### Auto Links Internal links use the `` component of your React framework (e.g. `next/link`) to allow prefetching and avoid hard-reload. External links will get the default `rel="noreferrer noopener" target="_blank"` attributes for security. ```mdx [My Link](https://github.github.com/gfm) This also works: https://github.github.com/gfm. ``` ### Cards Useful for adding links. ```mdx import { HomeIcon } from 'lucide-react'; Learn more about caching in Next.js Learn more about `fetch` in Next.js. } href="/" title="Home"> You can include icons too. ``` Learn more about caching in Next.js Learn more about `fetch` in Next.js. } href="/" title="Home"> You can include icons too. #### "Further Reading" Section You can do something like: ```tsx title="page.tsx" import { getPageTreePeers } from 'fumadocs-core/server'; import { source } from '@/lib/source'; {getPageTreePeers(source.pageTree, '/docs/my-page').map((peer) => ( {peer.description} ))} ; ``` This will show the other pages in the same folder as cards. ### Callouts Useful for adding tips/warnings, it is included by default. You can specify the type of callout: * `info` (default) * `warn`/`warning` * `error` * `success` ```mdx Hello World Hello World Hello World ``` Hello World Hello World Hello World ### Headings An anchor is automatically applied to each heading, it sanitizes invalid characters like spaces. (e.g. `Hello World` to `hello-world`) ```md # Hello `World` ``` #### TOC Settings The table of contents (TOC) will be generated based on headings, you can also customise the effects of headings: ```md # Heading [!toc] This heading will be hidden from TOC. # Another Heading [toc] This heading will **only** be visible in TOC, you can use it to add additional TOC items. Like headings rendered in a React component: ``` #### Custom Anchor You can add `[#slug]` to customise heading anchors. ```md # heading [#my-heading-id] ``` You can also chain it with TOC settings like: ```md # heading [toc] [#my-heading-id] ``` To link people to a specific heading, add the heading id to hash fragment: `/page#my-heading-id`. ### Codeblock Syntax Highlighting is supported by default using [Rehype Code](/docs/headless/mdx/rehype-code). ````mdx ```js console.log('Hello World'); ``` ```js title="My Title" console.log('Hello World'); ``` ```` #### Line Numbers Show line numbers, it also works with Twoslash and other transformers. ````mdx tab="Input" ```ts twoslash lineNumbers const a = 'Hello World'; // ^? console.log(a); // [!code highlight] ``` ```` ```ts twoslash lineNumbers tab="Output" const a = 'Hello World'; // ^? console.log(a); // [!code highlight] ``` You can set the initial value of line numbers. ````mdx tab="Input" ```js lineNumbers=4 function main() { console.log('starts from 4'); return 0; } ``` ```` ```js lineNumbers=4 tab="Output" function main() { console.log('starts from 4'); return 0; } ``` #### Shiki Transformers We support some of the [Shiki Transformers](https://shiki.style/packages/transformers), allowing you to highlight/style specific lines. ````md tab="Input" ```tsx // highlight a line
Hello World
// [\!code highlight] // highlight a word // [\!code word:Fumadocs]
Fumadocs
// diff styles console.log('hewwo'); // [\!code --] console.log('hello'); // [\!code ++] // focus return new ResizeObserver(() => {}) // [\!code focus] ``` ```` ```tsx tab="Output" // highlight a line
Hello World
// [!code highlight] // highlight a word // [!code word:Fumadocs]
Fumadocs
// diff styles: console.log('hewwo'); // [!code --] console.log('hello'); // [!code ++] // focus return new ResizeObserver(() => {}) // [!code focus] ``` #### Tab Groups ````mdx ```ts tab="Tab 1" console.log('A'); ``` ```ts tab="Tab 2" console.log('B'); ``` ```` ```ts tab="Tab 1" console.log('A'); ``` ```ts tab="Tab 2" console.log('B'); ``` While disabled by default, you use MDX in tab values by configuring the remark plugin: ```ts tab="Fumadocs MDX" title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkCodeTabOptions: { parseMdx: true, // [!code ++] }, }, }); ``` ```ts tab="MDX Compiler" import { compile } from '@mdx-js/mdx'; import { remarkCodeTab } from 'fumadocs-core/mdx-plugins'; await compile('...', { remarkPlugins: [ [ remarkCodeTab, { parseMdx: true, // [!code ++] }, ], ], }); ``` ````mdx ```ts tab=" Tab 1" console.log('A'); ``` ```ts tab=" Tab 2" console.log('B'); ``` ```` ```ts tab=" Tab 1" console.log('A'); ``` ```ts tab=" Tab 2" console.log('B'); ``` ### Include > This is only available on **Fumadocs MDX**. Reference another file (can also be a Markdown/MDX document). Specify the target file path in `` tag (relative to the MDX file itself). ```mdx title="page.mdx" ./another.mdx ``` See [other usages of include](/docs/mdx/include). ### NPM Commands Useful for generating commands for installing packages with different package managers. ````md tab="Input" ```npm npm i next -D ``` ```` npm pnpm yarn bun ```bash tab="Output" npm i next -D ``` ```bash tab="Output" pnpm add next -D ``` ```bash tab="Output" yarn add next --dev ``` ```bash tab="Output" bun add next --dev ``` When using Fumadocs MDX, you can customise it like: ```tsx title="source.config.ts" import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkNpmOptions: { // ... }, }, }); ``` ### Other Components You can configure & use the [built-in components](/docs/ui/components) in your MDX document, such as Tabs, Accordions and Zoomable Image. ## Additional Features You may be interested: # Fumadocs Framework: Math URL: /docs/ui/markdown/math Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/markdown/math.mdx Writing math equations in Markdown/MDX. *** title: Math description: Writing math equations in Markdown/MDX. ---------------------------------------------------- ## Getting Started npm pnpm yarn bun ```bash npm install remark-math rehype-katex katex ``` ```bash pnpm add remark-math rehype-katex katex ``` ```bash yarn add remark-math rehype-katex katex ``` ```bash bun add remark-math rehype-katex katex ``` ### Add Plugins Add the required remark/rehype plugins, the code might be vary depending on your content source. ```ts title="source.config.ts" tab="Fumadocs MDX" import rehypeKatex from 'rehype-katex'; import remarkMath from 'remark-math'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkMath], // Place it at first, it should be executed before the syntax highlighter rehypePlugins: (v) => [rehypeKatex, ...v], }, }); ``` ### Add Stylesheet Add the following to root layout to make it looks great: ```tsx title="layout.tsx" import 'katex/dist/katex.css'; ``` ### Done Type some TeX expression in your documents, like the Pythagoras theorem: ````mdx Inline: $$c = \pm\sqrt{a^2 + b^2}$$ ```math c = \pm\sqrt{a^2 + b^2} ``` ```` Inline: $$c = \pm\sqrt{a^2 + b^2}$$ ```math c = \pm\sqrt{a^2 + b^2} ``` Taylor Expansion (expressing holomorphic function $$f(x)$$ in power series): ```math \displaystyle {\begin{aligned}T_{f}(z)&=\sum _{k=0}^{\infty }{\frac {(z-c)^{k}}{2\pi i}}\int _{\gamma }{\frac {f(w)}{(w-c)^{k+1}}}\,dw\\&={\frac {1}{2\pi i}}\int _{\gamma }{\frac {f(w)}{w-c}}\sum _{k=0}^{\infty }\left({\frac {z-c}{w-c}}\right)^{k}\,dw\\&={\frac {1}{2\pi i}}\int _{\gamma }{\frac {f(w)}{w-c}}\left({\frac {1}{1-{\frac {z-c}{w-c}}}}\right)\,dw\\&={\frac {1}{2\pi i}}\int _{\gamma }{\frac {f(w)}{w-z}}\,dw=f(z),\end{aligned}} ``` You can actually copy equations on Wikipedia, they will be converted into a KaTeX string when you paste it. ```math \displaystyle S[{\boldsymbol {q}}]=\int _{a}^{b}L(t,{\boldsymbol {q}}(t),{\dot {\boldsymbol {q}}}(t))\,dt. ``` # Fumadocs Framework: Mermaid URL: /docs/ui/markdown/mermaid Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/markdown/mermaid.mdx Rendering diagrams in your docs *** title: Mermaid description: Rendering diagrams in your docs -------------------------------------------- Fumadocs doesn't have a built-in Mermaid wrapper provided, we recommend using `mermaid` directly. ## Setup Install the required dependencies, `next-themes` is used with Fumadocs to manage the light/dark mode. npm pnpm yarn bun ```bash npm install mermaid next-themes ``` ```bash pnpm add mermaid next-themes ``` ```bash yarn add mermaid next-themes ``` ```bash bun add mermaid next-themes ``` Create the Mermaid component: ```tsx title="components/mdx/mermaid.tsx" 'use client'; import { use, useEffect, useId, useState } from 'react'; import { useTheme } from 'next-themes'; export function Mermaid({ chart }: { chart: string }) { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); if (!mounted) return; return ; } const cache = new Map>(); function cachePromise( key: string, setPromise: () => Promise, ): Promise { const cached = cache.get(key); if (cached) return cached as Promise; const promise = setPromise(); cache.set(key, promise); return promise; } function MermaidContent({ chart }: { chart: string }) { const id = useId(); const { resolvedTheme } = useTheme(); const { default: mermaid } = use( cachePromise('mermaid', () => import('mermaid')), ); mermaid.initialize({ startOnLoad: false, securityLevel: 'loose', fontFamily: 'inherit', themeCSS: 'margin: 1.5rem auto 0;', theme: resolvedTheme === 'dark' ? 'dark' : 'default', }); const { svg, bindFunctions } = use( cachePromise(`${chart}-${resolvedTheme}`, () => { return mermaid.render(id, chart.replaceAll('\\n', '\n')); }), ); return (
{ if (container) bindFunctions?.(container); }} dangerouslySetInnerHTML={{ __html: svg }} /> ); } ``` > This is originally inspired by [remark-mermaid](https://github.com/the-guild-org/docs/blob/main/packages/remark-mermaid/src/mermaid.tsx). Add the component as a MDX component: ```tsx title="mdx-components.tsx" import defaultMdxComponents from 'fumadocs-ui/mdx'; import { Mermaid } from '@/components/mdx/mermaid'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, Mermaid, // [!code ++] ...components, }; } ``` ## Usage Use it in MDX files. ```mdx ``` ### As CodeBlock You can convert `mermaid` codeblocks into the MDX usage with the `remarkMdxMermaid` remark plugin. ```tsx tab="Fumadocs MDX" title="source.config.ts" import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins'; import { defineConfig } from 'fumadocs-mdx/config'; export default defineConfig({ mdxOptions: { remarkPlugins: [remarkMdxMermaid], }, }); ``` ````md ```mermaid graph TD; A-->B; A-->C; ``` ```` # Fumadocs Framework: Twoslash URL: /docs/ui/markdown/twoslash Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/markdown/twoslash.mdx Use Typescript Twoslash in your docs *** title: Twoslash description: Use Typescript Twoslash in your docs ------------------------------------------------- ## Usage Thanks to the Twoslash integration of [Shiki](https://github.com/shikijs/shiki), the default code syntax highlighter, it is as simple as adding a transformer. npm pnpm yarn bun ```bash npm install fumadocs-twoslash twoslash ``` ```bash pnpm add fumadocs-twoslash twoslash ``` ```bash yarn add fumadocs-twoslash twoslash ``` ```bash bun add fumadocs-twoslash twoslash ``` For Next.js, you need to externalize the following deps: ```js title="next.config.mjs (Next.js)" const config = { reactStrictMode: true, // [!code ++] serverExternalPackages: ['typescript', 'twoslash'], }; ``` Add to your Shiki transformers. ```ts twoslash title="source.config.ts (Fumadocs MDX)" import { defineConfig } from 'fumadocs-mdx/config'; import { transformerTwoslash } from 'fumadocs-twoslash'; import { rehypeCodeDefaultOptions } from 'fumadocs-core/mdx-plugins'; export default defineConfig({ mdxOptions: { rehypeCodeOptions: { themes: { light: 'github-light', dark: 'github-dark', }, transformers: [ ...(rehypeCodeDefaultOptions.transformers ?? []), transformerTwoslash(), ], }, }, }); ``` Add styles, Tailwind CSS v4 is required. ```css title="Tailwind CSS" @import 'fumadocs-twoslash/twoslash.css'; ``` Add MDX components. ```tsx title="mdx-components.tsx" import * as Twoslash from 'fumadocs-twoslash/ui'; import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, ...Twoslash, ...components, }; } ``` Now you can add `twoslash` meta string to codeblocks. ````md ```ts twoslash console.log('Hello World'); ``` ```` ### Example Learn more about [Twoslash notations](https://twoslash.netlify.app/refs/notations). ```ts twoslash title="Test" lineNumbers type Player = { /** * The player name * @default 'user' */ name: string; }; // ---cut--- // @noErrors console.g; // ^| const player: Player = { name: 'Hello World' }; // ^? ``` ```ts twoslash const a = '123'; console.log(a); // ^^^ ``` ```ts twoslash import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./museum.yaml'], output: './content/docs/ui', }); ``` ```ts twoslash // @errors: 2588 const a = '123'; a = 132; ``` ## Cache You can enable filesystem cache with `typesCache` option: ```ts twoslash title="source.config.ts" import { transformerTwoslash } from 'fumadocs-twoslash'; import { createFileSystemTypesCache } from 'fumadocs-twoslash/cache-fs'; transformerTwoslash({ typesCache: createFileSystemTypesCache(), }); ``` # Fumadocs Framework: Code Block URL: /docs/ui/mdx/codeblock Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/mdx/codeblock.mdx Displaying Shiki highlighted code blocks *** title: Code Block description: Displaying Shiki highlighted code blocks -----------------------------------------------------
```js title="config.js" import createMDX from 'fumadocs-mdx/config'; const withMDX = createMDX(); // [!code word:config] /** @type {import('next').NextConfig} */ const config = { // [!code highlight] reactStrictMode: true, // [!code highlight] }; // [!code highlight] export default withMDX(config); ```
This is a MDX component to be used with [Rehype Code](/docs/headless/mdx/rehype-code) to display highlighted codeblocks. Supported features: * Copy button * Custom titles and icons > If you're looking for an equivalent with runtime syntax highlighting, see [Dynamic Code Block](/docs/ui/components/dynamic-codeblock). ## Usage Wrap the pre element in ``, which acts as the wrapper of code block. ```tsx title="mdx-components.tsx" import defaultComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, // HTML `ref` attribute conflicts with `forwardRef` pre: ({ ref: _ref, ...props }) => (
{props.children}
{/* [!code highlight] */}
), ...components, }; } ``` See [Markdown](/docs/ui/markdown#codeblock) for usages. ### Keep Background Use the background color generated by Shiki. ```tsx import { Pre, CodeBlock } from 'fumadocs-ui/components/codeblock';
{props.children}
; ``` ### Icons Specify a custom icon by passing an `icon` prop to `CodeBlock` component. By default, the icon will be injected by the custom Shiki transformer. ```js title="config.js" console.log('js'); ``` # Fumadocs Framework: MDX URL: /docs/ui/mdx Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/mdx/index.mdx Default MDX Components *** title: MDX description: Default MDX Components ----------------------------------- ## Usage The default MDX components include Cards, Callouts, Code Blocks and Headings. ```ts import defaultMdxComponents from 'fumadocs-ui/mdx'; ``` ### Relative Link To support links with relative file path in `href`, override the default `a` component with: ```tsx title="app/docs/[[...slug]]/page.tsx" import { createRelativeLink } from 'fumadocs-ui/mdx'; import { source } from '@/lib/source'; import { getMDXComponents } from '@/mdx-components'; const page = source.getPage(['...']); return ( ); ``` ```mdx [My Link](./file.mdx) ``` [Example: `../(integrations)/open-graph.mdx`](../\(integrations\)/open-graph.mdx) Server Component only. # Fumadocs Framework: Navigation URL: /docs/ui/navigation Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/navigation/index.mdx Configure navigation in your Fumadocs app. *** title: Navigation description: Configure navigation in your Fumadocs app. index: true ----------- # Fumadocs Framework: Layout Links URL: /docs/ui/navigation/links Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/navigation/links.mdx Customise the shared navigation links on all layouts. *** title: Layout Links description: Customise the shared navigation links on all layouts. ------------------------------------------------------------------ ## Overview Fumadocs allows adding additional links to your layouts with a `links` prop, like linking to your "showcase" page.
<>![Nav](/docs/nav-layout-home.png) <>![Nav](/docs/nav-layout-docs.png)
```tsx tab="Shared Options" title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [], // [!code highlight] // other options} }; } ``` ```tsx tab="Docs Layout" title="app/docs/layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` ```tsx tab="Home Layout" title="app/(home)/layout.tsx" import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { baseOptions } from '@/lib/layout.shared'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` You can see all supported items below: ### Link Item A link to navigate to a URL/href, can be external. ```tsx title="lib/layout.shared.tsx" import { BookIcon } from 'lucide-react'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [ { icon: , text: 'Blog', url: '/blog', // secondary items will be displayed differently on navbar secondary: false, }, ], }; } ``` #### Active Mode The conditions to be marked as active. | Mode | Description | | ------------ | ----------------------------------------------------------- | | `url` | When browsing the specified url | | `nested-url` | When browsing the url and its child pages like `/blog/post` | | `none` | Never be active | ```tsx title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [ { text: 'Blog', url: '/blog', active: 'nested-url', }, ], }; } ``` ### Icon Item Same as link item, but is shown as an icon button. Icon items are secondary by default. ```tsx title="lib/layout.shared.tsx" import { BookIcon } from 'lucide-react'; import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [ { type: 'icon', label: 'Visit Blog', // `aria-label` icon: , text: 'Blog', url: '/blog', }, ], }; } ``` ### Custom Item Display a custom component. ```tsx title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [ { type: 'custom', children: , secondary: true, }, ], }; } ``` ### GitHub URL There's also a shortcut for adding GitHub repository link item. ```tsx twoslash title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { githubUrl: 'https://github.com', }; } ``` ### Normal Menu A menu containing multiple link items. ```tsx title="lib/layout.shared.tsx" import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; export function baseOptions(): BaseLayoutProps { return { links: [ { type: 'menu', text: 'Guide', items: [ { text: 'Getting Started', description: 'Learn to use Fumadocs', url: '/docs', }, ], }, ], }; } ``` ### Navigation Menu In Home Layout, you can add navigation menu (fully animated) to the navbar. ![Nav](/docs/nav-layout-menu.png) ```tsx title="app/(home)/layout.tsx" import { baseOptions } from '@/lib/layout.shared'; import type { ReactNode } from 'react'; import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { NavbarMenu, NavbarMenuContent, NavbarMenuLink, NavbarMenuTrigger, } from 'fumadocs-ui/layouts/home/navbar'; export default function Layout({ children }: { children: ReactNode }) { return ( Documentation Hello World ), }, // other items ]} > {children} ); } ``` # Fumadocs Framework: Sidebar Links URL: /docs/ui/navigation/sidebar Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/navigation/sidebar.mdx Customise sidebar navigation links on Docs Layout. *** title: Sidebar Links description: Customise sidebar navigation links on Docs Layout. --------------------------------------------------------------- ## Overview
![Sidebar](/docs/sidebar.png)
Sidebar items are rendered from the page tree you passed to ``. For `source.pageTree`, it generates the tree from your file structure, see [available patterns](/docs/ui/page-conventions). ```tsx title="layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { source } from '@/lib/source'; import type { ReactNode } from 'react'; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` You may hardcode it too: ```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} ); } ``` ### Sidebar Tabs (Dropdown) \[#sidebar-tabs] Sidebar Tabs are folders with tab-like behaviours, only the content of opened tab will be visible.
![Sidebar Tabs](/docs/sidebar-tabs.png)
By default, the tab trigger will be displayed as a **Dropdown** component (hidden unless one of its items is active). You can add items by marking folders as [Root Folders](/docs/ui/page-conventions#root-folder), create a `meta.json` file in the folder: ```json title="content/docs/my-folder/meta.json" { "title": "Name of Folder", "description": "The description of root folder (optional)", "root": true } ``` Or specify them explicitly: ```tsx title="/app/docs/layout.tsx" import { DocsLayout } from 'fumadocs-ui/layouts/docs'; ; ``` Set it to `false` to disable: ```tsx import { DocsLayout } from 'fumadocs-ui/layouts/docs'; ; ``` You can specify a `banner` to the [Docs Layout](/docs/ui/layouts/docs) component. ```tsx import { DocsLayout, type DocsLayoutProps } from 'fumadocs-ui/layouts/docs'; import type { ReactNode } from 'react'; import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; const docsOptions: DocsLayoutProps = { ...baseOptions(), tree: source.pageTree, sidebar: { banner:
Hello World
, }, }; export default function Layout({ children }: { children: ReactNode }) { return {children}; } ```
#### Decoration Change the icon/styles of tabs. ```tsx import { DocsLayout } from 'fumadocs-ui/layouts/docs'; ({ ...option, icon: , }), }, }} />; ``` # Fumadocs Framework: Algolia URL: /docs/ui/search/algolia Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search/algolia.mdx Using Algolia with Fumadocs UI. *** title: Algolia description: Using Algolia with Fumadocs UI. -------------------------------------------- ## Overview 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. ## Setup Create a search dialog, replace `appId`, `apiKey` and `indexName` with your desired values. ```tsx title="components/search.tsx" 'use client'; import { liteClient } from 'algoliasearch/lite'; import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogFooter, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; const appId = 'replace me'; const apiKey = 'replace me'; const client = liteClient(appId, apiKey); export default function CustomSearchDialog(props: SharedProps) { const { locale } = useI18n(); // (optional) for i18n const { search, setSearch, query } = useDocsSearch({ type: 'algolia', client, indexName: 'document', locale, }); return ( Search powered by Algolia ); } ``` `useDocsSearch()` doesn't use instant search (their official Javascript client). ### Replace Search Dialog Replace the search dialog with yours from ``: ```tsx import { RootProvider } from 'fumadocs-ui/provider'; // [!code ++] import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; {children} ; ``` If it was in a server component, you would need a separate client component for provider to pass functions: ```tsx tab="provider.tsx" 'use client'; import { RootProvider } from 'fumadocs-ui/provider'; import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; 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 ( {/* [!code --] */} {children} {/* [!code ++] */} {children} ); } ``` ### Tag Filter Optionally, you can add UI for filtering results by tags. Configure [Tag Filter](/docs/headless/search/algolia#tag-filter) on search server and add the following: ```tsx 'use client'; import { SearchDialog, SearchDialogContent, SearchDialogFooter, SearchDialogOverlay, type SharedProps, TagsList, TagsListItem, } from 'fumadocs-ui/components/dialog/search'; import { useState } from 'react'; import { useDocsSearch } from 'fumadocs-core/search/client'; export default function CustomSearchDialog(props: SharedProps) { // [!code ++] const [tag, setTag] = useState(); const { search, setSearch, query } = useDocsSearch({ tag, // [!code ++] // other options depending on your search engine }); return ( ... {/* [!code ++:3] */} My Value ); } ``` # Fumadocs Framework: Search URL: /docs/ui/search Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search/index.mdx Implement document search in your docs *** title: Search description: Implement document search in your docs --------------------------------------------------- ## Search UI You can customise some configurations from root provider. For example, to disable search UI: ```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} ; ``` ## Search Client You can choose & configure the search client according to your search engine, it defaults to Orama search. ### Community Integrations A list of integrations maintained by community. * [Trieve Search](/docs/headless/search/trieve) ### Highlight Matches Search integrations can provide `contentWithHighlights` to highlight matches. After configuring search client UI according to the guides above, you can customise how it is rendered. ```tsx title="components/search.tsx" ( { // ... }} /> )} /> ``` # Fumadocs Framework: Mixedbread URL: /docs/ui/search/mixedbread Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search/mixedbread.mdx Using Mixedbread with Fumadocs UI. *** title: Mixedbread description: Using Mixedbread with Fumadocs UI. ----------------------------------------------- ## Overview For the setup guide, see [Integrate Mixedbread Search](/docs/headless/search/mixedbread). ## Setup Create a search dialog, replace `apiKey` and `vectorStoreId` with your desired values. ```tsx title="components/search.tsx" 'use client'; import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogFooter, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; import Mixedbread from '@mixedbread/sdk'; const client = new Mixedbread({ apiKey: 'mxb_xxxx', baseURL: 'https://api.example.com', // Optional, defaults to https://api.mixedbread.com }); export default function CustomSearchDialog(props: SharedProps) { const { locale } = useI18n(); // (optional) for i18n const { search, setSearch, query } = useDocsSearch({ type: 'mixedbread', client, vectorStoreId: 'vectorStoreId', locale, }); return ( Search powered by Mixedbread ); } ``` ### Replace Search Dialog Replace the search dialog with yours from ``: ```tsx import { RootProvider } from 'fumadocs-ui/provider'; // [!code ++] import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; {children} ; ``` If it was in a server component, you would need a separate client component for provider to pass functions: ```tsx tab="provider.tsx" 'use client'; import { RootProvider } from 'fumadocs-ui/provider'; import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; 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 ( {/* [!code --] */} {children} {/* [!code ++] */} {children} ); } ``` ### Tag Filter Optionally, you can add UI for filtering results by tags. Configure [Tag Filter](/docs/headless/search/mixedbread#tag-filter) on search server and add the following: ```tsx 'use client'; import { SearchDialog, SearchDialogContent, SearchDialogFooter, SearchDialogOverlay, type SharedProps, TagsList, TagsListItem, } from 'fumadocs-ui/components/dialog/search'; import { useState } from 'react'; import { useDocsSearch } from 'fumadocs-core/search/client'; export default function CustomSearchDialog(props: SharedProps) { // [!code ++] const [tag, setTag] = useState(); const { search, setSearch, query } = useDocsSearch({ tag, // [!code ++] // other options depending on your search engine }); return ( ... {/* [!code ++:3] */} My Value ); } ``` # Fumadocs Framework: Orama Cloud URL: /docs/ui/search/orama-cloud Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search/orama-cloud.mdx Using Orama Cloud with Fumadocs UI. *** title: Orama Cloud description: Using Orama Cloud with Fumadocs UI. ------------------------------------------------ ## Setup For the setup guide, see [Integrate Orama Cloud](/docs/headless/search/orama-cloud). Create a search dialog, replace `endpoint` and `api_key` with your desired values. ```tsx title="components/search.tsx" 'use client'; import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogFooter, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { OramaClient } from '@oramacloud/client'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; const client = new OramaClient({ endpoint: 'Endpoint URL', api_key: 'API Key', }); export default function CustomSearchDialog(props: SharedProps) { const { locale } = useI18n(); // (optional) for i18n const { search, setSearch, query } = useDocsSearch({ type: 'orama-cloud', client, locale, }); return ( Search powered by Orama ); } ``` ### Replace Search Dialog Replace the search dialog with yours from ``: ```tsx import { RootProvider } from 'fumadocs-ui/provider'; // [!code ++] import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; {children} ; ``` If it was in a server component, you would need a separate client component for provider to pass functions: ```tsx tab="provider.tsx" 'use client'; import { RootProvider } from 'fumadocs-ui/provider'; import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; 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 ( {/* [!code --] */} {children} {/* [!code ++] */} {children} ); } ``` # Fumadocs Framework: Orama (default) URL: /docs/ui/search/orama Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/search/orama.mdx The default search engine powered by Orama. *** title: Orama (default) description: The default search engine powered by Orama. -------------------------------------------------------- ## Overview Fumadocs configures [Orama search engine](/docs/headless/search/orama) out-of-the-box. It works through a API endpoint (running on server), or a statically cached JSON file for static websites. ## Setup Create a search dialog. The UI has been configured by default, you can also re-create it for further customisations: ```tsx title="components/search.tsx" 'use client'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; export default function DefaultSearchDialog(props: SharedProps) { const { locale } = useI18n(); // (optional) for i18n const { search, setSearch, query } = useDocsSearch({ type: 'fetch', locale, }); return ( ); } ``` For Static Export, you can configure [static mode](/docs/headless/search/orama#static-export) on search server, and use the `static` client: npm pnpm yarn bun ```bash npm install @orama/orama ``` ```bash pnpm add @orama/orama ``` ```bash yarn add @orama/orama ``` ```bash bun add @orama/orama ``` ```tsx title="components/search.tsx" 'use client'; import { SearchDialog, SearchDialogClose, SearchDialogContent, SearchDialogHeader, SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay, type SharedProps, } from 'fumadocs-ui/components/dialog/search'; import { useDocsSearch } from 'fumadocs-core/search/client'; import { create } from '@orama/orama'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; function initOrama() { return create({ schema: { _: 'string' }, // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); } export default function DefaultSearchDialog(props: SharedProps) { const { locale } = useI18n(); // (optional) for i18n const { search, setSearch, query } = useDocsSearch({ type: 'static', initOrama, locale, }); return ( ); } ``` ### Replace Search Dialog Replace the search dialog with yours from ``: ```tsx import { RootProvider } from 'fumadocs-ui/provider'; // [!code ++] import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; {children} ; ``` If it was in a server component, you would need a separate client component for provider to pass functions: ```tsx tab="provider.tsx" 'use client'; import { RootProvider } from 'fumadocs-ui/provider'; import SearchDialog from '@/components/search'; import type { ReactNode } from 'react'; 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 ( {/* [!code --] */} {children} {/* [!code ++] */} {children} ); } ``` ### Tag Filter Optionally, you can add UI for filtering results by tags. Configure [Tag Filter](/docs/headless/search/orama#tag-filter) on search server and add the following: ```tsx 'use client'; import { SearchDialog, SearchDialogContent, SearchDialogFooter, SearchDialogOverlay, type SharedProps, TagsList, TagsListItem, } from 'fumadocs-ui/components/dialog/search'; import { useState } from 'react'; import { useDocsSearch } from 'fumadocs-core/search/client'; export default function CustomSearchDialog(props: SharedProps) { // [!code ++] const [tag, setTag] = useState(); const { search, setSearch, query } = useDocsSearch({ tag, // [!code ++] // other options depending on your search engine }); return ( ... {/* [!code ++:3] */} My Value ); } ``` # Fumadocs MDX (the built-in content source): Bun URL: /docs/mdx/loader/bun Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/loader/bun.mdx Access content in Bun. *** title: Bun description: Access content in Bun. ----------------------------------- ## Setup Register the plugin in preload script: ```toml tab="bunfig.toml" preload = ["./scripts/preload.ts"] ``` ```ts tab="scripts/preload.ts" import { createMdxPlugin } from 'fumadocs-mdx/bun'; Bun.plugin(createMdxPlugin()); ``` Now running any script with Bun will automatically support accessing your content files. # Fumadocs MDX (the built-in content source): Loader URL: /docs/mdx/loader Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/loader/index.mdx Fumadocs MDX loader integration. *** title: Loader description: Fumadocs MDX loader integration. --------------------------------------------- ## Introduction By default, Fumadocs MDX requires a bundler to work. When you are accessing your content without a bundler, such as: * A script executed by Node.js or Bun. * **Config files:** config files like `react-router.config.ts` are executed without a bundler. * Any unbundled environment. You will need a runtime loader. See below for available loader integrations. # Fumadocs MDX (the built-in content source): Node.js URL: /docs/mdx/loader/node Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/loader/node.mdx Access content in Node.js runtime. *** title: Node.js description: Access content in Node.js runtime. ----------------------------------------------- ## Setup Make sure to run the script under ESM environment. ```js title="scripts/example.js" import { register } from 'node:module'; register('fumadocs-mdx/node/loader', import.meta.url); // accessing content const { source } = await import('./lib/source'); console.log(source.getPages()); ``` # Fumadocs MDX (the built-in content source): React Router URL: /docs/mdx/vite/react-router Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/vite/react-router.mdx Use Fumadocs MDX with React Router *** title: React Router description: Use Fumadocs MDX with React Router ----------------------------------------------- ## Setup npm pnpm yarn bun ```bash npm i fumadocs-mdx fumadocs-core @types/mdx ``` ```bash pnpm add fumadocs-mdx fumadocs-core @types/mdx ``` ```bash yarn add fumadocs-mdx fumadocs-core @types/mdx ``` ```bash bun add fumadocs-mdx fumadocs-core @types/mdx ``` Create the configuration file: ```ts title="source.config.ts" import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', }); export default defineConfig(); ``` Add the Vite plugin: ```ts title="vite.config.ts" import { reactRouter } from '@react-router/dev/vite'; import tailwindcss from '@tailwindcss/vite'; import { defineConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; import mdx from 'fumadocs-mdx/vite'; import * as MdxConfig from './source.config'; export default defineConfig({ plugins: [mdx(MdxConfig), tailwindcss(), reactRouter(), tsconfigPaths()], }); ``` ### Integrate with Fumadocs To integrate with Fumadocs, make a `lib/source.ts` file: ```ts title="app/lib/source.ts" import { loader } from 'fumadocs-core/source'; import { create, docs } from '../../source.generated'; export const source = loader({ source: await create.sourceAsync(docs.doc, docs.meta), baseUrl: '/docs', }); ``` The `source.generated.ts` file will be generated when you run development server or production build. ### Done The configuration is now finished. ## Examples ### Rendering Content As React Router doesn't support RSC at the moment, use `toClientRenderer()` to lazy load MDX content as a component on browser. For example: ```tsx import type { Route } from './+types/page'; import { source } from '@/lib/source'; import { docs } from '../../source.generated'; import { toClientRenderer } from 'fumadocs-mdx/runtime/vite'; export async function loader({ params }: Route.LoaderArgs) { const slugs = params['*'].split('/').filter((v) => v.length > 0); const page = source.getPage(slugs); if (!page) throw new Response('Not found', { status: 404 }); return { path: page.path, }; } const renderer = toClientRenderer(docs.doc, ({ default: Mdx, frontmatter }) => { return (

{frontmatter.title}

); }); export default function Page(props: Route.ComponentProps) { const { path } = props.loaderData; const Content = renderer[path]; return ; } ``` Note that you can import the `source.generated.ts` file directly, it's useful to access compiled content without `loader()`. ```ts import { docs } from './source.generated'; console.log(docs); ``` # Fumadocs MDX (the built-in content source): Tanstack Start URL: /docs/mdx/vite/tanstack Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/vite/tanstack.mdx Use Fumadocs MDX with Tanstack Start & Router *** title: Tanstack Start description: Use Fumadocs MDX with Tanstack Start & Router ---------------------------------------------------------- ## Setup npm pnpm yarn bun ```bash npm i fumadocs-mdx fumadocs-core @types/mdx ``` ```bash pnpm add fumadocs-mdx fumadocs-core @types/mdx ``` ```bash yarn add fumadocs-mdx fumadocs-core @types/mdx ``` ```bash bun add fumadocs-mdx fumadocs-core @types/mdx ``` Create the configuration file: ```ts title="source.config.ts" import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', }); export default defineConfig(); ``` Add the Vite plugin: ```ts title="vite.config.ts" import react from '@vitejs/plugin-react'; import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import { defineConfig } from 'vite'; import tsConfigPaths from 'vite-tsconfig-paths'; import tailwindcss from '@tailwindcss/vite'; import mdx from 'fumadocs-mdx/vite'; export default defineConfig({ server: { port: 3000, }, plugins: [ mdx(await import('./source.config')), tailwindcss(), tsConfigPaths({ projects: ['./tsconfig.json'], }), tanstackStart({ customViteReactPlugin: true, prerender: { enabled: true, }, }), react(), ], }); ``` ### Integrate with Fumadocs To integrate with Fumadocs, create a `lib/source.ts` file: ```ts title="src/lib/source.ts" import { loader } from 'fumadocs-core/source'; import { create, docs } from '../../source.generated'; export const source = loader({ source: await create.sourceAsync(docs.doc, docs.meta), baseUrl: '/docs', }); ``` The `source.generated.ts` file will be generated when you run development server or production build. ### Done The configuration is now finished. ## Examples ### Rendering Content As Tanstack Start doesn't support RSC at the moment, use `createClientLoader()` to lazy load MDX content as a component on browser. For example: ```tsx title="src/routes/docs/$.tsx" import { createFileRoute, notFound } from '@tanstack/react-router'; import { createServerFn } from '@tanstack/react-start'; import { source } from '@/lib/source'; import { docs } from '../../../source.generated'; import { createClientLoader } from 'fumadocs-mdx/runtime/vite'; export const Route = createFileRoute('/docs/$')({ component: Page, loader: async ({ params }) => { const data = await loader({ data: params._splat?.split('/') ?? [] }); await clientLoader.preload(data.path); return data; }, }); const loader = createServerFn({ method: 'GET', }) .validator((slugs: string[]) => slugs) .handler(async ({ data: slugs }) => { const page = source.getPage(slugs); if (!page) throw notFound(); return { path: page.path, }; }); const clientLoader = createClientLoader(docs.doc, { id: 'docs', component({ frontmatter, default: MDX }) { return (

{frontmatter.title}

); }, }); function Page() { const data = Route.useLoaderData(); const Content = clientLoader.getComponent(data.path); return ; } ``` > As you see, the `source.generated.ts` file can be imported directly: > > ```ts > import { docs } from './source.generated'; > console.log(docs); > ``` # Fumadocs MDX (the built-in content source): Waku URL: /docs/mdx/vite/waku Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/mdx/(integrations)/vite/waku.mdx Use Fumadocs MDX with Waku *** title: Waku description: Use Fumadocs MDX with Waku --------------------------------------- ## Setup npm pnpm yarn bun ```bash npm i fumadocs-mdx fumadocs-core fumadocs-ui @types/mdx ``` ```bash pnpm add fumadocs-mdx fumadocs-core fumadocs-ui @types/mdx ``` ```bash yarn add fumadocs-mdx fumadocs-core fumadocs-ui @types/mdx ``` ```bash bun add fumadocs-mdx fumadocs-core fumadocs-ui @types/mdx ``` Create the configuration file: ```ts title="source.config.ts" import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; export const docs = defineDocs({ dir: 'content/docs', }); export default defineConfig(); ``` Add the Vite plugin: ```ts title="vite.config.ts" import { defineConfig } from 'waku/config'; import mdx from 'fumadocs-mdx/vite'; import * as MdxConfig from './source.config.js'; import tailwindcss from '@tailwindcss/vite'; import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ vite: { plugins: [tailwindcss(), mdx(MdxConfig), tsconfigPaths()], }, }); ``` ### Integrate with Fumadocs Create a `lib/source.ts` file: ```ts title="src/lib/source.ts" import { loader } from 'fumadocs-core/source'; import { create, docs } from '../../source.generated.js'; export const source = loader({ source: await create.sourceAsync(docs.doc, docs.meta), baseUrl: '/docs', }); ``` The `source.generated.ts` file will be generated when you run development server or production build. ### Done Have fun! ## Examples ### Rendering Pages Since Waku supports RSC, you can use the exported `default` component directly. ```tsx import { source } from '@/lib/source'; import type { PageProps } from 'waku/router'; import defaultMdxComponents from 'fumadocs-ui/mdx'; import { DocsBody, DocsDescription, DocsPage, DocsTitle, } from 'fumadocs-ui/page'; export default async function DocPage({ slugs, }: PageProps<'/docs/[...slugs]'>) { const page = source.getPage(slugs); if (!page) { // ... } const MDX = page.data.default; return ( {page.data.title} {page.data.description} ); } ``` # Fumadocs Framework: Configurations URL: /docs/ui/openapi/configurations Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/openapi/configurations.mdx Customise Fumadocs OpenAPI *** title: Configurations description: Customise Fumadocs OpenAPI --------------------------------------- ## File Generator Pass options to the `generateFiles` function. ### Input * OpenAPI Server object. * anything supported by [`input`](#input-1). ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./unkey.json'], }); ``` ### Output The output directory. ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ output: '/content/docs', }); ``` ### Per Customise how the page is generated, default to `operation`. Operation refers to an API endpoint with specific method like `/api/weather:GET`. | mode | | output | | --------- | --------------------------- | ------------------------------------- | | tag | operations with same tag | `{tag_name}.mdx` | | file | operations in schema schema | `{file_name}.mdx` | | operation | each operation | `{operationId ?? endpoint_path}.mdx`) | ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ per: 'tag', }); ``` ### Group By In `operation` mode, you can group output files with folders. | value | output | | ----- | ------------------------------------------------------ | | tag | `{tag}/{operationId ?? endpoint_path}.mdx` | | route | `{endpoint_path}/{method}.mdx` (ignores `name` option) | | none | `{operationId ?? endpoint_path}.mdx` (default) | ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ per: 'operation', groupBy: 'tag', }); ``` ### Index Generate index files with cards linking to generated pages. ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./petstore.yaml', './museum.yaml'], output: './content/docs', index: { // for generating `href` url: { baseUrl: '/docs', contentDir: './content/docs', }, items: [ { path: 'index.mdx', // optional: restrict the input files only: ['petstore.yaml'], // optional: set frontmatter description: 'All available pages', }, ], }, }); ``` ### Imports Add custom imports to generated MDX files. This is useful for including components, utilities, or other dependencies in your generated documentation. ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./petstore.yaml'], output: './content/docs', imports: [ { names: ['CustomComponent', 'AnotherComponent'], from: '@/components/custom', }, { names: ['utils'], from: '@/lib/utils', }, { names: ['API_BASE_URL'], from: '@/constants', }, ], }); ``` This will add the following imports to all generated MDX files: ```ts import { CustomComponent, AnotherComponent } from '@/components/custom'; import { utils } from '@/lib/utils'; import { API_BASE_URL } from '@/constants'; ``` The `imports` configuration is especially important for Cards generation in index files, where you need to import `source` and `getPageTreePeers` for the navigation functionality to work. ### Name A function that controls the output path of MDX pages. ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ name: (output, document) => { if (output.type === 'operation') { const operation = document.paths![output.item.path]![output.item.method]!; // operation object console.log(operation); return 'my-dir/filename'; } const hook = document.webhooks![output.item.name][output.item.method]!; // webhook object console.log(hook); return 'my-dir/filename'; }, }); ``` ### Frontmatter Customise the frontmatter of MDX files. By default, it includes: | property | description | | ------------- | ------------------------------------------------ | | `title` | Page title | | `description` | Page description | | `full` | Always true, added for Fumadocs UI | | `method` | Available method of operation (`operation` mode) | | `route` | Route of operation (`operation` mode) | ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./petstore.yaml'], output: './content/docs', frontmatter: (title, description) => ({ myProperty: 'hello', }), }); ``` ### Add Generated Comment Add a comment to the top of generated files indicating they are auto-generated. ```ts import { generateFiles } from 'fumadocs-openapi'; void generateFiles({ input: ['./petstore.yaml'], output: './content/docs', // Add default comment addGeneratedComment: true, // Or provide a custom comment addGeneratedComment: 'Custom auto-generated comment', // Or disable comments addGeneratedComment: false, }); ``` ### Tag Display Name Adding `x-displayName` to OpenAPI Schema can control the display name of your tags. ```yaml title="openapi.yaml" tags: - name: test description: this is a tag. x-displayName: My Test Name ``` ## OpenAPI Server The server to render pages. ### Input * File Paths * External URLs * A function (see below) ```ts tab="Basic" import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ input: ['./unkey.json'], }); ``` ```ts tab="Function" import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ async input() { return { // [id]: downloaded OpenAPI Schema my_schema: await fetch( 'https://registry.scalar.com/@scalar/apis/galaxy/latest?format=json', ).then((res) => res.json()), }; }, }); ``` ### Generate Code Samples Generate custom code samples for each API endpoint. Make sure to install the types package to give you type-safety when customising it: npm pnpm yarn bun ```bash npm install openapi-types -D ``` ```bash pnpm add openapi-types -D ``` ```bash yarn add openapi-types --dev ``` ```bash bun add openapi-types --dev ``` ```ts import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ generateCodeSamples(endpoint) { return [ { lang: 'js', label: 'JavaScript SDK', source: "console.log('hello')", }, ]; }, }); ``` In addition, you can also specify code samples via OpenAPI schema. ```yaml paths: /plants: get: x-codeSamples: - lang: js label: JavaScript SDK source: | const planter = require('planter'); planter.list({ unwatered: true }); ``` #### Disable Code Sample You can disable the code sample for a specific language, for example, to disable cURL: ```ts import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ generateCodeSamples(endpoint) { return [ { lang: 'curl', label: 'cURL', source: false, }, ]; }, }); ``` ### Renderer Customise components in the page. ```ts import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ renderer: { Root(props) { // your own (server) component }, }, }); ``` # Fumadocs Framework: OpenAPI URL: /docs/ui/openapi Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/openapi/index.mdx Generating docs for OpenAPI schema *** title: OpenAPI description: Generating docs for OpenAPI schema ----------------------------------------------- It only works under RSC environments. ## Manual Setup Install the required packages. npm pnpm yarn bun ```bash npm i fumadocs-openapi shiki ``` ```bash pnpm add fumadocs-openapi shiki ``` ```bash yarn add fumadocs-openapi shiki ``` ```bash bun add fumadocs-openapi shiki ``` If you encountered any issues, please check [#2223](https://github.com/fuma-nama/fumadocs/issues/2223). ### Generate Styles Add the following line: ```css title="Tailwind CSS" @import 'tailwindcss'; @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; /* [!code ++] */ @import 'fumadocs-openapi/css/preset.css'; ``` ### Configure Pages Create an OpenAPI instance on the server. ```ts title="lib/openapi.ts" import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ // the OpenAPI schema, you can also give it an external URL. input: ['./unkey.json'], }); ``` ```ts title="lib/source.ts" import { transformerOpenAPI } from 'fumadocs-openapi/server'; import { loader } from 'fumadocs-core/source'; export const source = loader({ // [!code ++:3] adds a badge to each page item in page tree pageTree: { transformers: [transformerOpenAPI()], }, }); ``` Add `APIPage` to your MDX Components, so that you can use it in MDX files. ```tsx title="mdx-components.tsx" import defaultComponents from 'fumadocs-ui/mdx'; import { APIPage } from 'fumadocs-openapi/ui'; import { openapi } from '@/lib/openapi'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultComponents, APIPage: (props) => , ...components, }; } ``` > `APIPage` is a React Server Component. ### Generate Files You can generate MDX files directly from your OpenAPI schema. Create a script: ```js title="scripts/generate-docs.ts" import { generateFiles } from 'fumadocs-openapi'; import { openapi } from '@/lib/openapi'; void generateFiles({ input: openapi, output: './content/docs', // we recommend to enable it // make sure your endpoint description doesn't break MDX syntax. includeDescription: true, }); ``` > Only OpenAPI 3.0 and 3.1 are supported. Generate docs with the script: ```bash bun ./scripts/generate-docs.ts ``` ## Features The official OpenAPI integration supports: * Basic API endpoint information * Interactive API playground * Example code to send request (in different programming languages) * Response samples and TypeScript definitions * Request parameters and body generated from schemas ### Demo [View demo](/docs/openapi). # Fumadocs Framework: Media Adapters URL: /docs/ui/openapi/media-adapters Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/openapi/media-adapters.mdx Support other media types *** title: Media Adapters description: Support other media types -------------------------------------- ## Overview A media adapter in Fumadocs supports: * Converting value into `fetch()` body compatible with corresponding media type. * Generate code example based on different programming language/tool. Put your media adapters in a separate file. ```ts tab="lib/media-adapters.ts" twoslash import type { MediaAdapter } from 'fumadocs-openapi'; export const myAdapter: MediaAdapter = { encode(data) { return JSON.stringify(data.body); }, // returns code that inits a `body` variable, used for request body generateExample(data, ctx) { if (ctx.lang === 'js') { return `const body = "hello world"`; } if (ctx.lang === 'python') { return `body = "hello world"`; } if (ctx.lang === 'go' && 'addImport' in ctx) { ctx.addImport('strings'); return `body := strings.NewReader("hello world")`; } }, }; ``` ```ts tab="lib/media-adapters.client.ts" 'use client'; // forward them so that Fumadocs can also use your media adapter in a client component export { myAdapter } from './media-adapters'; ``` Pass the adapter. ```ts title="lib/source.ts" import { createOpenAPI } from 'fumadocs-openapi/server'; import * as Adapters from './media-adapters'; import * as ClientAdapters from './media-adapters.client'; export const openapi = createOpenAPI({ proxyUrl: '/api/proxy', mediaAdapters: { // [!code ++:4] override the default adapter of `application/json` 'application/json': { ...Adapters.myAdapter, client: ClientAdapters.myAdapter, }, }, }); ``` # Fumadocs Framework: Creating Proxy URL: /docs/ui/openapi/proxy Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/ui/(integrations)/openapi/proxy.mdx Avoid CORS problem *** title: Creating Proxy description: Avoid CORS problem ------------------------------- ## Introduction A proxy server is useful for executing HTTP (`fetch`) requests, as it doesn't have CORS constraints like on the browser. We can use it for executing HTTP requests on the OpenAPI playground, when the target API endpoints do not have CORS configured correctly. Do not use this on unreliable sites and API endpoints, the proxy server will forward all received headers & body, including HTTP-only `Cookies` and `Authorization` header. ### Setup Create a route handler for proxy server. ```ts title="/api/proxy/route.ts" import { openapi } from '@/lib/openapi'; export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy({ // optional, we recommend to set a list of allowed origins for proxied requests allowedOrigins: ['https://example.com'], }); ``` > Follow the [Getting Started](/docs/ui/openapi) guide if `openapi` server is not yet configured. And enable the proxy from `createOpenAPI`. ```ts title="lib/source.ts" import { createOpenAPI } from 'fumadocs-openapi/server'; export const openapi = createOpenAPI({ proxyUrl: '/api/proxy', // [!code ++] }); ```