Next.js

Static Site Generation with

does not require any additional setup and should work out of the box.

View on

Dynamic RSC page

are ideal for the purpose of bundling documents because they render in a server context.

can be utilized, if you are using .

Bundling documents

import { docs } from 'mdx-butler';
import { cache } from 'react';

export const getDocs = cache(() =>
  docs({
    fields: {
      title: {
        required: true,
      },
    },
  })
);

Static Site Generation

Export a generateStaticParams function to use dynamic routing and Static Site Generation ().

export async function generateStaticParams() {
  const docs = await getDocs();

  return docs.map((x) => ({
    slug: x.path.split('/'),
  }));
}

Exporting Metadata

export async function generateMetadata({
  params: { slug },
}: {
  params: { slug: string[] };
}) {
  const docs = await getDocs();
  const path = slug.join('/');

  const doc = docs.find((x) => path === x.path);

  return {
    title: doc?.frontmatter.title,
  };
}

Creating the Page

import { Component } from 'mdx-butler/client';

// ...

export default async function Docs({
  params: { slug },
}: {
  params: { slug: string[] };
}) {
  const docs = await getDocs();
  const path = slug.join('/');

  const doc = docs.find((x) => path === x.path);

  if (!doc) return <div>not found</div>;

  return (
    <div
      style={{
        display: 'flex',
        gap: '1rem',
        flexDirection: 'row',
      }}
    >
      <div>
        <h1>{doc.frontmatter.title}</h1>
        <Component doc={doc} />
      </div>
      <div
        style={{
          display: 'flex',
          gap: '1rem',
          flexDirection: 'column',
        }}
      >
        <h2>On this page</h2>
        {doc.headings.map((x) => (
          <a key={x.title} href={`#${x.title}`}>
            {x.title}
          </a>
        ))}
      </div>
    </div>
  );
}

Customization/Optimization

For more ambitious use-cases and for filtering that depends on the Frontmatter object, the getDocs function can be adjusted to use a for filtering.

import { docs, createFrontmatterProcessor } from 'mdx-butler';
import { cache } from 'react';

type Frontmatter = {
  title: string;
  description: string;
};

const getDocs = cache((path?: string) =>
  docs<Frontmatter>({
    frontmatterProcessor: (x) => {
      const processor = createFrontmatterProcessor<Frontmatter>({
        title: {
          required: true,
        },
        description: {},
      });

      return (processor(x) && path == undefined) || path === x.path;
    },
  })
);

// ...

export default async function Docs({
  params: { slug },
}: {
  params: { slug: string[] };
}) {
  const path = slug.join('/');
  const docs = await getDocs(path);

  const doc = docs.find((x) => path === x.path);

  if (!doc) return <div>not found</div>;

  // ...
}