Without RSC
Setup guide for non-RSC environments.
Setup
Install the required packages.
npm i fumadocs-openapi shikishiki must be installed, otherwise Vite will have bundling problem with Shiki's WASM regex engine.
Generate Styles
Add the following line:
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
@import 'fumadocs-openapi/css/preset.css';Configure Plugin
Create the OpenAPI server instance & <ClientAPIPage /> component.
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
// the OpenAPI schema, you can also give it an external URL.
input: ['./openapi.json'],
});See createOpenAPI() & <APIPage /> for available options.
Generate Pages
You can generate pages dynamically by integrating into Loader API.
import { loader, multiple } from 'fumadocs-core/source';
import { openapiPlugin, openapiSource } from 'fumadocs-openapi/server';
import { docs } from 'collections/server';
import { openapi } from '@/lib/openapi';
export const source = loader(
multiple({
docs: docs.toFumadocsSource(),
openapi: await openapiSource(openapi, {
baseDir: 'openapi',
}),
}),
{
baseUrl: '/docs',
plugins: [openapiPlugin()],
// ...
},
);Update References to source
openapiSource() is a server-side API that generates pages directly to your loader(), hence allowing dynamic generation (e.g. different page tree as schema changes).
But it will change the type of your pages, explicit handling of OpenAPI pages is necessary.
import { source } from '@/lib/source';
const page = source.getPage(['...']);
if (page.data.type === 'openapi') {
// page data for generated OpenAPI pages
console.log(page.data);
} else {
// original flow...
}Update all references to the pages of source, for example:
import type { InferPageType } from 'fumadocs-core/source';
/**
* return page content for LLMs
*/
export async function getLLMText(page: InferPageType<typeof source>) {
if (page.data.type === 'openapi') {
// e.g. return the stringified OpenAPI schema
return JSON.stringify(page.data.getSchema().bundled, null, 2);
}
// your original flow below...
}Ensure the migration is complete!
Run a type check to verify before continuing, e.g.
npm run types:checkRender Page
Pass a client payload from server, then render the page using the <ClientAPIPage /> component you created above.
For example, in Tanstack Start:
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 browserCollections from 'collections/browser';
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import { useFumadocsLoader } from 'fumadocs-core/source/client';
import { type ReactNode, Suspense } from 'react';
import { ClientAPIPage } from '@/components/api-page';
export const Route = createFileRoute('/docs/$')({
component: Page,
loader: async ({ params }) => {
const slugs = params._splat?.split('/') ?? [];
const data = await serverLoader({ data: slugs });
// Fumadocs MDX: only preload content for normal pages
if (data.type === 'docs') {
await clientLoader.preload(data.path);
}
return data;
},
});
const serverLoader = createServerFn({
method: 'GET',
})
.inputValidator((slugs: string[]) => slugs)
.handler(async ({ data: slugs }) => {
const page = source.getPage(slugs);
if (!page) throw notFound();
const pageTree = await source.serializePageTree(source.getPageTree());
// different result for OpenAPI pages
if (page.data.type === 'openapi') {
return {
type: 'openapi',
title: page.data.title,
description: page.data.description,
pageTree,
props: await page.data.getClientAPIPageProps(),
};
}
return {
type: 'docs',
path: page.path,
markdownUrl: getPageMarkdownUrl(page).url,
pageTree,
};
});
const clientLoader = browserCollections.docs.createClientLoader({
component(pageData, props) {
// ...
},
});
function Page() {
const page = useFumadocsLoader(Route.useLoaderData());
let content: ReactNode;
// render OpenAPI page content
if (page.type === 'openapi') {
content = (
<DocsPage full>
<DocsTitle>{page.title}</DocsTitle>
<DocsDescription>{page.description}</DocsDescription>
<DocsBody>
{/* pass the payload data */}
<ClientAPIPage {...page.props} />
</DocsBody>
</DocsPage>
);
} else {
content = clientLoader.useContent(page.path, page);
}
return (
<DocsLayout tree={page.pageTree}>
<Suspense>{content}</Suspense>
</DocsLayout>
);
}You can see the full Tanstack Start example.
After configurating Fumadocs OpenAPI, you should be able to view the generated API pages after starting your app.
How is this guide?
Last updated on
