Using MDX with Next.js

MDX is a superset of markdown that lets you write JSX directly in your markdown files. It is a powerful way to add dynamic interactivity, and embed components within your content, helping you to bring your pages to life.

Next.js supports MDX through a number of different means, this page will outline some of the ways you can begin integrating MDX into your Next.js project.

Why use MDX?

Authoring in markdown is an intuitive way to write content, its terse syntax, once adopted, can enable you to write content that is both readable and maintainable. Because you can use HTML elements in your markdown, you can also get creative when styling your markdown pages.

However, because markdown is essentially static content, you can’t create dynamic content based on user interactivity. Where MDX shines is in its ability to let you create and use your React components directly in the markup. This opens up a wide range of possibilities when composing your sites pages with interactivity in mind.

MDX Plugins

Internally MDX uses remark and rehype. Remark is a markdown processor powered by a plugins ecosystem. This plugin ecosystem lets you parse code, transform HTML elements, change syntax, extract frontmatter, and more.

Rehype is an HTML processor, also powered by a plugin ecosystem. Similar to remark, these plugins let you manipulate, sanitize, compile and configure all types of data, elements and content.

To use a plugin from either remark or rehype, you will need to add it to the MDX packages config.

@next/mdx

The @next/mdx package is configured in the next.config.js file at your projects root. It sources data from local files, allowing you to create pages with a .mdx extension, directly in your /pages directory.

Setup @next/mdx in Next.js

The following steps outline how to setup @next/mdx in your Next.js project:

  1. Install the required packages:

    1. npm install @next/mdx @mdx-js/loader @mdx-js/react
  2. Require the package and configure to support top level .mdx pages. The following adds the options object key allowing you to pass in any plugins:

    1. // next.config.js
    2. const withMDX = require('@next/mdx')({
    3. extension: /\.mdx?$/,
    4. options: {
    5. remarkPlugins: [],
    6. rehypePlugins: [],
    7. // If you use `MDXProvider`, uncomment the following line.
    8. // providerImportSource: "@mdx-js/react",
    9. },
    10. })
    11. module.exports = withMDX({
    12. // Append the default value with md extensions
    13. pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
    14. })
  3. Create a new MDX page within the /pages directory:

    1. - /pages
    2. - my-mdx-page.mdx
    3. - package.json

Using Components, Layouts and Custom Elements

You can now import a React component directly inside your MDX page:

  1. import { MyComponent } from 'my-components'
  2. # My MDX page
  3. This is a list in markdown:
  4. - One
  5. - Two
  6. - Three
  7. Checkout my React component:
  8. <MyComponent/>

Frontmatter

Frontmatter is a YAML like key/value pairing that can be used to store data about a page. @next/mdx does not support frontmatter by default, though there are many solutions for adding frontmatter to your MDX content, such as gray-matter.

To access page metadata with @next/mdx, you can export a meta object from within the .mdx file:

  1. export const meta = {
  2. author: 'Rich Haines'
  3. }
  4. # My MDX page

Layouts

To add a layout to your MDX page, create a new component and import it into the MDX page. Then you can wrap the MDX page with your layout component:

  1. import { MyComponent, MyLayoutComponent } from 'my-components'
  2. export const meta = {
  3. author: 'Rich Haines'
  4. }
  5. # My MDX Page with a Layout
  6. This is a list in markdown:
  7. - One
  8. - Two
  9. - Three
  10. Checkout my React component:
  11. <MyComponent/>
  12. export default ({ children }) => <MyLayoutComponent meta={meta}>{children}</MyLayoutComponent>

Custom Elements

One of the pleasant aspects of using markdown, is that it maps to native HTML elements, making writing fast, and intuitive:

  1. # H1 heading
  2. ## H2 heading
  3. This is a list in markdown:
  4. - One
  5. - Two
  6. - Three

The above generates the following HTML:

  1. <h1>H1 heading</h1>
  2. <h2>H2 heading</h2>
  3. <p>This is a list in markdown:</p>
  4. <ul>
  5. <li>One</li>
  6. <li>Two</li>
  7. <li>Three</li>
  8. </ul>

When you want to style your own elements to give a custom feel to your website or application, you can pass in shortcodes. These are your own custom components that map to HTML elements. To do this you use the MDXProvider and pass a components object as a prop. Each object key in the components object maps to a HTML element name.

To enable you need to specify providerImportSource: "@mdx-js/react" in next.config.js.

  1. // next.config.js
  2. const withMDX = require('@next/mdx')({
  3. // ...
  4. options: {
  5. providerImportSource: '@mdx-js/react',
  6. },
  7. })

Then setup the provider in your page

  1. // pages/index.js
  2. import { MDXProvider } from '@mdx-js/react'
  3. import Image from 'next/image'
  4. import { Heading, InlineCode, Pre, Table, Text } from 'my-components'
  5. const ResponsiveImage = (props) => (
  6. <Image alt={props.alt} layout="responsive" {...props} />
  7. )
  8. const components = {
  9. img: ResponsiveImage,
  10. h1: Heading.H1,
  11. h2: Heading.H2,
  12. p: Text,
  13. pre: Pre,
  14. code: InlineCode,
  15. }
  16. export default function Post(props) {
  17. return (
  18. <MDXProvider components={components}>
  19. <main {...props} />
  20. </MDXProvider>
  21. )
  22. }

If you use it across the site you may want to add the provider to _app.js so all MDX pages pick up the custom element config.