Nextra, MDX documentation on a Next.js plugin
Nextra is a Next.js plugin that turns MDX files into a finished documentation site or blog, taking its entire appearance from a separate theme package. The current version of the nextra, nextra-theme-docs and nextra-theme-blog packages is 4.6.1, published to the npm registry on 4 December 2025 under the MIT licence.
The fifth model for building documentation
Documentation tools differ less in their feature lists than in where your project ends and someone else's begins. Docusaurus is a separate React framework with its own routing and its own build. Starlight is a theme for Astro, so the docs are a separate Astro project. Mintlify is a hosted platform whose rendering engine does not belong to you. Fumadocs is a set of libraries added to an existing Next.js application.
Nextra is the fifth arrangement: a Next.js plugin steered by a theme. You wrap the Next.js config in the nextra() function, add a single catch-all route, import the Layout component from the theme package, and you have a finished site. The Next.js app exists, but in practice it is a carrier for the documentation rather than a product you are attaching documentation to.
The difference against Fumadocs is subtle, so let me state it concretely. In Fumadocs you assemble the page from components: you own the routes, you own the layouts, and the library hands you building blocks. In Nextra you configure: the whole appearance is one Layout component from the theme, taking around twenty props, and you set them the way you set options, not the way you compose an interface. When you need something outside that set, you do not drop a component next to it, you swap the theme or write your own.
The second decisive point is the platform requirement, because it sometimes decides for you. Nextra 4.6.1 declares peer dependencies next: ">=14", react: ">=18" and react-dom: ">=18", and its engines field asks for Node 18 or newer. Fumadocs 16.15.0 declares next: "16.x.x" and react: "^19.2.0". If your app sits on Next.js 14 or 15 and you have no budget for a migration, Fumadocs falls out at install time and Nextra installs. In the other direction: a loose >=14 range is a declaration, not a test matrix. Next.js 16 compatibility only landed in 4.6.1, which the changelog entry confirms, and the nextra-theme-docs package carries next: "^16.0.7" in its dev dependencies.
One more thing to keep in mind: in the Nextra family, themes pin the core to an exact version. nextra-theme-docs@4.6.1 and nextra-theme-blog@4.6.1 both declare nextra: "4.6.1" as a peer, with no range. Fumadocs does the same, with fumadocs-ui pinning fumadocs-core at 16.15.0. I checked all three Nextra packages and found no divergence in licence or in ranges: MIT everywhere, the same peer set everywhere.
Versions, licence and four numbers outside the registry
The licence is exemplary and all three sources agree. The repository's main branch has exactly one file, LICENSE, containing MIT with the note "Copyright (c) 2020 Shu Ding"; the variants LICENSE.md, LICENSE.MD, LICENSE.txt, LICENCE and COPYING all return 404. The license field in the npm registry reads MIT for all three packages. The published tarballs contain a package/LICENSE file with the same text and real code under dist, so the declaration matches the contents. The nextra package adds a second file, license.txt, and that is not a contradiction: it is MIT with the note "Copyright (c) 2019-PRESENT Vjacheslav Trushkin", the Iconify author's notice covering the bundled dist/iconify.js file of 51,312 bytes.
Package sizes: nextra unpacks to 403,613 bytes across 291 files with 39 direct dependencies, nextra-theme-docs to 299,142 bytes across 77 files with 7 dependencies, nextra-theme-blog to 120,002 bytes across 30 files with 3 dependencies. The docs theme stylesheet alone is 90,411 bytes before compression, the blog theme one 97,591 bytes.
Now the part worth reading this chapter to the end for. Version 4.6.1 shipped on 4 December 2025, roughly eight and a half months before the date of this text. The earlier rhythm was entirely different: 4.3.0 on 28 July 2025, 4.4.0 on 22 August, 4.5.0 on 21 September, 4.5.1 on 27 September, 4.6.0 on 2 October. That is roughly one release a month, so the current gap is not this project's natural pace but a clear slowdown.
A slowdown does not mean abandonment, and here it gets interesting. The package.json on the main branch reports version 4.6.5, both for nextra and for nextra-theme-docs. The changelog on main carries entries for 4.6.2, 4.6.3, 4.6.4 and 4.6.5. In the npm registry the highest number is 4.6.1. Four version bumps were merged and none of them were ever published.
What is inside them:
- 4.6.2 fixes Pagefind search when
basePathis set in the Next.js config. The index base URL was hardcoded to'/'instead of going throughaddBasePath('/'), so a deployment under a path such as/docslost its search. - 4.6.3 and 4.6.4 have no entries of their own, they were bumped as dependent packages.
- 4.6.5 fixes
GIT_ROOTresolution for Git worktrees. Therepository.path()call pointed inside.git/worktrees/, so every file modification date lookup walked the whole history from a path that did not exist. The entry gives a measurement: a site of 110 files built in roughly 176 seconds instead of roughly 1.8 seconds. The fix usesrepository.workdir().
The commit stream says something too. The last commit on main dates to 23 June 2026, about two months ago, and adds a documentation page about a Typesense integration. The nineteen most recent commits spread between 3 October 2025 and 23 June 2026, which works out to roughly two commits a month. One of them is especially telling, from 2 June 2026: switching npm publishing to Trusted Publishing over OIDC. That looks like an attempt to repair the release pipeline, yet nothing has been published since.
Not a single version in the registry is marked as deprecated; I checked all 364 versions of the nextra package. Prerelease tags exist, but none of them is ahead of the stable release: alpha points at 4.3.0-alpha.31 from 10 July 2025, which predates 4.3.0 itself, rc at 4.0.0-rc.0, beta at 2.0.0-beta.45, canary at 3.1.0-canary.1. There is also a lone 5.0.0-alpha.24 from 19 June 2025 that no tag points to and after which nothing from the 5.0 line appeared.
My conclusion, stated plainly: the project is not dead, since commits ran all through 2026, but releases have stopped and fixed bugs sit in the repository beyond the reach of npm install. If you pick Nextra today, you are picking the December 2025 state.
Configuration: the plugin, the theme and one route
Installation is three things: a Next.js app, the plugin with a theme, and the search indexer separately.
# app, plugin and theme
npm i next react react-dom nextra nextra-theme-docs
# the search indexer is a separate dev dependency
npm i -D pagefind
# build, then index the generated HTML
npm run build
npx pagefind --site .next/server/app --output-path public/_pagefindThe plugin config goes into next.config.mjs. The nextra() function takes a NextraConfig object and returns a function that wraps the ordinary Next.js config. The options below come from the schema in the published 4.6.1 tarball, not from memory.
import nextra from 'nextra'
const withNextra = nextra({
search: { codeblocks: false },
staticImage: true,
readingTime: true,
defaultShowCopyCode: true,
codeHighlight: true,
contentDirBasePath: '/docs',
whiteListTagsStyling: ['figure', 'figcaption'],
mdxOptions: {
format: 'detect',
rehypePrettyCodeOptions: {}
}
})
export default withNextra({
reactStrictMode: true
})Several of these fields are easy to misread. staticImage and codeHighlight are on by default. search defaults to { codeblocks: false }, so code blocks stay out of the index. whiteListTagsStyling extends the list of HTML tags replaced by components from the mdx-components.js file; by default Nextra only replaces <details> and <summary>. contentDirBasePath moves the content directory under a given prefix instead of the root. There is also unstable_shouldAddLocaleToLinks, whose prefix tells you how to treat it.
The whole appearance is configured by one component. The prop names below come from the LayoutProps type in the nextra-theme-docs@4.6.1 tarball.
import { Footer, Layout, Navbar } from 'nextra-theme-docs'
import { Head } from 'nextra/components'
import { getPageMap } from 'nextra/page-map'
import 'nextra-theme-docs/style.css'
export default async function RootLayout({ children }) {
return (
<html lang="en" dir="ltr" suppressHydrationWarning>
<Head />
<body>
<Layout
pageMap={await getPageMap()}
navbar={<Navbar logo={<b>Documentation</b>} />}
footer={<Footer>MIT {new Date().getFullYear()}</Footer>}
docsRepositoryBase="https://github.com/acme/docs/tree/main"
copyPageButton
darkMode
sidebar={{ autoCollapse: true, defaultMenuCollapseLevel: 1, toggleButton: true }}
toc={{ float: true, backToTop: 'Back to top', title: 'On this page' }}
navigation={{ next: true, prev: true }}
feedback={{ content: 'Questions?', labels: 'feedback' }}
themeSwitch={{ dark: 'Dark', light: 'Light', system: 'System' }}
>
{children}
</Layout>
</body>
</html>
)
}Content plugs in through a single catch-all route. The importPage function returns an object with default, toc and metadata fields, and generateStaticParamsFor takes the segment name plus an optional locale segment name.
import { generateStaticParamsFor, importPage } from 'nextra/pages'
import { useMDXComponents as getMDXComponents } from '../../mdx-components'
export const generateStaticParams = generateStaticParamsFor('mdxPath')
export async function generateMetadata(props) {
const params = await props.params
const { metadata } = await importPage(params.mdxPath)
return metadata
}
const Wrapper = getMDXComponents().wrapper
export default async function Page(props) {
const params = await props.params
const { default: MDXContent, toc, metadata } = await importPage(params.mdxPath)
return (
<Wrapper toc={toc} metadata={metadata}>
<MDXContent {...props} params={params} />
</Wrapper>
)
}The sidebar and top navigation are described by _meta files sitting next to the content. The schema in the tarball accepts five value shapes: a bare title, an item object, a link, a separator and a menu.
import type { MetaRecord } from 'nextra'
export default {
index: 'Introduction',
guide: { type: 'doc', title: 'Guide' },
api: { type: 'page', title: 'API', theme: { layout: 'full', toc: false } },
changelog: { display: 'hidden' },
'sep-1': { type: 'separator', title: 'Resources' },
github: { title: 'Repository', href: 'https://github.com/acme/docs' },
versions: {
type: 'menu',
title: 'Versions',
items: {
v3: { title: 'v3 documentation', href: 'https://v3.example.com' }
}
}
} satisfies MetaRecordThe display field accepts normal, hidden or children, where the last value hides the folder itself while keeping its contents in the tree. The theme field supports layout with a value of default or full, plus the navbar, pagination, sidebar, timestamp, toc and typesetting switches.
Themes: two official ones and what comes after
There are exactly two official themes: nextra-theme-docs and nextra-theme-blog. Both sit at 4.6.1, both shipped on the same day as the core, and both pin it to an exact version, so they move in one step. They are maintained exactly as well or as poorly as the core itself, because they come from the same repository and the same release process.
The docs theme provides a top bar, search, a sidebar and a page table of contents. It exports Layout, Navbar, Footer, LastUpdated, LocaleSwitch, NotFoundPage, ThemeSwitch, Link and the useConfig, useMenu, useThemeConfig and useTheme hooks. The blog theme is much smaller: Layout, Navbar, Footer, PostCard, ThemeSwitch, Comments backed by Cusdis, and a BlogMetadata type. Three dependencies and 30 files, so calibrate expectations accordingly.
Beyond the themes, the core ships components through nextra/components: Banner, Bleed, Button, Callout, Cards, Collapse, FileTree, Head, ImageZoom, Playground, Search, Select, Steps, Tabs, Popup, SkipNavContent, Mermaid, MathJax and MathJaxContext. These work regardless of which theme you pick.
The third-party theme ecosystem looks thin. Searching the npm registry for "nextra-theme" returns mostly forks of the docs theme whose last publications fall between 2021 and 2024. A few are fresher, for example nextra-theme-docs-neovate carrying version 4.6.4 from 6 January 2026, a number that never appeared under the official name. That shows where this branch of the ecosystem stands: whoever needs fixes from the main branch ships their own fork.
When you need something beyond the two themes, you have two paths. You can override the styles, since a theme is an ordinary stylesheet and ordinary React components. You can also write your own theme, and that road is not closed: the core exports getPageMap, normalizePages, importPage, evaluate, compileMdx and useMDXComponents, the full set the official themes are built from. A Nextra theme is simply a component that takes pageMap. The cost is real, though, because you take over maintaining the entire page layout.
Pagefind search and its bill
Nextra 4 uses Pagefind, the same library as Starlight, but in a different mode. Pagefind is not a dependency of the nextra package. I checked the list of 39 direct dependencies and it is not there. You install it yourself as a dev dependency and add the postbuild step yourself, because Pagefind indexes built .html files rather than .md sources.
The search option in NextraConfig does not run the indexer, and that is the most common misunderstanding. It does exactly two things: it sets the data-pagefind-body attribute on the <main> element and, with codeblocks: false, adds data-pagefind-ignore="all" to every <pre> element. The rest is on you.
On the browser side, the Search component dynamically imports /_pagefind/pagefind.js through addBasePath, with a webpackIgnore comment, only after the search field receives focus. The index therefore does not weigh on the first render.
I measured the fixed cost on nextra.site, which is built with Nextra; its pagefind-entry.json reports Pagefind 1.3.0 and 76 English pages. The results: pagefind.js weighs 32,912 bytes uncompressed and 9,885 bytes after gzip, wasm.en.pagefind weighs 70,873 bytes and returns 70,916 bytes when requested with gzip, meaning it does not compress in practice, and the pf_meta metadata file is 653 bytes. The uncompressed sum is 104,438 bytes, and roughly 81,454 bytes over the wire.
That is about 81 kB the first time the search opens, plus index fragments fetched separately per query. In the Starlight text the same library came out above 250 kB, and the difference does not mean Nextra is lighter: a different thing is being counted. Here I counted three fixed files and left out the index fragments, and those grow with the corpus. For a site of several thousand pages the bill will be different and you need to measure it yourself.
The biggest inconvenience is not size. Search does not work under next dev, because it indexes the HTML produced by the build. Nextra shows a notice in that spot telling you to run next build and then restart next dev. That is bearable, but you want to know it before spending an hour hunting a bug that is not there.
What the core does not have
Nextra has no documentation versioning. I verified this rather than assumed it: the public surface of nextra, nextra/page-map, nextra/pages and nextra/components contains nothing for parallel version lines, and the project itself solves the problem by hosting the v2 and v3 docs as separate deployments and linking them through a type: 'menu' entry in _meta. If you expect the Docusaurus core mechanism, where frozen versions are a framework feature, in Nextra you will build it yourself.
The second gap concerns Turbopack. With the --turbopack flag, loader options must be JSON serializable, so you cannot pass custom remarkPlugins, rehypePlugins or recmaPlugins, since those are functions. The documentation quotes the message you will see: Error: loader nextra/loader for match "./{src/app,app}/**/page.{md,mdx}" does not have serializable options. The same page claims Turbopack does not support next build, but it carries a "Last updated on October 3, 2025" stamp, meaning it predates Next.js 16; treat that sentence as stale and check the Next.js documentation before relying on it.
The third item is the absence of a vendor and a price list, which is an advantage and a drawback at once. Nextra is entirely MIT, there are no paid plans and no commercial variant, so the only cost is hosting the Next.js app. At the same time there is no support contract you can enforce, and the last eight months show what that means in practice.
Nextra against Fumadocs, Docusaurus, Starlight and Mintlify
| Tool | Version and registry date | Integration model | Required platform | Engine and hosting |
|---|---|---|---|---|
| Nextra | 4.6.1, 4 December 2025 | Next.js plugin plus theme package | next >=14, react >=18, Node >=18 | open, you host it |
| Fumadocs | 16.15.0, 21 August 2026 | libraries inside your app | next 16.x.x, react ^19.2.0 | open, you host it |
| Docusaurus | 3.10.2, 10 July 2026 | separate React framework | react ^18 or ^19, Node >=20 | open, you host it |
| Starlight | 0.41.7, 5 August 2026 | theme for Astro | astro ^7.0.2 | open, you host it |
| Mintlify | no public engine package | hosted platform | no local requirements | closed, the vendor hosts it |
A readable selection rule looks like this. If the documentation is the whole project, you want a finished look from a config file, and you accept that it stands on Next.js, take Nextra. If the documentation is to be part of an existing product app and must blend into your design system, take Fumadocs, provided you can afford Next.js 16. If you do not want Next.js at all, that leaves Docusaurus or Starlight. If you do not want to maintain anything, that leaves Mintlify along with its price list and closed engine.
To that I add the question of pace. Fumadocs releases often and forces high dependency versions, so you pay with constant upgrades. Nextra has shipped nothing for eight and a half months, so you pay with missing fixes. Those are two different kinds of cost and two different kinds of risk, and choosing between them depends more on how much maintenance time you have than on any feature list.
Common mistakes
Expecting search to work in development mode. It will not, because Pagefind indexes built HTML. You have to run next build, then the postbuild step, and only then go back to next dev.
Skipping the postbuild step or using the wrong path. For a regular server build it is pagefind --site .next/server/app --output-path public/_pagefind, and for a static export the same --site but --output-path out/_pagefind. Mixing up the output directory gives you an empty search box with no console error.
Setting basePath in Next.js on version 4.6.1. The index will not load from under the prefix, because the base URL is hardcoded. The fix exists in the repository as 4.6.2 but is absent from the npm registry. The practical ways out are deploying at the domain root or patching the installed package.
Passing custom remark or rehype plugins with next dev --turbopack. Loader options must be serializable, so functions fail with an error. For that configuration you are left with Webpack.
Mixing core and theme versions. The peer dependency is pinned to an exact value, so a package manager in strict mode will refuse to install nextra@4.6.1 against a theme on a different version. These two packages are raised together.
Reading the main branch documentation and assuming the fix is in the tarball. With this project that is currently the costliest mistake. The changelog describes 4.6.5, while npm install gives you 4.6.1.
Counting on documentation versioning out of the box. You have to build it yourself, most often as separate deployments linked by a menu entry in _meta.
FAQ
Is Nextra abandoned?
Not in the sense of a dead repository, but releases have stopped. The last npm publication is 4.6.1 from 4 December 2025, and the last commit on the main branch dates to 23 June 2026. Between those dates four version bumps were merged, up to and including 4.6.5, and none reached the registry. There is also a commit from 2 June 2026 switching publication to Trusted Publishing, which looks like repair work on the release pipeline. The practical takeaway: installing Nextra gives you the December 2025 state.
Nextra or Fumadocs, if I am on Next.js anyway?
What settles it is what documentation is in your project. Nextra fits when the docs are the whole site and the look a theme gives you, configured through Layout props, is enough. Fumadocs fits when the docs are a section of an existing app and must use your components. The second factor is hard: Fumadocs 16.15.0 requires Next.js 16 and React 19.2, while Nextra 4.6.1 declares next >=14 and react >=18.
What does the search actually cost?
On nextra.site, with 76 pages and Pagefind 1.3.0, the fixed overhead is 32,912 bytes of pagefind.js (9,885 after gzip), 70,873 bytes of WebAssembly that does not compress, and 653 bytes of metadata, so roughly 81 kB over the wire. It loads only after you click the search field, so it does not affect the first render. On top of that come index fragments fetched per query, which grow with the number of pages.
Does Nextra support documentation versioning?
Not in the core. Neither the nextra package API nor the themes have a mechanism for parallel version lines. The project itself hosts the v2 and v3 docs as separate deployments and exposes them as a type: 'menu' entry in a _meta file. If versioning is a requirement, Docusaurus has it in the core and is the more sensible pick.
Can Nextra be used without an official theme?
It can. The core exports getPageMap, normalizePages, importPage, compileMdx, evaluate and useMDXComponents, and a theme is a component that takes pageMap, so a custom layout is workable and the official themes were built from the same blocks. The cost is maintaining the entire view layer on your side, including navigation, table of contents and dark mode.
Does Nextra work with Next.js 16 and TypeScript?
Next.js 16 compatibility landed in 4.6.1, confirmed by the changelog entry, and the docs theme is tested against next ^16.0.7. Types ship inside the packages, _meta.ts is described by the MetaRecord type and the plugin config by NextraConfig, so writing in TypeScript needs no extra packages. Sources: the project repository, the search guide and the Pagefind documentation.