By Titus Wormer
Getting started
This article explains how to integrate MDX into your project. It shows how to use MDX with your bundler and JSX runtime of choice. To understand how the MDX format works, we recommend that you start with § What is MDX. See § Using MDX when you’re all set up and ready to use MDX.
Contents
Prerequisites
MDX relies on JSX, so it’s required that your project supports JSX as well. Any JSX runtime (React, Preact, Vue, etc.) will do. Note that we do compile JSX to JavaScript for you so you don’t have to set that up.
All @mdx-js/*
packages are written in modern JavaScript. A Node.js version of 16.0 or later is needed to use them. Our packages are also ESM only, so they have to be import
ed instead of require
d.
Note: Using Rust instead of Node.js? Try mdxjs-rs
!
Quick start
Bundler
MDX is a language that’s compiled to JavaScript. (We also compile regular markdown to JavaScript.) The easiest way to get started is to use an integration for your bundler if you have one:
- if you use esbuild, install and configure
@mdx-js/esbuild
- if you use Rollup (or Vite), install and configure
@mdx-js/rollup
- if you use webpack (or Next.js), install and configure
@mdx-js/loader
You can also use MDX without bundlers:
- you can import MDX files in Node.js with
@mdx-js/node-loader
- you can use our core compiler
@mdx-js/mdx
to compile MDX files - you can use our core compiler
@mdx-js/mdx
to evaluate (compile and run) MDX files
For more info on these tools, see their dedicated sections: ¶ Next.js, ¶ Node.js, ¶ Rollup, ¶ Vite, ¶ esbuild, and ¶ webpack.
JSX
Now you’ve set up an integration or @mdx-js/mdx
itself, it’s time to configure your JSX runtime.
- if you use React, that’s the default; optionally install and configure
@mdx-js/react
- if you use Preact, set
jsxImportSource
inProcessorOptions
to'preact'
; optionally install and configure@mdx-js/preact
- if you use Svelte, install
svelte-jsx
, and setjsxImportSource
inProcessorOptions
to'svelte-jsx'
- if you use Vue, set
jsxImportSource
inProcessorOptions
to'vue'
; optionally install and configure@mdx-js/vue
- if you use Solid, set
jsxImportSource
inProcessorOptions
to'solid-js/h'
- if you use Emotion, set
jsxImportSource
inProcessorOptions
to'@emotion/react'
- if you use Theme UI, install and configure
@mdx-js/react
, then wrap your MDX content in a<ThemeProvider />
Other JSX runtimes are supported by setting jsxImportSource
in ProcessorOptions
.
For more info on these tools, see their dedicated sections: ¶ Emotion, ¶ Preact, ¶ React, ¶ Solid, ¶ Svelte, ¶ Theme UI, and ¶ Vue.
Editor
You can enhance the experience of using MDX by adding support of it to your editor:
- if you use VS Code, try
mdx-js/vscode-mdx
- if you use Vim, try
jxnblk/vim-mdx-js
- if you use Sublime Text, try
jonsuh/mdx-sublime
- if you use JetBrains IntelliJ/WebStorm, try
JetBrains/mdx-intellij-plugin
The syntax highlighting that powers our VS Code extension and that is used to highlight code blocks on GitHub is maintained at wooorm/markdown-tm-language
.
Types
Expand example of typed imports
First install the package:
npm install @types/mdx
…TypeScript should automatically pick it up:
import Post from './post.mdx' // `Post` is now typed.
Our packages are typed with TypeScript. For types to work, the JSX
namespace must be typed. This is done by installing and using the types of your framework, such as @types/react
.
To enable types for imported .mdx
, .md
, etc., install and use @types/mdx
. This package also exports several useful types, such as MDXComponents
which represents the components
prop. You can import them like so:
import type {MDXComponents} from 'mdx/types.js'
Security
MDX is a programming language. If you trust your authors, that’s fine. If you don’t, it’s unsafe.
Do not let random people from the internet write MDX. If you do, you might want to look into using <iframe>
s with sandbox
, but security is hard, and that doesn’t seem to be 100%. For Node.js, vm2 sounds interesting. But you should probably also sandbox the whole OS using Docker or similar, perform rate limiting, and make sure processes can be killed when taking too long.
Integrations
Bundlers
esbuild
Expand example
import mdx from '@mdx-js/esbuild'
import esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['index.mdx'],
format: 'esm',
outfile: 'output.js',
plugins: [mdx({/* jsxImportSource: …, otherOptions… */})]
})
We support esbuild. Install and configure the esbuild plugin @mdx-js/esbuild
. Configure your JSX runtime depending on which one (React, Preact, Vue, etc.) you use.
To use more modern JavaScript features than what your users support, configure esbuild’s target
.
Rollup
Expand example
import mdx from '@mdx-js/rollup'
import {babel} from '@rollup/plugin-babel'
/** @type {import('rollup').RollupOptions} */
const config = {
// …
plugins: [
// …
mdx({/* jsxImportSource: …, otherOptions… */})
// Babel is optional:
babel({
// Also run on what used to be `.mdx` (but is now JS):
extensions: ['.js', '.jsx', '.cjs', '.mjs', '.md', '.mdx'],
// Other options…
})
]
}
export default config
We support Rollup. Install and configure the Rollup plugin @mdx-js/rollup
. Configure your JSX runtime depending on which one (React, Preact, Vue, etc.) you use.
To use more modern JavaScript features than what your users support, install and configure @rollup/plugin-babel
.
See also ¶ Vite, if you use Rollup through it, for more info.
Webpack
Expand example
/** @type {import('webpack').Configuration} */
const webpackConfig = {
module: {
// …
rules: [
// …
{
test: /\.mdx?$/,
use: [
// Babel is optional:
{loader: 'babel-loader', options: {}},
{
loader: '@mdx-js/loader',
/** @type {import('@mdx-js/loader').Options} */
options: {/* jsxImportSource: …, otherOptions… */}
}
]
}
]
}
}
export default webpackConfig
We support webpack. Install and configure the webpack loader @mdx-js/loader
. Configure your JSX runtime depending on which one (React, Preact, Vue, etc.) you use.
To use more modern JavaScript features than what your users support, install and configure babel-loader
.
See also ¶ Next.js, if you use webpack through it, for more info.
Build systems
Vite
Expand example
import mdx from '@mdx-js/rollup'
import {defineConfig} from 'vite'
const viteConfig = defineConfig({
plugins: [
mdx(/* jsxImportSource: …, otherOptions… */)
]
})
export default viteConfig
We support Vite. Install and configure the Rollup plugin @mdx-js/rollup
. Configure your JSX runtime depending on which one (React, Preact, Vue, etc.) you use.
To use more modern JavaScript features than what your users support, configure Vite’s build.target
.
Note: If you also use vitejs/vite-plugin-react
, you must force @mdx-js/rollup
to run in the pre
phase before it:
// …
const viteConfig = defineConfig({
plugins: [
{enforce: 'pre', ...mdx(/* jsxImportSource: …, otherOptions… */)},
react({include: /\.(jsx|js|mdx|md|tsx|ts)$/})
]
})
// …
See also ¶ Rollup which is used in Vite and see ¶ Vue if you’re using that, for more info.
Compilers
Babel
Expand plugin and sample use
This plugin:
import path from 'node:path'
import parser from '@babel/parser'
import {compileSync} from '@mdx-js/mdx'
import estreeToBabel from 'estree-to-babel'
export function babelPluginSyntaxMdx() {
// Tell Babel to use a different parser.
return {parserOverride: babelParserWithMdx}
}
// A Babel parser that parses MDX files with `@mdx-js/mdx` and passes any
// other things through to the normal Babel parser.
function babelParserWithMdx(value, options) {
if (options.sourceFileName && /\.mdx?$/.test(options.sourceFileName)) {
// Babel does not support async parsers, unfortunately.
return compileSync(
{value, path: options.sourceFileName},
// Tell `@mdx-js/mdx` to return a Babel tree instead of serialized JS.
{recmaPlugins: [recmaBabel], /* jsxImportSource: …, otherOptions… */}
).result
}
return parser.parse(value, options)
}
// A “recma” plugin is a unified plugin that runs on the estree (used by
// `@mdx-js/mdx` and much of the JS ecosystem but not Babel).
// This plugin defines `'estree-to-babel'` as the compiler,
// which means that the resulting Babel tree is given back by `compileSync`.
function recmaBabel() {
this.compiler = estreeToBabel
}
…can be used like so with the Babel API:
import babel from '@babel/core'
import {babelPluginSyntaxMdx} from './plugin.js'
// Note that a filename must be set for our plugin to know it’s MDX instead of JS.
await babel.transformAsync(file, {filename: 'example.mdx', plugins: [babelPluginSyntaxMdx]})
You should probably use Rollup or webpack instead of Babel directly as that gives the best interface. It is possible to use @mdx-js/mdx
in Babel and it’s a bit faster, as it skips @mdx-js/mdx
serialization and Babel parsing, if Babel is used anyway.
Babel does not support syntax extensions to its parser (it has “syntax” plugins but those only turn internal flags on or off). It does support setting a different parser. Which in turn lets us choose whether to use the @mdx-js/mdx
or @babel/parser
.
Site generators
Astro
Astro has its own MDX integration. You can add the integration with the Astro CLI: npx astro add mdx
.
This base setup lets you import markdown, Astro components, and MDX files as components. See Astro’s Framework components guide for info on how to use components from frameworks in your MDX files.
For more on how to combine Astro and MDX, see Astro’s MDX integration docs.
Docusaurus
Docusaurus supports MDX by default. See Docusaurus’ MDX and React guide for info on how to use MDX with Docusaurus.
Gatsby
Gatsby has its own plugin to support MDX. See gatsby-plugin-mdx
on how to use MDX with Gatsby.
Next.js
Expand example
import nextMdx from '@next/mdx'
const withMdx = nextMdx({
// By default only the `.mdx` extension is supported.
extension: /\.mdx?$/,
options: {/* otherOptions… */}
})
const nextConfig = withMdx({
// Support MDX files as pages:
pageExtensions: ['md', 'mdx', 'tsx', 'ts', 'jsx', 'js'],
})
export default nextConfig
Next.js has its own MDX integration. Install and configure @next/mdx
.
Do not use providerImportSource
and @mdx-js/react
with Next to inject components. Add an mdx-components.tsx
(in src/
or /
) file instead. See Configuring MDX on nextjs.org
for more info.
Parcel
Parcel has its own plugin to support MDX. See @parcel/transformer-mdx
on how to use MDX with Parcel.
JSX runtimes
Emotion
Expand example
import {compile} from '@mdx-js/mdx'
const js = String(await compile('# hi', {jsxImportSource: '@emotion/react', /* otherOptions… */}))
Emotion is supported when jsxImportSource
in ProcessorOptions
is set to '@emotion/react'
. You can optionally install and configure @mdx-js/react
to support context based component passing.
See also ¶ React, which is used in Emotion, and see ¶ Rollup and ¶ webpack, which you might be using, for more info.
Ink
Expand example
# Hi!
import React from 'react'
import {Text, render} from 'ink'
import Content from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
const components = {
h1(properties) {
return React.createElement(Text, {bold: true, ...properties})
},
p: Text
}
render(React.createElement(Content, {components}))
Can be used with:
node --loader=@mdx-js/node-loader example.js
Ink uses the React JSX runtime, so set that up. You will need to swap HTML elements out for Ink’s components. See § Table of components for what those are and Ink’s docs on what they can be replaced with.
See also ¶ Node.js and ¶ React for more info.
Preact
Expand example
import {compile} from '@mdx-js/mdx'
const js = String(await compile('# hi', {jsxImportSource: 'preact', /* otherOptions… */}))
Preact is supported when jsxImportSource
in ProcessorOptions
is set to 'preact'
. You can optionally install and configure @mdx-js/preact
to support context based component passing.
See also ¶ Rollup, ¶ esbuild, and ¶ webpack, which you might be using, for more info.
React
React is supported by default. You can optionally install and configure @mdx-js/react
to support context based component passing.
See also ¶ Rollup, ¶ esbuild, and ¶ webpack, which you might be using, for more info.
Theme UI
Expand example
Example w/o @mdx-js/react
import {base} from '@theme-ui/preset-base'
import {ThemeProvider, components} from 'theme-ui'
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS.
<ThemeProvider theme={base}>
<Post components={components} />
</ThemeProvider>
Example w/ @mdx-js/react
import {base} from '@theme-ui/preset-base'
import {ThemeProvider} from 'theme-ui'
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS.
<ThemeProvider theme={base}>
<Post />
</ThemeProvider>
Theme UI is a React-specific library that depends on context to access its effective components. You can install and configure @mdx-js/react
to support context based component passing.
See also ¶ Emotion, ¶ React, ¶ Rollup, and ¶ esbuild, ¶ webpack, which you might be using, for more info.
Svelte
Expand example
import {compile} from '@mdx-js/mdx'
const js = String(await compile('# hi', {jsxImportSource: 'svelte-jsx', /* otherOptions… */}))
Svelte is supported when jsxImportSource
in ProcessorOptions
is set to 'svelte-jsx'
.
See also ¶ Rollup, ¶ esbuild, and ¶ webpack, which you might be using, for more info.
Vue
Expand example
import {compile} from '@mdx-js/mdx'
const js = String(await compile('# hi', {jsxImportSource: 'vue', /* otherOptions… */}))
Vue is supported when jsxImportSource
in ProcessorOptions
is set to 'vue'
. You can optionally install and configure @mdx-js/vue
to support context based component passing.
See also ¶ Vite, which you might be using, for more info.
Solid
Expand example
import {compile} from '@mdx-js/mdx'
const js = String(await compile('# hi', {jsxImportSource: 'solid-js/h', /* otherOptions… */}))
Solid is supported when jsxImportSource
in ProcessorOptions
is set to 'solid-js/h'
.
See also ¶ Rollup and ¶ Vite, which you might be using, for more info.
JavaScript engines
Node.js
MDX files can be imported in Node by using @mdx-js/node-loader
. See its readme on how to configure it.
Further reading
- If you want to use MDX content in your project, see § Using MDX
- If you’re getting errors integrating MDX, see § Troubleshooting MDX or § Support