CodeWorlds
Back to collections
Guide17 min readCodeWorlds Team

Fumadocs, documentation inside your Next.js app

Fumadocs 16.15.0 is a docs library you add to an existing React app. ZBSearch search, OpenAPI page generation, major release cadence and the drawbacks.

Fumadocs, documentation inside your Next.js app

Fumadocs is a set of libraries you add to an existing React application to get search, navigation, MDX components and pages generated from an OpenAPI definition. The current version of fumadocs-core and fumadocs-ui is 16.15.0, published to the npm registry on 21 August 2026, under the MIT license.

A library, not a separate generator

The difference against the other documentation tools is not the feature set but who owns the application. Docusaurus is a separate framework: you get its routing, its build and its conventions. Starlight is a theme for Astro, so the docs are a separate Astro project. Mintlify is a hosted platform whose rendering engine is closed. Fumadocs is a fourth arrangement: a dependency in your package.json, with documentation pages served as ordinary routes of your own app.

The practical consequences are concrete. The header, the footer and the product design system are the same, because they are the same components. There is one deployment, because there is one application. Authentication covers the docs exactly as it covers the rest of the site, because the pages pass through the same middleware. There is also no synchronisation step between two repositories.

The cost sits on the other side. You need a React application on a version Fumadocs accepts, and the requirements are strict. Version 16.15.0 of fumadocs-ui declares peer dependencies of react at ^19.2.0, react-dom at the same range, and next at 16.x.x. Not ^15 || ^16, just 16.x.x. If your product runs on Next.js 15, the UI layer of this Fumadocs version will not install without a forced override.

The label "a Next.js library" is already out of date, too. The fumadocs-core package exports adapters at fumadocs-core/framework/next, /framework/react-router, /framework/tanstack, /framework/waku and /framework/astro, and fumadocs-ui ships matching context providers at fumadocs-ui/provider/next and so on. The core peer dependencies list react-router at 7.x.x || 8.x.x and @tanstack/react-router at 1.x.x, both marked optional. Next.js is the best supported path, not the only one.

Code
Bash
# a new project from scratch
npm create fumadocs-app@latest

# adding it to an existing application
npm install fumadocs-core fumadocs-ui fumadocs-mdx

# page generation from an OpenAPI definition, a separate package
npm install fumadocs-openapi

# tool for copying components and migrating layouts
npx @fumadocs/cli@latest

Package family, versions and license

Fumadocs packages do not share a version number, and that is the first thing that confuses people during an upgrade. As of 22 August 2026 the picture is this: fumadocs-core 16.15.0 and fumadocs-ui 16.15.0 released on 21 August 2026, fumadocs-mdx 15.3.1 the same day, fumadocs-openapi 11.3.0 also that day, and fumadocs-typescript 5.3.0 plus fumadocs-docgen 3.1.0 earlier, on 7 July 2026. Six packages, six independent version lines.

The peer dependency ranges between them are consistent, which I checked package by package. fumadocs-ui requires fumadocs-core at exactly 16.15.0, with no range. fumadocs-openapi requires fumadocs-core and fumadocs-ui at ^16.15.0. fumadocs-mdx and fumadocs-typescript accept ^16.7.0, and fumadocs-docgen accepts ^15.7.2 || ^16.0.0. Version 16.15.0 satisfies all of these, so installing the full set produces no warnings. The consequence of the exact pin in fumadocs-ui is that the core cannot be bumped on its own: you upgrade both packages together or not at all.

Code
JSON
{
  "dependencies": {
    "fumadocs-core": "16.15.0",
    "fumadocs-ui": "16.15.0",
    "fumadocs-mdx": "15.3.1",
    "fumadocs-openapi": "11.3.0",
    "next": "16.1.0",
    "react": "19.2.0",
    "react-dom": "19.2.0"
  }
}

I checked the license from three sides and it comes out clean. The fuma-nama/fumadocs repository holds a LICENSE file, with no spelling variants, containing the MIT license text and the note "Copyright (c) 2023 Fuma". The license field in the npm registry reads MIT for every package of the family I checked. The unpacked fumadocs-core, fumadocs-ui and fumadocs-openapi tarballs each contain a LICENSE file byte-identical to the repository one, next to a dist directory with actual code. The repository address does not redirect anywhere else.

One detail is not uniform and an audit should see it. The core has a hard dependency on zbsearch at ^4.0.0, and that package is Apache 2.0, with a LICENSE.md file and the note "Copyright 2023 ZBSearchSearch Inc". Fumadocs itself is MIT, but the dependency tree is not entirely. Both licenses are permissive, so for most companies this changes nothing, but if you keep a license inventory, record both.

There is also a naming trap. Running npm install fumadocs gets you nothing useful: the fumadocs package in the registry has a single version, 0.0.0, published on 3 October 2024, and it is a name reservation. The release title fumadocs@16.15.0 visible in the GitHub releases feed refers to an internal package of that monorepo, not to this npm package. The real tools are @fumadocs/cli at version 1.4.1 and create-fumadocs-app at version 16.1.18.

Major release cadence and what the number 16 costs you

Major version 16 looks alarming, and checking it was the most important part of preparing this article, because frequent major bumps mean migrations, and migrations are a real maintenance cost.

The registry history goes like this. Version 8.0.0 appeared on 26 January 2024, 9.0.0 on 19 February 2024, 10.0.0 on 4 March 2024, 11.0.0 on 17 April 2024, 12.0.0 on 9 June 2024, 13.0.0 on 28 July 2024, 14.0.0 on 22 October 2024, 15.0.0 on 29 January 2025 and 16.0.0 on 22 October 2025. That is eight major bumps across 635 days, an average of one every eighty days or so.

The average misleads, because the trend runs one way. The gap between 9.0.0 and 10.0.0 was fourteen days, between 14.0.0 and 15.0.0 ninety-nine days, and between 15.0.0 and 16.0.0 two hundred and sixty-six days. The 16.x line has been current for 304 days. The project visibly calmed down during 2025, and that is a better forecast than the average over its whole history. Even so, this is a tool that changed its major version every few weeks in its first year, and every such change moved export names and layout shapes.

The registry also lists fumadocs-core 17.0.0, dated 1 February 2026, but the latest tag points at 16.15.0 and 17.0.0 itself is deprecated with a message stating plainly that it was published by accident through a bug in the Changesets tool and should not be used. If your package.json holds a ^16 range, nothing happens to you, but a * range or manually reaching for "the highest number" leads into a dead end.

The third element of risk is organisational. In the npm registry fumadocs-core has exactly one maintainer, the sonmoosans account. The code accepts outside contributions and releases are published by a continuous integration bot, but control over publishing and over the project direction sits in one pair of hands. That is a common arrangement for an open project led by one person, and it carries the known risk: no support contract you can enforce, and a single point of failure.

Loading content and building the page tree

The content layer rests on the loader function, which takes a source and returns an object with a ready navigation tree and methods for fetching pages. The source is usually fumadocs-mdx reading MDX files from disk, but it can also be a virtual source generated by fumadocs-openapi.

Code
TypeScript
import { loader } from 'fumadocs-core/source'
import { docs } from '@/.source'

export const source = loader({
  source: docs.toFumadocsSource(),
  baseUrl: '/docs',
  plugins: []
})

// available once created
source.pageTree
source.getPage(['getting-started'])
source.getPages()
source.getPageByHref('/docs/getting-started#installation')
source.generateParams('slug', 'lang')

The LoaderOptions fields are baseUrl, i18n, url, pageTree, plugins and icon. The resulting object gives you pageTree, getPageTree(locale), getPage(slugs, language), getPages(language), getPageByHref(href, options) and generateParams(slug, lang). That last method plugs straight into the Next.js generateStaticParams.

What this API does not have is documentation versioning. Neither fumadocs-core nor fumadocs-ui ships any version mechanism in its published code: LoaderOptions has no such field, and searching both packages for the concept returns no hits. Versioning can be built by hand, with a separate loader and a separate route segment per version, but that is your work, not a feature of the tool. Docusaurus has it in the core, and if you maintain documentation for three supported product releases, that settles the choice.

The visual layer is fumadocs-ui with four documentation layouts: layouts/docs, layouts/notebook, layouts/flux and layouts/glass, plus layouts/home for a landing page. Each layout exposes replaceable slots, for example layouts/docs/slots/header, slots/sidebar and page/slots/toc, so a single fragment can be swapped for your own component without rewriting the whole thing. Styling rests on Tailwind CSS through the @fumadocs/tailwind package, and the primitives come from Radix UI.

Search and the cost of each path

Search is built into the core, unlike Docusaurus where it has to be added separately. The engine is zbsearch version 4, a hard dependency of fumadocs-core. It is Michele Riva's project, the successor to the Orama engine, and that is where the deprecated aliases in the API come from: oramaStaticClient is now an alias of staticClient, and the initOrama option an alias of initDB.

On the server side you get three functions. createSearchAPI('simple' | 'advanced', options) builds an index from a list of records, createI18nSearchAPI does the same across several languages, and createFromSource(loader, options) takes the index straight from the object returned by loader. The simple schema has the fields url, title, breadcrumbs, description, content, keywords and locale. The advanced schema has content, page_id, type, breadcrumbs, tags, url, locale and embeddings typed as vector[512], that is, room for vector search. The default tokenizer is set to multilingual and handles every language with no configuration, which is why the localeMap option is marked deprecated.

TSapp/api/search/route.ts
TypeScript
// app/api/search/route.ts
import { createFromSource } from 'fumadocs-core/search/server'
import { source } from '@/lib/source'

export const { GET, staticGET } = createFromSource(source, {
  language: 'multilingual',
  localeFilter: true,
  buildIndex(page) {
    return {
      id: page.url,
      url: page.url,
      title: page.data.title,
      description: page.data.description,
      structuredData: page.data.structuredData,
      tag: page.slugs[0]
    }
  }
})

The SearchAPI object exposes two handlers: GET(request) answers queries on the server, and staticGET() dumps the whole index as a single file for the browser to download. That is the cost fork, and it has to be chosen deliberately.

The first path, GET, costs nothing in cash but needs a running server process. The index lives in that process's memory and is rebuilt from scratch on every cold start of a serverless function. The browser downloads only the results. The client is fetchClient with a default api of /api/search.

The second path, staticGET, works on a static export and needs no server. The client is staticClient with the options from, defaulting to /api/search, plus initDB, tag, locale and search. The price lands on the browser: the entire exported index is fetched, in one piece, before the first query works at all. The engine itself is small. The unpacked zbsearch 4.0.0 tarball contains eight ESM files under dist/browser totalling 20,728 bytes unminified, which compresses to 6,914 bytes with gzip. For comparison, in the Starlight article we measured the local Pagefind index at over 250 kB of fixed overhead. The caveat matters, though, because those numbers measure different things. Those 6.9 kB are the engine alone before a bundler minifies it, not the index. I did not measure the index itself, because its size scales linearly with the volume of documentation, and Pagefind additionally splits its index into fragments fetched on demand, which the staticGET export does not do. On large documentation sets that architectural difference works against Fumadocs.

The third path is an external service. The core ships ready client presets: algoliaClient with the fields indexName and client, oramaCloudClient with client, an index of either default or crawler, and params, plus variants for Mixedbread and for FlexSearch. On the server there are sync functions for pushing documents to Algolia and to Orama. I will not put numbers on the cost of this path, because they could not be confirmed: the Algolia pricing page returns 546 kB of HTML with plan names but without the record and request quotas, which JavaScript attaches later, and the Orama pricing page describes the Pro plan as a flat monthly fee plus one-time onboarding covering four hours with their team, also with no figure in the page source.

Pages generated from an OpenAPI definition

This is the feature none of the three compared tools has in its core, and the strongest reason to reach for Fumadocs. It lives in a separate package, fumadocs-openapi, currently at version 11.3.0.

Code
TypeScript
import { generateFiles } from 'fumadocs-openapi'
import { openapi } from '@/lib/openapi'

await generateFiles({
  input: openapi,
  output: './content/docs/api',
  per: 'operation',
  groupBy: 'tag',
  index: {
    items: [{ path: 'index.mdx', title: 'API Reference' }],
    url: { baseUrl: '/docs/api', contentDir: './content/docs' }
  },
  meta: { folderStyle: 'folder' },
  beforeWrite(files) {
    // last chance to change the contents before they hit disk
  }
})

The OpenAPI server is created separately and takes input as a list of paths, URLs or an object mapping a schema id to a file, plus disableCache and proxyUrl, whose only job is working around CORS restrictions in the try-it panel. The supported types are OpenAPIV2, OpenAPIV3, OpenAPIV3_1 and OpenAPIV3_2. The output mode is set by the per field with the values operation, meaning a page per operation, tag, file and custom. Folder grouping is set by groupBy with the values tag, route and none, and the default is none. Request sample generators are available for seven languages: curl, C#, Go, Java, JavaScript, Python and Rust. The interactive try-it panel is not Fumadocs code but an integration with the Scalar client, declared as the optional peer dependency @scalar/api-client-react.

There are two limitations and both concern the life cycle of the files. First, generateFiles writes MDX files to disk, so the generated pages are an artefact in the repository that you either commit or recreate during the build. The alternative is staticSource or dynamicSource wired in as a loaderPlugin on the loader, in which case nothing lands on disk. Second, schema change tracking is thin: a watch option exists, but the type definitions describe it as intended for simple cases, ignoring custom input functions and URLs, with a recommendation to configure chokidar yourself.

Fumadocs against Docusaurus, Starlight and Mintlify

PropertyFumadocsDocusaurusStarlightMintlify
Modellibrary inside your appseparate React frameworktheme for Astrohosted platform
Required environmentReact 19.2, Next.js 16 or another adapterits own buildAstronone, content in a repository
Search in coreyes, ZBSearch, server or staticno, added separatelyyes, local Pagefindyes, on the vendor side
Documentation versioningnone, you build ityes, in corenot in coreon the vendor side
OpenAPI pages in coreyes, fumadocs-openapinonoyes
LicenseMITMITMITclosed engine
Hostingyours, with the appyoursyoursthe vendor's

The choice comes down to three questions. Should the documentation be part of an existing product built in React, or a separate site. Do you need versioning. Do you want somebody else to host it. Fumadocs wins when the answers are: part of the product, no versioning needed, I host it myself. It loses when the team does not work in Next.js or any of the supported routers, when versioning is a requirement, or when nobody wants to maintain layout code.

Nextra sits on the same foundation: an older documentation generator for Next.js, driven by a ready-made theme rather than assembled from components. The difference comes down to how much you want to configure: a theme gets you there faster, a library gives you control. Before choosing, though, check the release state, because that is where the problem is: the latest version dates from December 2025 even though commits continued through 2026, so fixes sit in the repository beyond the reach of a registry install.

Common mistakes

The first is installing the fumadocs package from npm. It is a reserved name at version 0.0.0 from two years ago. What you need is fumadocs-core and fumadocs-ui, plus create-fumadocs-app for scaffolding.

The second is bumping fumadocs-core without fumadocs-ui. The UI pins the core to an exact version, so the package manager will either refuse or quietly install two copies of the core in the dependency tree.

The third is reaching for version 17.0.0 because the number is higher. It is deprecated with a note saying it came from a bug in the release tooling.

The fourth is assuming staticGET is free. It is free for the server and paid for by the user's browser, because the whole index is fetched in a single request before the first search. On documentation of a few hundred pages, measure that file before committing to this path.

The fifth is planning documentation versioning on the assumption that the tool provides it. It is not present in the published code of either main package.

The sixth is keeping generated OpenAPI files in the repository with no process that refreshes them. After a schema change the pages drift silently, because nothing invalidates them. Either generate them during the build or use loaderPlugin instead of writing to disk.

The seventh is treating proxyUrl as a security feature. It is a CORS workaround for the try-it panel, nothing more.

FAQ

Does Fumadocs require Next.js?

No, although Next.js is the best supported path. The core exports adapters for Next.js, React Router, TanStack Router, Waku and Astro, and the peer dependencies list react-router at 7.x.x || 8.x.x and @tanstack/react-router at 1.x.x. The hard requirement is React at ^19.2.0, and fumadocs-ui 16.15.0 accepts only next at 16.x.x.

Does search need an external service?

No. The default ZBSearch engine is a hard dependency of the core and runs either in the server process through the GET handler, or entirely in the browser through staticGET and the staticClient. External services, meaning Algolia, Orama Cloud and Mixedbread, are an option for large content sets, and the corresponding packages are declared as optional peer dependencies.

Does Fumadocs support documentation versioning?

Not in the core. Neither fumadocs-core nor fumadocs-ui at 16.15.0 contains a version mechanism, and LoaderOptions has no such field. Versions can be built by hand, with a separate loader and a separate route segment per version, but maintaining that arrangement falls on you.

How much does Fumadocs cost?

Nothing. Every package of the family I checked is MIT licensed, the LICENSE file in the repository, the license field in the npm registry and the contents of the published tarballs all agree, and there is no paid variant. A cost can appear only on the side of an external search provider, if you pick one.

Does a high major version number mean constant migrations?

It used to, less so now. Eight major bumps between 8.0.0 on 26 January 2024 and 16.0.0 on 22 October 2025 average one every eighty days or so, but the gaps grew from fourteen days to two hundred and sixty-six, and the 16.x line has been current for 304 days.

Who maintains the project?

In the npm registry fumadocs-core has one maintainer, the sonmoosans account. Releases are published by a continuous integration bot and the repository accepts outside contributions, but direction and publishing rights sit in one pair of hands. When picking a tool for several years, that is a risk factor to record next to the advantages, as with any single-author project written in TypeScript.

Sources: the fuma-nama/fumadocs repository, fumadocs-core in the npm registry and the project documentation.

Read next

We use cookies to enhance your experience on the site