Fumadocs

Takumi

Integrate Takumi for framework agnostic and fast metadata image generation.

Installation

npm install @takumi-rs/image-response

Make sure to add @takumi-rs/image-response to serverExternalPackages (Next.js) or external depends on what bundler you are using.

next.config.ts
import type { NextConfig } from 'next';

const config: NextConfig = {
  serverExternalPackages: ['@takumi-rs/image-response'],
};

Metadata Image

You can generate metadata images dynamically using @takumi-rs/image-response.

Add the following under your loader, and define image metadata for pages:

lib/source.ts
import { type InferPageType } from 'fumadocs-core/source';

export function getPageImage(page: InferPageType<typeof source>) {
  const segments = [...page.slugs, 'image.webp'];

  return {
    segments,
    url: `/og/docs/${segments.join('/')}`,
  };
}
app/docs/[[...slug]]/page.tsx
import { notFound } from 'next/navigation';
import { source, getPageImage } from '@/lib/source';
import type { Metadata } from 'next';

export async function generateMetadata(props: PageProps<'/docs/[[...slug]]'>): Promise<Metadata> {
  const params = await props.params;
  const page = source.getPage(params.slug);
  if (!page) notFound();

  return {
    title: page.data.title,
    description: page.data.description,
    openGraph: {
      images: getPageImage(page).url,
    },
  };
}

We append image.webp to the end of slugs so that we can access it via /og/docs/my-page/image.webp, which results in smaller image sizes. You could use image.png if you prefer.

Finally, create a route handler to generate images at build time:

app/docs/og.tsx
import { getPageImage, source } from '@/lib/source';
import { notFound } from 'next/navigation';
import { ImageResponse } from '@takumi-rs/image-response';
import { generate as DefaultImage } from 'fumadocs-ui/og';

export const revalidate = false;

export async function GET(_req: Request, { params }: RouteContext<'/og/docs/[...slug]'>) {
  const { slug } = await params;
  const page = source.getPage(slug.slice(0, -1));
  if (!page) notFound();

  return new ImageResponse(
    <DefaultImage title={page.data.title} description={page.data.description} site="My App" />,
    {
      width: 1200,
      height: 630,
      format: 'webp',
    },
  );
}

export function generateStaticParams() {
  return source.getPages().map((page) => ({
    lang: page.locale,
    slug: getPageImage(page).segments,
  }));
}

Takumi comes with pre-bundled full-axis (100-900) Geist and Geist Mono fonts, so you don't need to worry about it.

See Takumi's Documentation for more details or advanced usage.

Other Templates

There's other available templates, see Takumi Templates for a full list.

How is this guide?

Last updated on

On this page