1 min read

@document0/mdx

The MDX package compiles your .mdx files into executable JavaScript, extracts frontmatter, and runs Shiki over code blocks, all in one call.

processMdx

import { processMdx } from "@document0/mdx";

const { code, frontmatter, toc } = await processMdx(source, options);

Parameters

ParameterTypeDescription
sourcestringRaw MDX/Markdown source content
optionsProcessMdxOptionsProcessing options (see below)

Options

interface ProcessMdxOptions {
  highlighter?: HighlighterGeneric<BundledLanguage, BundledTheme>;
  defaultLanguage?: string;       // default: "plaintext"
  theme?: string;                 // default: "github-dark"
  remarkPlugins?: PluggableList;
  rehypePlugins?: PluggableList;
  jsxRuntime?: "automatic" | "classic"; // default: "automatic"
}
OptionDefaultDescription
highlighter-Shiki highlighter instance. Omit to skip syntax highlighting
defaultLanguage"plaintext"Language used for code blocks with no language specified
theme"github-dark"Shiki theme name
remarkPlugins[]Additional remark plugins to run
rehypePlugins[]Additional rehype plugins to run
jsxRuntime"automatic"JSX runtime mode passed to @mdx-js/mdx

Return value

interface ProcessMdxResult {
  code: string;          // compiled JS; pass to @mdx-js/mdx run()
  frontmatter: Record<string, unknown>;
  toc: TocEntry[];
}

TocEntry

interface TocEntry {
  id: string;    // heading anchor id (auto-generated from text)
  text: string;  // heading text content
  depth: number; // 1–6
}

Built-in plugins

remarkToc

Extracts headings into toc without modifying the document tree.

rehypeShiki

Replaces fenced code blocks with Shiki-highlighted hast output. Uses codeToHast internally so the result is proper hast nodes, compatible with @mdx-js/mdx's JSX runtime.

Language is read from the code fence tag:

```typescript
const x: number = 1;
```

Setting up Shiki

Create the highlighter once and reuse it across requests (it's expensive to initialise):

import { createHighlighter } from "shiki";

let highlighter: Awaited<ReturnType<typeof createHighlighter>> | null = null;

export async function getHighlighter() {
  if (highlighter) return highlighter;
  highlighter = await createHighlighter({
    themes: ["github-dark", "github-light"],
    langs: ["typescript", "javascript", "bash", "json", "css"],
  });
  return highlighter;
}

Then pass it to processMdx:

const highlighter = await getHighlighter();
const { code, toc } = await processMdx(raw, { highlighter, theme: "github-dark" });

Running compiled MDX

The code returned by processMdx is a compiled JS module. Use @mdx-js/mdx's run to execute it:

import { run } from "@mdx-js/mdx";
import * as runtime from "react/jsx-runtime";

const { default: MDXContent } = await run(code, {
  ...(runtime as object),
  baseUrl: import.meta.url,
});

// Render it; pass your component overrides
<MDXContent components={myComponents} />

Custom MDX components

Pass a components object to override any HTML element:

const components = {
  h2: ({ children, id }) => (
    <h2 id={id} className="text-2xl font-bold mt-10 mb-4 text-white">
      {children}
    </h2>
  ),
  pre: ({ children }) => (
    <pre className="rounded-xl border border-zinc-800 bg-zinc-900 p-4 overflow-x-auto">
      {children}
    </pre>
  ),
};