Exploring the synergies of Remark and Rehype plugins alongside MDX in Next.js powered by @next/mdx

I’ve been experimenting with Github Flavored Markdown using @next/mdx, but I’m struggling to understand how to use plugins alongside the code. Here’s a breakdown of what I’ve attempted so far:

(I’m following the instructions from the Next.js Documentation: https://nextjs.org/docs/advanced-features/using-mdx)


Getting Started

1. Created a Next.js App with the command:

yarn create next-app next-gfm

2. Added the necessary modules by running:

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

3. In the pages/ directory, replaced the auto-generated index.js with an index.mdx file.

For the next.config.js configuration, I used the following setup:

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

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

Running yarn dev at this point seems to work without any issues.

Integrating GFM

However, when trying to incorporate GitHub Flavored Markdown, I installed the necessary packages with:

yarn add remark-gfm rehype-stringify

Import Syntax Error?

While attempting to import the modules in next.config.js using ES6 syntax like:

import remarkGfm from 'remark-gfm'

This resulted in the following error:

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

SyntaxError: Cannot use import statement outside a module

Making it a module

Tried adding the line below at the top of my package.json:

"type" : "module",

But this caused conflicts with the require syntax used for importing @next/mdx:

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

Resulting in 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.

Trying older import() syntax

After looking online, found the import() syntax and modified my next.config.js as shown below:

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'],
})

When running this setup with yarn dev, everything seems to be working fine except that none of the markdown plugins are actually functioning (basic markdown works but advanced features like footnotes, tables render as plain text).

If anyone has insight on effectively incorporating external plugins (such as GitHub Flavored Markdown) with MDX and Next.js using the @next/mdx package, your guidance would be greatly appreciated.

Project Structure and Files

Below is the structure along with relevant files in my project:

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

Files

index.md

# GFM

## Autolink literals

www.example.com, https://example.com, and <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c3a0acadb7a2a0b783a6bba2aeb3afa6eda0acae">[email protected]</a>.

## 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'],
})

Answer №1

To enable ES Modules, simply rename the configuration file to next.config.mjs.

References:

In your scenario, the file should be named as follows:

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'],
})

Answer №2

next-mdx-remote enthusiasts:

To enhance your experience, execute npm i remark-gfm

Begin by creating a new file named mdSerializer.ts, and insert the following code:

import { serialize } from 'next-mdx-remote/serialize';
import remarkGfm from 'remark-gfm';

const mdSerialize = async (source: string) => {
  return await serialize(source, {
    mdxOptions: { remarkPlugins: [remarkGfm] },
  });
};

export { mdSerialize };

Import mdSerialize wherever you would normally import serialize


For more in-depth information about the serialize API, visit here

Explore the Gfm example from the original mdx site, accessible here

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Leveraging react.js through npm along with ASP.NET Web API during development

Currently, I am building a React.js front-end application that communicates with an ASP.NET Web API backend. To prevent cross-site-scripting issues, it is required to host both the front and back end on the same domain. While this setup works well under n ...

What are some effective methods for overriding materialui styles?

Here is the CSS style for a checkbox in Material-UI that I captured from the console: .MuiCheckbox-colorPrimary-262.MuiCheckbox-checked-260 I attempted to override this style using the following code, but it was unsuccessful: MuiCheckBox: { colorP ...

Securing page access with a straightforward password protection using Flask

Looking for a straightforward way to add password protection to a Flask app site. I've explored options like flask_login, but they seem overly complex for my needs. I don't need high security or login sessions - just something like: "enter passw ...

What is the best way to retrieve a value from a function that contains multiple nested functions in Javascript?

The issue at hand is my struggle to extract a value from a nested method and utilize it outside of its parent method. I am aiming for the output of "console.log(someObjects[i].valueChecker);" to display either "true" or "false," but instead, it simply retu ...

Innovative sound system powered by React

I am working on implementing a music player feature on a website where users can select a song and have it play automatically. The challenge I am facing is with the play/pause button functionality. I have created all the necessary components, but there see ...

Is there a way to adjust the state value in Pinia within a Vue3 component test, and have an impact on the component?

When testing the component using pinia with vue-test-utils, I encountered difficulty in modifying the state value stored in pinia. Despite trying multiple methods, I was unable to achieve the desired result. The original component and store files are provi ...

Visualize data from ajax call in tabular format

I want to display the results of an SQL query in a table using AJAX. I have written code that executes the query during the AJAX call, but when I try to display these values in a table, it shows null values on the div tag. What could be the reason for this ...

What could be causing my React function to be declared but not utilized?

Struggling with my React project, I hit a roadblock when trying to import my generalInput file into my App file. The error message stated that the generalInput was declared but never read. Desperate for a solution, I even turned to AI for help, but it too ...

Display the highlighted text within the input field

Is there a library available that can create an input field with a suggestions dropdown for Male and Female where typing "M" will highlight the letters "ale," allowing for autopopulation when clicked or tabbed? The same functionality should apply for typin ...

Currently, I'm harnessing the power of TypeScript and React to identify and capture a click event on a dynamically generated element within my document

Is there a way to detect a click on the <p> tag with the ID of "rightDisplayBtn"? I've tried using an onclick function and event listener, but neither seem to be working as expected. function addDetails() { hideModal(); addBook ...

Listener for body keystrokes

Is there a way to trigger a function when the space bar is pressed on the page, without it being called if an input field is focused? Any thoughts or suggestions? The current code triggers the function even when an input bar is focused: $(document).keydo ...

Different options for reading large files during an AJAX file upload

It seems that php's file_get_contents is not able to handle large files. What can be done as an alternative if it is causing a "stack overflow" issue? HTML File <form id='test'> <input type='file' name='Receipt&ap ...

Tips for organizing AJAX code to show images and videos in HTML with a switch case approach

I have 2 tables, one for images and one for videos. Within my HTML code, I have three buttons: slide_image, video, and single_image. Using iframes, I am able to load images and videos on the same page. When I click on the slide_image button, it displays im ...

Hide specific content while displaying a certain element

Creating three buttons, each of which hides all content divs and displays a specific one when clicked. For instance, clicking the second button will only show the content from the second div. function toggleContent(id) { var elements = document.getEl ...

Tips for transferring information from a parent to a child controller in Angular using the $broadcast method?

I am trying to send an id argument to a child controller using Angular's $broadcast method, but despite my best efforts with the code below, I can't seem to make it work. Any suggestions on what might be incorrect in my implementation? ParentCtr ...

Exploring JavaScript through the Lens of Object-Oriented Concepts from Java

Having spent a significant amount of time using Java, I delved into web development with GWT (Google Web Toolkit) where the convenience of having my Java object-oriented constructs translated to GWT seamlessly by Google was a major draw. While my knowledge ...

The method by which JavaScript identifies when a Promise has resolved or rejected

How does JavaScript determine when the state of myPromise has transitioned to "fulfilled" in the code provided below? In other words, what is the process that determines it's time to add the .then() handler to the microqueue for eventual execution? co ...

Click on the text within a paragraph element

Is there a way to retrieve the selected text or its position within a paragraph (<p>)? I'm displaying a text sentence by sentence using a Vue.js loop of paragraphs. <p class="mreadonly text-left mark-context" v-for="line in ...

Introducing Next.js 13 - Leveraging Redis Singleton within Route Handlers

I have a basic library file that encapsulates some redis features: import Redis from 'ioredis' // Setting TTL to expire after an hour const TTL = 3600 console.log('Performing operations in redis.ts') const client = new Redis(process.e ...

Developing a compressed file in JavaScript

async purchaseMultiple(decoded, purchaseData){ const user = await Database.user.findOne({where: { id_user: decoded.id_user }}); if( ! user) return [404, 'ERROR: User [' + decoded.id_user + '] not found']; if(user.credi ...