Refactor this blog with FumaDocs

May-25, 2025 · 5min

change language

Detailed introduction to how to refactor the existing blog with FumaDocs, including schema definition, custom component development, MDX plugin configuration, and type-safe structured data processing.

Why choose FumaDocs

Previously, I used the combination of Nextjs and mdx, using mdx as a page to render articles. For details, please refer to this article How I Built This Blog, but there is a problem that it is not very easy to obtain structured data, and some mdx plugins are complex to implement from scratch. Recently, I saw the FumaDocs project on X, and it felt quite good, so I decided to try it out.

Overall, it felt good, with high customization, type-safe structured data, and the ability to configure different mdx plugins for different collections. For example, when generating og images for each article, I can directly get all the article data through source.getPages().

Refactor process

Define date type schema

For most articles, I usually define three frontmatter fields, namely title, date, and duration. However, the schema in FumaDocs does not provide a date type schema, so I need to define it myself.

---
title: Refactor this blog with FumaDocs
date: 2025-05-25T10:27:54.972Z
duration: 5min
---

FumaDocs uses gray-matter to parse frontmatter, for data type, you need to use the zod transform method to convert the string to a date type.

export const docs = defineDocs({
  dir: 'content/posts',
  docs: {
    schema: frontmatterSchema.extend({
      duration: z.string().optional().default('1m'),
      date: z.date().transform((val) => new Date(val)), 
    }),
  },
  meta: {
    schema: metaSchema,
  },
})

Custom Toc component

FumaDocs source code position

Here, I mainly want to remove the title on this page, so I need to customize the Toc component.

{
  slot(
    { enabled: tocEnabled, component: tocReplace },
    <Toc>
      {tocOptions.header}
      <h3 className='inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground'>
        <Text className='size-4' />
        <I18nLabel label='toc' />
      </h3>
      <TOCScrollArea>
        {tocOptions.style === 'clerk' ? (
          <ClerkTOCItems items={toc} />
        ) : (
          <TOCItems items={toc} />
        )}
      </TOCScrollArea>
      {tocOptions.footer}
    </Toc>,
    {
      items: toc,
      ...tocOptions,
    },
  )
}

Passing the component parameter allows you to customize the Toc component.

function TableOfContent(props: any) {
  const { items, style } = props

  return (
    <Toc>
      <TOCScrollArea>{style === 'clerk' ? <ClerkTOCItems items={items} /> : <TOCItems items={items} />}</TOCScrollArea>
    </Toc>
  )
}

export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
  const params = await props.params
  const slug = params.slug

  const page = source.getPage(slug)
  if (!page) notFound()

  const MDXContent = page.data.body

  const { prevPage, nextPage } = getRelativePage(slug)

  return (
    <DocsPage
      toc={page.data.toc}
      full={page.data.full}
      breadcrumb={{ enabled: false }}
      tableOfContent={{
        enabled: page.file.dirname.startsWith('(blog)') && page.data.toc.length > 0,
        style: 'clerk',
        header: null,
        component: <TableOfContent />,
      }}
      footer={{
        enabled: false,
      }}
    >
      <DocsTitle>{page.data.title}</DocsTitle>
      <p className='text-muted-foreground mt-2'>
        <span>{format(page.data.date, 'MMM-dd, yyyy')}</span>
        {' · '}
        <span>{page.data.duration}</span>
      </p>
      <DocsDescription>{page.data.description}</DocsDescription>
      <DocsBody className='break-words'>
        <MDXContent
          components={getMDXComponents({
            a: createRelativeLink(source, page),
          })}
        />
        <Cards>
          {prevPage && <Card title={prevPage.data.title} href={prevPage.url} />}
          {nextPage && <Card title={nextPage.data.title} href={nextPage.url} />}
        </Cards>
      </DocsBody>
    </DocsPage>
  )
}

Integrate twoslash

FumaDocs documentation