AsyncAPI
Generating docs for AsyncAPI schema.
Setup
Install the required packages.
npm i @fumadocs/asyncapi shikiGenerate Styles
Add the following line:
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
@import '@fumadocs/asyncapi/css/preset.css';Configure Plugin
Create the AsyncAPI server instance & <AsyncAPIPage /> component.
import { createAsyncAPI } from '@fumadocs/asyncapi/server';
// note: this is a server-side API
export const asyncapi = createAsyncAPI({
// the AsyncAPI schema, you can also give it an external URL.
input: ['./asyncapi.yaml'],
});See createAsyncAPI() & createAsyncAPIPage() for available options.
Generate Pages
You can generate MDX files directly from your AsyncAPI schema.
Create a script:
import { generateFiles } from '@fumadocs/asyncapi';
import { asyncapi } from '@/lib/asyncapi';
void generateFiles({
input: asyncapi,
output: './content/docs',
// we recommend to enable it
// make sure your operation description doesn't break MDX syntax.
includeDescription: true,
});Generate docs with the script:
bun ./scripts/generate-docs.tsAdd the AsyncAPIPage component to your MDX components. Generated files use the <APIPage /> tag.
import { source } from '@/lib/source';
import { asyncapi } from '@/lib/asyncapi';
import { AsyncAPIPage } from '@/components/api-page';
import { getMDXComponents } from '@/components/mdx';
// e.g. in your page renderer
export default function Page({ slug }) {
const page = source.getPage(slug);
const MdxContent = page.data.body;
return (
<MdxContent
components={getMDXComponents({
// add the MDX component
AsyncAPIPage: async (props) => (
<AsyncAPIPage {...await asyncapi.preloadAsyncAPIPage(page)} {...props} />
),
})}
/>
);
}You can also use it without generating real files by integrating into Loader API.
import { loader } from 'fumadocs-core/source';
import { docs } from 'collections/server';
import { asyncapi } from '@/lib/asyncapi';
export const source = loader(
{
docs: docs.toFumadocsSource(),
asyncapi: await asyncapi.staticSource({
baseDir: 'asyncapi',
}),
},
{
baseUrl: '/docs',
plugins: [asyncapi.loaderPlugin()],
// ...
},
);staticSource() is a server-side API that generates pages directly to your loader(), hence it allows dynamic content generation, such as re-generating page tree as schema changes.
It will change the type of your pages, make sure to update all references to your source.
For example, where you return text for LLM:
import { source } from '@/lib/source';
export async function getLLMText(page: (typeof source)['$inferPage']) {
if (page.type === 'asyncapi') {
// e.g. return the stringified AsyncAPI schema
return JSON.stringify(page.data.getSchema().bundled, null, 2);
}
// your original flow below...
}And update your page renderer:
import { AsyncAPIPage } from '@/components/api-page';
export default function Page({ slug }) {
const page = source.getPage(slug);
// for AsyncAPI pages
if (page.type === 'asyncapi') {
return (
<DocsPage full>
<h1 className="text-[1.75em] font-semibold">{page.data.title}</h1>
<DocsBody>
<AsyncAPIPage {...page.data.getAsyncAPIPageProps()} />
</DocsBody>
</DocsPage>
);
}
// your original flow below...
}Pass a client payload from server, then render the page using the <AsyncAPIPage /> 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 { AsyncAPIPage } 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 AsyncAPI pages
if (page.type === 'asyncapi') {
return {
type: 'asyncapi',
title: page.data.title,
description: page.data.description,
pageTree,
props: page.data.getAsyncAPIPageProps(),
};
}
return {
type: 'docs',
path: page.path,
pageTree,
// ...
};
});
const clientLoader = browserCollections.docs.createClientLoader({
component(pageData, props) {
// ...
},
});
function Page() {
const page = useFumadocsLoader(Route.useLoaderData());
let content: ReactNode;
// render AsyncAPI page content
if (page.type === 'asyncapi') {
content = (
<DocsPage full>
<DocsTitle>{page.title}</DocsTitle>
<DocsDescription>{page.description}</DocsDescription>
<DocsBody>
{/* pass the payload data */}
<AsyncAPIPage {...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 AsyncAPI example.
Ensure the migration is complete!
Run a type check to verify before continuing, e.g.
npm run types:checkFeatures
The official AsyncAPI integration supports:
- Basic operation information (send/receive)
- Channel, message, and reply documentation
- Message payload & header schemas
- Security schemes
- Message examples (from schema examples or generated samples)
- Bindings and traits
Demo
See the AsyncAPI example.
How is this guide?
Last updated on
