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.jsfor 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 constCollections
To add a new MDX collection:
- 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 }
},
})- Add to
generate-mdx-index.js:
const COLLECTIONS = [
// ...
{ name: "myCollection", dir: "myCollection" }
]- 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
- Cloudflare Compatible: No
eval()at runtime - Performance: SSG pre-renders all pages at build time
- Type Safety: Full TypeScript support with proper types
- Maintainable: Shared utilities eliminate code duplication
- Developer Experience: Clear error messages and documentation
- 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 buildTroubleshooting
"MDX module not found" error
- Ensure
build:content-collectionsran successfully - Check that the
_index.jsfile 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:
rehypeShikifor 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