Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
MDX Remark plugin to handle frontmatter
// helps us in parsing the frontmatter from text content
const matter = require('gray-matter')
// helps us safely stringigy the frontmatter as a json object
const stringifyObject = require('stringify-object')
// helps us in getting the reading time for a given text
const readingTime = require('reading-time')
// please make sure you have installed these dependencies
// before proceeding further, or remove the require statements
// that you don't use
* This is a plugin for remark in mdx.
* This should be a function that may take some options and
* should return a function with the following signature
* @param tree - the MDXAST
* @param file - the file node
* @return void - it should mutate the tree if needed
module.exports = () => (tree, file) => {
// we will get the frontMatter using `gray-matter`
const { data: frontMatter, content } = matter(file.contents)
// the frontMatter holds the json object of the frontmatter
// the content holds the text of markdown except frontmatter
// we can do whatever we want with the frontmatter
// like, adding the time to read, formatting the date to display,
// adding a short description using the content
const { text } = readingTime(content)
frontMatter.timeToRead = text
// finally we will add a `export` node to the tree
type: 'export',
value: `export const frontMatter = ${stringifyObject(frontMatter)}`,
// now `frontMatter` will be available to use in our codebase
// we essentically changed the frontmatter of yml form to a
// constant and exported it
// now we need to remove the frontmatter from the tree
// because it has already been processed by mdx and nodes
// have beed created for it assuming it was a markdown content
// remove the thematicBreak "<hr />" to first heading
// --- => thematicBreak
// title: this
// date: 2020-12-12 => becomes heading
// ---
if (tree.children[0].type === 'thematicBreak') {
const firstHeadingIndex = tree.children.findIndex(t => t.type === 'heading')
if (firstHeadingIndex !== -1) {
// we will mutate the tree.children by removing these nodes
tree.children.splice(0, firstHeadingIndex + 1)
title: Hello World
date: 2020-12-12
Some content
<!-- is essentially same as writting -->
export const frontMatter = {
title: "Hello World"
date: "2020-12-12",
timeToRead: "3 min read" <!-- this is automatically added so that's an advantage -->
Some content
// we will import our custom remark plugin
const frontmatterRemarkPlugin = require('./frontmatter')
// here I'm using next.config.js example to use our custom plugin, which
// internally passes these options to `@mdx-js/loader`.
// so you can our custom plugin wherever we can use `@mdx-js`
// add it to the remarkPlugins option to the @next/mdx plugin
const mdxPlugin = require('@next/mdx')({
// these options directly gets passed to `@mdx-js/loader`
options: {
remarkPlugins: [frontmatterRemarkPlugin],
// export the configuration
module.exports = mdxPlugin({
pageExtensions: ['ts', 'tsx', 'md', 'mdx'],
Copy link

swashata commented Jun 28, 2021

Thank you very much for this.

Copy link

lingdocs commented Feb 14, 2022

Great! Just using Next.js 12 I had to change line 22 of frontmatter.js to:

  const { data: frontMatter, content } = matter(file.value)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment