MDX Content Format

MDX lets you use JSX components directly inside your Markdown content. It's the hybrid format between pure Markdown (.md) and pure JSX (.tsx).

Format Spectrum

Dune supports three content formats:

  • .md — Pure Markdown, rendered through theme templates. Best for blog posts, documentation.
  • .mdx — Markdown + JSX. Write prose in Markdown, embed interactive components. Best for tutorials with live examples.
  • .tsx — Pure JSX. The component IS the content. Best for landing pages, dashboards.

Writing MDX

MDX files use the same frontmatter as Markdown files:

---
title: "My Tutorial"
taxonomy:
  tag: [tutorial, interactive]
---

# Welcome

Regular **markdown** works normally.

You can also embed JSX components inline.

Key Differences from Markdown

  1. JSX expressions — You can use JSX syntax inline with markdown
  2. Standard HTML always works — All standard HTML elements (<div>, <aside>, <details>, etc.) can be used directly in MDX without any setup
  3. Custom components need registration — Theme components (<Alert />, <Chart />, etc.) must be registered via the component registry (see below)
  4. Same frontmatter — YAML between --- delimiters, identical to .md
  5. Same routing — Folder = page, numeric prefixes for ordering
  6. Same collections — MDX pages appear in collections and taxonomy queries
  7. Same API — REST API returns format: "mdx" for MDX pages

Rendering pipeline

When a .mdx file is requested:

  1. Parse frontmatter — YAML between --- delimiters extracted with gray-matter
  2. Compile MDX@mdx-js/mdx compiles the source to a JavaScript module
  3. Evaluate — The compiled module is evaluated to get a Preact component
  4. Render — The component is rendered to an HTML string via Preact SSR
  5. Media resolution — Relative image paths are rewritten to /content-media/ URLs (same as Markdown)

The output is a static HTML string — MDX content is server-rendered, not client-side.

Component registry

MDX content can use custom JSX components that aren't standard HTML elements. Components are loaded from a convention file in your active theme.

Theme convention (recommended)

Create themes/{your-theme}/mdx-components.ts with a default export mapping component names to Preact component functions:

// themes/my-theme/mdx-components.ts
import { Alert } from "./components/Alert.tsx";
import { Chart } from "./components/Chart.tsx";
import { Callout } from "./components/Callout.tsx";

export default { Alert, Chart, Callout };

Dune loads this file automatically at startup. No other configuration is needed. In debug mode, the loaded component names are logged to the console.

MDX content can then use registered components by name:

---
title: "My Tutorial"
---

# Getting Started

<Alert type="warning">
  Make sure you've installed Deno 2 before continuing.
</Alert>

Programmatic API

For custom bootstrap setups outside the standard dune serve / dune dev flow, use the programmatic API:

import { createMdxComponentRegistry, MdxHandler, FormatRegistry } from "@dune/core";
import { Alert } from "./components/Alert.tsx";

const registry = createMdxComponentRegistry({ Alert });
const formats = new FormatRegistry();
formats.register(new MdxHandler({ components: registry }));
// Pass formats to createDuneEngine...

Any component name not in the registry falls back to the equivalent HTML element, or renders nothing if there is no matching HTML tag.

When to Use MDX

Choose MDX when you need the simplicity of Markdown for most of your content but want to embed interactive elements or custom components in specific sections.

For pure prose, use .md. For fully programmatic pages, use .tsx. For the middle ground, use .mdx.