Overview

MDX lets a content file embed components. In Astro it needs the @astrojs/mdx integration, and it works in src/pages/ and in content collections. Since Astro 7, both .md and .mdx render through Sätteri, a native Rust processor with GitHub-Flavored Markdown, smart punctuation, and heading IDs built in. Use MDX only when a page needs components, islands, or callouts; keep prose-only pages as .md.

Install with astro add

npx astro add mdx

By default .mdx inherits the project’s markdown settings (extendMarkdownConfig: true) and processor. Set mdx({ processor: ... }) to give MDX a different processor from Markdown.

Import components at the top of each MDX file

Imports are local to the file. Astro components and framework components both work, and framework components take client:* directives like they do in .astro files.

import Callout from "../components/Callout.astro";
import { Chart } from "../components/Chart";
 
<Callout type="warning">Read this first.</Callout>
 
<Chart data={[1, 2, 3]} client:visible />

An island with client:load in an MDX file ships its framework runtime to every page that renders that file, so prefer client:visible; see astro-islands.

Replace HTML elements with the components prop

Astro does not use MDXProvider. Render the entry and pass a components map to <Content />, keyed by HTML element name.

---
import { getEntry, render } from "astro:content";
import Heading from "../components/Heading.astro";
 
const entry = await getEntry("posts", Astro.params.id);
const { Content, headings } = await render(entry);
---
<Content components={{ h2: Heading }} />

Use headings from render() to build a table of contents instead of exporting data from the MDX file.

Choose the processor for plugins

Sätteri is the default and needs no install. To add its mdastPlugins and hastPlugins, install @astrojs/markdown-satteri and pass satteri({ ... }) to markdown.processor. If you depend on remark, rehype, or recma plugins and cannot port them, install @astrojs/markdown-remark and switch to the unified processor.

// astro.config.mjs
import { unified } from "@astrojs/markdown-remark";
import rehypeExternalLinks from "rehype-external-links";
 
export default defineConfig({
  markdown: {
    processor: unified({ rehypePlugins: [[rehypeExternalLinks, { target: "_blank" }]] }),
  },
});

Recma plugins work only with unified: pass them as unified({ recmaPlugins }) to mdx({ processor }). The markdown.remarkPlugins, rehypePlugins, gfm, and smartypants options are deprecated; they still work but require @astrojs/markdown-remark to be installed. Under unified, add rehypeHeadingIds from the same package before any plugin that relies on heading IDs.