Nextra Export
Export a complete Nextra site as one Markdown file or a print-ready book. The PDF view renders the site’s real MDX and React components. Markdown components use a project-owned renderer registry.
Install
pnpm dlx shadcn@latest add @utilities/nextra-exportThe command writes the exporter below src/lib/nextra-export. It installs
unified and the Remark packages used for MDX normalization. The target site
must already use Nextra 4.
Load the documentation tree
Create one project-specific loader:
import {
collectNextraExportPages,
loadNextraPageSources,
} from "@/lib/nextra-export";
import { importPage } from "nextra/pages";
import { getPageMap } from "nextra/page-map";
export async function loadDocsExportPages() {
const pages = collectNextraExportPages(await getPageMap());
return loadNextraPageSources(pages, importPage);
}Page order follows Nextra navigation. Set export: false in a page’s
frontmatter to omit it.
Add the PDF route
Reserve /export/pdf inside the existing Nextra catch-all. Next 16 can let the
root optional catch-all shadow a sibling page at the same URL.
import { ExportBook } from "@/lib/nextra-export";
import { loadDocsExportPages } from "@/lib/docs-export";
async function PdfExportPage() {
const pages = await loadDocsExportPages();
return (
<ExportBook
title="My Docs"
siteHref="https://docs.example.com"
siteLabel="docs.example.com"
pages={pages.map(({ Content, route, sectionPath, title }) => ({
content: <Content />,
route,
sectionPath,
title,
}))}
/>
);
}
export default async function Page({ params }) {
const { mdxPath } = await params;
if (mdxPath?.join("/") === "export/pdf") return <PdfExportPage />;
// Keep the site's existing importPage(...) implementation here.
}If the Nextra theme layout wraps the catch-all route, return children directly
from that layout for export/pdf. This keeps the navbar and sidebar out of the
book.
Add the button to the Nextra navbar:
import { ExportPdfButton } from "@/lib/nextra-export";
<Navbar logo={logo}>
<ExportPdfButton />
</Navbar>;The button opens the complete book and starts the browser print flow after its
fonts and images settle. Choose Save as PDF to create one bundled file.
The table of contents uses internal PDF jumps. Links inside exported pages are
rewritten to canonical absolute URLs and printed in full after their labels, so
they remain usable in strict PDF viewers and on paper. siteHref sets that
canonical origin and adds the documentation link to the cover.
Add the Markdown route
Create a route handler outside the Nextra catch-all:
import { exportMarkdownBundle } from "@/lib/nextra-export";
import { loadDocsExportPages } from "@/lib/docs-export";
export async function GET() {
const markdown = await exportMarkdownBundle({
title: "My Docs",
pages: await loadDocsExportPages(),
siteUrl: "https://docs.example.com",
});
return new Response(markdown, {
headers: {
"Content-Disposition": 'attachment; filename="my-docs.md"',
"Content-Type": "text/markdown; charset=utf-8",
},
});
}Internal page links become anchors in the bundled document. Frontmatter and MDX module statements are removed.
Render custom MDX components
Pass a renderer for each project-specific component:
import type { ComponentRendererRegistry } from "@/lib/nextra-export";
export const components: ComponentRendererRegistry = {
ProductCard: ({ attributes }) =>
`**${attributes.title}** — [Open product](${attributes.href})`,
Diagram: async ({ attributes }) =>
`})`,
};Renderers receive literal attributes, normalized Markdown children, and the current page. They may return text, tables, code, links, or generated image URLs. Unregistered components keep their children and produce a labeled fallback instead of disappearing.
Pass the registry as components to exportMarkdownBundle.