Skip to navigation
2-3 minutes read
By Titus Wormer

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 mdx-bundler and next-mdx-remote also do, but they add more features.

Quick example

On the server:

server.js
import {compile} from '@mdx-js/mdx'

const code = String(await compile('# hi', {
  outputFormat: 'function-body',
  development: false
  // ^-- Generate code for production.
  // `false` if you use `/jsx-runtime` on client, `true` if you use
  // `/jsx-dev-runtime`.
  /* …otherOptions */
}))
// To do: send `code` to the client somehow.

On the client:

client.js
import {run} from '@mdx-js/mdx'
import * as runtime from 'react/jsx-runtime' // Production.
// import * as runtime from 'react/jsx-dev-runtime' // Development.

const code = '' // To do: get `code` from server somehow.

const {default: Content} = await run(code, runtime)

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.

Next.js example

Some frameworks let you write the server and client code in one file, such as Next.

pages/hello.js
import {useState, useEffect, Fragment} from 'react'
import * as runtime from 'react/jsx-runtime' // Production.
// import * as runtime from 'react/jsx-dev-runtime' // Development.
import {compile, run} from '@mdx-js/mdx'

export default function Page({code}) {
  const [mdxModule, setMdxModule] = useState()
  const Content = mdxModule ? mdxModule.default : Fragment

  useEffect(() => {
    ;(async () => {
      setMdxModule(await run(code, runtime))
    })()
  }, [code])

  return <Content />
}

export async function getStaticProps() {
  const code = String(
    await compile('# hi', {
      outputFormat: 'function-body',
      development: false,
      // ^-- Generate code for production.
      // `false` if you use `/jsx-runtime` on client, `true` if you use
      // `/jsx-dev-runtime`.
      /* …otherOptions */
    })
  )
  return {props: {code}}
}