Guides / MDX on demand
This guide shows how to use @mdx-js/mdx
to compile MDX on the server and run the result on clients. Some frameworks, such as Next.js and Remix, make it easy to split work between servers and clients. Using that it’s possible to for example do most of the work on demand on the server instead of at build time, then pass the resulting data to clients, where they finally use it.
This is similar to what people sometimes use mdx-bundler
or next-mdx-remote
for, but MDX also supports it.
On the server:
import {compile} from '@mdx-js/mdx'
const code = String(await compile('# hi', {
outputFormat: 'function-body',
/* …otherOptions */
}))
// To do: send `code` to the client somehow.
(alias) function compile(vfileCompatible: any, compileOptions?: Readonly<CompileOptions> | null | undefined): Promise<VFile>
import compile
Compile MDX to JS.
const code: string
var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
(alias) compile(vfileCompatible: any, compileOptions?: Readonly<CompileOptions> | null | undefined): Promise<VFile>
import compile
Compile MDX to JS.
(property) outputFormat?: "function-body" | "program" | null | undefined
Output format to generate (default: 'program'
); in most cases 'program'
should be used, it results in a whole program; internally evaluate
uses 'function-body'
to compile to code that can be passed to run
; in some cases, you might want what evaluate
does in separate steps, such as when compiling on the server and running on the client.
On the client:
import {run} from '@mdx-js/mdx'
import * as runtime from 'react/jsx-runtime'
const code = '' // To do: get `code` from server somehow.
// @ts-expect-error: `runtime` types are currently broken.
const {default: Content} = await run(code, {...runtime, baseUrl: import.meta.url})
(alias) function run(code: {
toString(): string;
}, options: RunOptions): Promise<MDXModule>
import run
Run code compiled with outputFormat: 'function-body'
.
☢️ Danger: this
eval
s JavaScript.
default
field set to the component; anything else that was exported is available too.import runtime
const code: ""
(property) MDXModule.default: MDXContent
A functional JSX component which renders the content of the MDX file.
const Content: MDXContent
A functional JSX component which renders the content of the MDX file.
(alias) run(code: {
toString(): string;
}, options: RunOptions): Promise<MDXModule>
import run
Run code compiled with outputFormat: 'function-body'
.
☢️ Danger: this
eval
s JavaScript.
default
field set to the component; anything else that was exported is available too.const code: ""
import runtime
(property) baseUrl?: any
Use this URL as import.meta.url
and resolve import
and export … from
relative to it (optional, example: import.meta.url
); this option can also be given at compile time in CompileOptions
; you should pass this (likely at runtime), as you might get runtime errors when using import.meta.url
/ import
/ export … from
otherwise.
The type of import.meta
.
If you need to declare that a given property exists on import.meta
, this type may be augmented via interface merging.
any
Content
is now an MDXContent
component that you can use like normal in your framework (see § Using MDX).
More information is available in the API docs of @mdx-js/mdx
for compile
and run
. For other use cases, you can also use evaluate
, which both compiles and runs in one.
Note: MDX is not a bundler (esbuild, webpack, and Rollup are bundlers): you can’t import other code from the server within the string of MDX and get a nicely minified bundle out or so.
Some frameworks let you write the server and client code in one file, such as Next.
/**
* @import {MDXModule} from 'mdx/types.js'
* @import {Dispatch, SetStateAction} from 'react'
*/
import {compile, run} from '@mdx-js/mdx'
import {Fragment, useEffect, useState} from 'react'
import * as runtime from 'react/jsx-runtime'
/**
* @param {{code: string}} props
* @returns {JSX.Element}
*/
export default function Page({code}) {
/** @type {[MDXModule | undefined, Dispatch<SetStateAction<MDXModule | undefined>>]} */
const [mdxModule, setMdxModule] = useState()
const Content = mdxModule ? mdxModule.default : Fragment
useEffect(
function () {
;(async function () {
// @ts-expect-error: `runtime` types are currently broken.
setMdxModule(await run(code, {...runtime, baseUrl: import.meta.url}))
})()
},
[code]
)
return <Content />
}
export async function getStaticProps() {
const code = String(
await compile('# hi', {
outputFormat: 'function-body'
/* …otherOptions */
})
)
return {props: {code}}
}
(alias) function compile(vfileCompatible: any, compileOptions?: Readonly<CompileOptions> | null | undefined): Promise<VFile>
import compile
Compile MDX to JS.
(alias) function run(code: {
toString(): string;
}, options: RunOptions): Promise<MDXModule>
import run
Run code compiled with outputFormat: 'function-body'
.
☢️ Danger: this
eval
s JavaScript.
default
field set to the component; anything else that was exported is available too.(alias) const Fragment: React.ExoticComponent<{
children?: React.ReactNode | undefined;
}>
import Fragment
Lets you group elements without a wrapper node.
import { Fragment } from 'react';
<Fragment>
<td>Hello</td>
<td>World</td>
</Fragment>
// Using the <></> shorthand syntax:
<>
<td>Hello</td>
<td>World</td>
</>
(alias) function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
import useEffect
Accepts a function that contains imperative, possibly effectful code.
(alias) function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>] (+1 overload)
import useState
Returns a stateful value, and a function to update it.
import runtime
function Page({ code }: {
code: string;
}): JSX.Element
(parameter) code: string
const mdxModule: MDXModule | undefined
const setMdxModule: Dispatch<SetStateAction<MDXModule | undefined>>
(alias) useState<MDXModule>(): [MDXModule | undefined, Dispatch<SetStateAction<MDXModule | undefined>>] (+1 overload)
import useState
Returns a stateful value, and a function to update it.
const Content: React.ExoticComponent<{
children?: React.ReactNode | undefined;
}> | MDXContent
const mdxModule: MDXModule | undefined
const mdxModule: MDXModule
(property) MDXModule.default: MDXContent
A functional JSX component which renders the content of the MDX file.
(alias) const Fragment: React.ExoticComponent<{
children?: React.ReactNode | undefined;
}>
import Fragment
Lets you group elements without a wrapper node.
import { Fragment } from 'react';
<Fragment>
<td>Hello</td>
<td>World</td>
</Fragment>
// Using the <></> shorthand syntax:
<>
<td>Hello</td>
<td>World</td>
</>
(alias) useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
import useEffect
Accepts a function that contains imperative, possibly effectful code.
const setMdxModule: (value: SetStateAction<MDXModule | undefined>) => void
(alias) run(code: {
toString(): string;
}, options: RunOptions): Promise<MDXModule>
import run
Run code compiled with outputFormat: 'function-body'
.
☢️ Danger: this
eval
s JavaScript.
default
field set to the component; anything else that was exported is available too.(parameter) code: string
import runtime
(property) baseUrl?: any
Use this URL as import.meta.url
and resolve import
and export … from
relative to it (optional, example: import.meta.url
); this option can also be given at compile time in CompileOptions
; you should pass this (likely at runtime), as you might get runtime errors when using import.meta.url
/ import
/ export … from
otherwise.
The type of import.meta
.
If you need to declare that a given property exists on import.meta
, this type may be augmented via interface merging.
any
(parameter) code: string
const Content: React.ExoticComponent<{
children?: React.ReactNode | undefined;
}> | MDXContent
function getStaticProps(): Promise<{
props: {
code: string;
};
}>
const code: string
var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
(alias) compile(vfileCompatible: any, compileOptions?: Readonly<CompileOptions> | null | undefined): Promise<VFile>
import compile
Compile MDX to JS.
(property) outputFormat?: "function-body" | "program" | null | undefined
Output format to generate (default: 'program'
); in most cases 'program'
should be used, it results in a whole program; internally evaluate
uses 'function-body'
to compile to code that can be passed to run
; in some cases, you might want what evaluate
does in separate steps, such as when compiling on the server and running on the client.
(property) props: {
code: string;
}
(property) code: string