Overview

MDX lets you use JSX components inside markdown files. In Astro, the @astrojs/mdx integration enables .mdx files in src/pages/ and inside Content Collections. Use MDX when a content page needs interactive islands, custom callout components, or embedded data visualizations that plain markdown cannot express. For pages that are entirely prose, prefer .md over .mdx; MDX adds a build-time transform cost and ships a slightly larger output.

Install the integration with astro add

npx astro add mdx

This installs @astrojs/mdx and adds it to astro.config.mjs automatically. No further config is needed for basic MDX support. See astro-integrations for the full astro add workflow.

Import components directly in an MDX file

Components used in one MDX file go at the top of that file. They are local imports, not global.

---
title: "My Post"
---
import { Chart } from "../components/Chart";
import Callout from "../components/Callout.astro";
 
<Callout type="warning">Read this first.</Callout>
 
The data tells an interesting story.
 
<Chart data={[1, 2, 3]} />

Astro components (.astro) and framework components both work in MDX. Add client:* directives to framework components the same way you would in a .astro file.

Use components prop to replace HTML elements globally

Pass a components object to an MDX layout to override HTML elements site-wide. This replaces every <h2>, <a>, or <img> in MDX output with a custom component without touching individual files.

---
// src/layouts/BlogLayout.astro
import { MDXProvider } from "@astrojs/mdx";
import Heading from "../components/Heading.astro";
import ExternalLink from "../components/ExternalLink.astro";
 
const components = { h2: Heading, a: ExternalLink };
---
<slot components={components} />

Use this for syntax-highlighted code blocks, custom image wrappers, and anchor tags that add external link icons. The components object flows through the layout and applies to all MDX content rendered by that layout.

Add remark and rehype plugins in astro.config.mjs

Remark transforms the markdown AST before MDX processes it; rehype transforms the HTML AST after. Configure plugins in astro.config.mjs.

// astro.config.mjs
import { defineConfig } from "astro/config";
import mdx from "@astrojs/mdx";
import remarkGfm from "remark-gfm";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeSlug from "rehype-slug";
 
export default defineConfig({
  integrations: [
    mdx({
      remarkPlugins: [remarkGfm],
      rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings],
    }),
  ],
});

Common plugin picks: remark-gfm for GitHub Flavored Markdown (tables, task lists), rehype-slug to add IDs to headings, rehype-autolink-headings for anchor links, and rehype-pretty-code or shiki for syntax highlighting.

Export named values from MDX for metadata access

MDX files can export named constants. Use this to expose structured data to the consuming page without duplicating it in frontmatter.

export const toc = [
  { title: "Overview", slug: "overview" },
  { title: "Rules", slug: "rules" },
];

Access the export in a .astro file that imports the MDX module: const { toc } = await import("./my-post.mdx"). This is useful for generating a table of contents without parsing headings in a separate step.

Avoid importing MDX into other MDX files

Importing one MDX file into another creates a dependency graph that Vite re-transforms on every change to either file. On large sites this slows incremental builds noticeably. Compose shared MDX content via Astro components or Content Collection queries instead.

Also avoid heavy client-side framework components in MDX unless they need interactivity. A React component without a client:* directive renders to static HTML at build time, which is fine. A component with client:load in an MDX file ships its framework runtime to every page that includes that MDX file.