Fumadocs

AsyncAPI

Generating docs for AsyncAPI schema.

Setup

Install the required packages.

npm i @fumadocs/asyncapi shiki

Generate Styles

Add the following line:

Tailwind CSS
@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:

scripts/generate-docs.ts
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.ts

Add the AsyncAPIPage component to your MDX components. Generated files use the <APIPage /> tag.

app/docs/[[...slug]]/page.tsx
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.

lib/source.ts
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:

docs/[[...slug]]/page.tsx
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:

routes/docs/$.tsx
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:check

Features

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

On this page