Dokumentation

MDX Compilation

MDX Compilation for Cloudflare Workers

This directory contains the MDX compilation infrastructure that enables MDX rendering in Cloudflare Workers without using eval().

Architecture

Problem

Cloudflare Workers prohibit runtime code generation (eval(), new Function()), but the default @content-collections/mdx package compiles MDX to JavaScript strings that require eval() to execute.

Solution

We pre-compile MDX files to ES modules at build time, then use standard dynamic imports (which don't use eval()) to load them.

File Structure

apps/web/
├── content-collections.ts          # MDX compilation configuration
├── lib/
│   └── mdx-loader.ts              # Shared MDX module loader utility
├── scripts/
│   └── generate-mdx-index.js      # Generates index files for dynamic imports
└── .content-collections/
    └── mdx-modules/               # Generated ES modules (gitignored)
        ├── posts/
        │   ├── _index.js          # Auto-generated import registry
        │   ├── first-post.js      # Compiled MDX module
        │   └── ...
        ├── legal/
        └── docs/

How It Works

1. Build-time Compilation (content-collections.ts)

MDX files are compiled to ES modules using @mdx-js/mdx:

  • Posts/Legal: Basic compilation with Shiki syntax highlighting
  • Docs: Extended compilation with fumadocs plugins (TOC, heading IDs, code icons)

Key features:

  • Extracts shared compilation logic into reusable functions
  • Centralizes plugin configuration
  • Handles file writing and path sanitization

2. Index Generation (generate-mdx-index.js)

After compilation, generates _index.js files that map filenames to dynamic imports:

export const modules = {
  "first-post.js": () => import("./first-post.js"),
  "second-post.js": () => import("./second-post.js"),
}

This allows components to dynamically import modules without using eval().

3. Runtime Loading (lib/mdx-loader.ts)

The shared importMDXModule() utility:

  • Uses explicit switch-case for collection imports (avoids dynamic path construction)
  • Loads the appropriate _index.js for a collection
  • Retrieves the module import function by filename
  • Provides detailed error messages if modules are not found
  • Fully type-safe with no webpack warnings

4. Component Usage

Components use the loader to render MDX:

import { importMDXModule } from "../../../../lib/mdx-loader"

export async function PostContent({ content, type }) {
  const { default: MDXContent } = await importMDXModule(type, content)
  return <MDXContent components={components} />
}

Configuration

Shiki Theme

The syntax highlighting theme is centralized in content-collections.ts:

const SHIKI_THEME = "nord" as const

Collections

To add a new MDX collection:

  1. Add to content-collections.ts:
const myCollection = defineCollection({
  name: "myCollection",
  transform: async (document, context) => {
    const compiledCode = await compileMDXToModule(
      document.content,
      document._meta.filePath,
      baseCompileOptions
    )
    const fileName = await writeCompiledMDX("myCollection", document._meta.fileName, compiledCode)
    return { ...document, body: fileName }
  },
})
  1. Add to generate-mdx-index.js:
const COLLECTIONS = [
  // ...
  { name: "myCollection", dir: "myCollection" }
]
  1. Update lib/mdx-loader.ts:
async function loadCollectionIndex(collection: CollectionType) {
  switch (collection) {
    case "posts":
      return import("../.content-collections/mdx-modules/posts/_index.js")
    case "legal":
      return import("../.content-collections/mdx-modules/legal/_index.js")
    case "docs":
      return import("../.content-collections/mdx-modules/docs/_index.js")
    case "myCollection":
      return import("../.content-collections/mdx-modules/myCollection/_index.js")
  }
}

Benefits

  1. Cloudflare Compatible: No eval() at runtime
  2. Performance: SSG pre-renders all pages at build time
  3. Type Safety: Full TypeScript support with proper types
  4. Maintainable: Shared utilities eliminate code duplication
  5. Developer Experience: Clear error messages and documentation
  6. Fumadocs Support: All features (TOC, syntax highlighting, code icons) work correctly

Build Process

# 1. Compile MDX to ES modules
pnpm run build:content-collections

# 2. Generate import index files (automatic)
node scripts/generate-mdx-index.js

# 3. Build Next.js app
pnpm run build

Troubleshooting

"MDX module not found" error

  • Ensure build:content-collections ran successfully
  • Check that the _index.js file exists for the collection
  • Verify the filename matches exactly (case-sensitive)

Fumadocs features not working

  • Ensure the correct plugins are applied in content-collections.ts
  • For docs: remarkHeading, remarkStructure, rehypeCode, rehypeShiki
  • For posts: rehypeShiki for syntax highlighting

Build warnings about "dependency is an expression"

  • This is expected due to dynamic import paths in mdx-loader.ts
  • The warning is safe to ignore as the paths are constrained to known collections