javascriptreactjsnext.jsmarkdownmdxjs

Using Remark and Rehype plugins with MDX in Next.js (with @next/mdx)


I was trying to use Github Flavored Markdown using @next/mdx but I can't seem to figure out how to use plugins with the code. Here is what I did:

(I am following along from Next.js Documentation: https://nextjs.org/docs/advanced-features/using-mdx)


Initializing the Application

1. I created the Next.js App using the command

yarn create next-app next-gfm

2. I then added the required modules to the

yarn add @next/mdx @mdx-js/loader

3. In the pages/ directory, I deleted the index.js file that was auto generated and replaced it using a index.mdx file.

From here, I used the following configuration for my next.config.js file.

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [],
    rehypePlugins: [],
  },
})

module.exports = withMDX({
  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
})

If we run the code using yarn dev everything works fine so far.

Adding GFM

Here is where the main issue is. I now installed the following packages to use Github Flavored Markdown using the command:

yarn add remark-gfm rehype-stringify

Import Syntax Error?

I tried to import the modules in the next.config.js using the syntax

import remarkGfm from 'remark-gfm'

But this gives me the following error:

import remarkGfm from 'remark-gfm'
^^^^^^

SyntaxError: Cannot use import statement outside a module

Making the project a module

I have also tried adding the following line at the top of my package.json

"type" : "module",

But this seems to conflict with the require syntax that is being used to import @next/mdx:

const withMDX = require('@next/mdx')({
...

This gave me the error:

Failed to load next.config.js, see more info here https://nextjs.org/docs/messages/next-config-error

ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and 'package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.

Using older import() syntax

I did a bit of searching online, and found the import() syntax. I attempted to do this and my next.config.js now looked like:

const remarkGfm  = import('remark-gfm');
const remarkParse  = import('remark-parse')
const remarkRehype  = import('remark-rehype')
const rehypeStringify  = import('rehype-stringify')

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [remarkGfm, remarkParse, remarkRehype],
    rehypePlugins: [rehypeStringify],
  },
})

module.exports = withMDX({
  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
})

I tried running this, using the yarn dev and everything works, except that none of the markdown plugins are functioning at all. (The base markdown is working but if I try using footnotes, or tables it is rendered as plain text).

Could anyone explain how one might proceed using external plugins (e.g Github Flavored Markdown) with MDX and Next.js (using the @next/mdx package)?

Reference

This is my complete project structure and (relevant) files:

next-gfm
  |_ pages
       |_ index.md
  |_ package.json
  |_ next.config.js

Files

index.md

# GFM

## Autolink literals

www.example.com, https://example.com, and contact@example.com.

## Footnote

A note[^1]

[^1]: Big note.

## Strikethrough

~one~ or ~~two~~ tildes.

## Table

| a | b  |  c |  d  |
| - | :- | -: | :-: |

## Tasklist

* [ ] to do
* [x] done

package.json

{
  "name": "next-mdx-template",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@mdx-js/loader": "^2.1.1",
    "@next/mdx": "^12.1.5",
    "next": "12.1.5",
    "react": "18.0.0",
    "react-dom": "18.0.0",
    "rehype-katex": "^6.0.2",
    "rehype-stringify": "^9.0.3",
    "remark-gfm": "^3.0.1",
    "remark-math": "^5.1.1",
    "remark-mdx": "^2.1.1"
  },
  "devDependencies": {
    "eslint": "8.13.0",
    "eslint-config-next": "12.1.5"
  }
}

next.config.js

const remarkGfm  = import('remark-gfm');
const remarkParse  = import('remark-parse')
const remarkRehype  = import('remark-rehype')
const rehypeStringify  = import('rehype-stringify')

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [remarkGfm, remarkParse, remarkRehype],
    rehypePlugins: [rehypeStringify],
  },
})

module.exports = withMDX({
  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
})


Solution

  • ES Modules are supported if you rename the config file to next.config.mjs.

    Sources

    In your case it might look like,

    next.config.mjs

    import nextMDX from '@next/mdx'
    import remarkGfm from 'remark-gfm'
    import remarkParse from 'remark-parse'
    import remarkRehype from 'remark-rehype'
    import rehypeStringify from 'rehype-stringify'
    
    const withMDX = nextMDX({
      extension: /\.mdx?$/,
      options: {
        remarkPlugins: [remarkGfm, remarkParse, remarkRehype],
        rehypePlugins: [rehypeStringify],
      },
    })
    
    export default withMDX({
      pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
    })