How to embed React components in markdown using Gatsby, MDX, and short-codes

Table of contents

After finishing a project that involved migrating a Sculpin site to GatsbyJS using MDX to allow embeding React components in markdown files, we decided to publish a series of posts to share our experience.

Welcome to the first post of the series. In this post, you will learn how to use MDXRenderer and MDXProvider to embed JSX/React components using short-codes.

Why MDX

MDX is Markdown for the component era. It lets you combine JSX with Markdown’s syntax. Yes, you can embed JSX code or even better embed reusable components within your content.

  • Powerful: MDX blends markdown and JSX syntax to fit perfectly in JSX-based projects.
  • Everything is a component: Import JSX components and render them directly in your MDX document.
  • Customizable: Decide which component is rendered for each markdown element.
  • Markdown-based: The simplicity and elegance of Markdown remain, you interleave JSX only when you want to.
  • Blazingly blazing fast: MDX has no runtime, all compilation occurs during the build stage.

Adding MDX dependency using npm

Run this on your Gatsby project root directory.

npm install --save gatsby-mdx @mdx-js/mdx@next @mdx-js/react@next

Configuring the gatsby-mdx plugin

Using this configuration on the project gatsby-config.js file, we ensure any file with the extension .md and .mdx is transformed by the gatsby-mdx plugin.

plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/content`,
        name: `content`,
      },
    },
    {
      resolve: `gatsby-mdx`,
      options: {
        extensions: [".mdx", ".md"],
        gatsbyRemarkPlugins: [
          {
            resolve: "gatsby-remark-images",
            options: {
              maxWidth: 1035,
              sizeByPixelDensity: true,
              showCaptions: true,
              linkImagesToOriginal: false,
            },
          },
          {
            resolve: `gatsby-remark-prismjs`,
            options: {
              classPrefix: "language-",
              inlineCodeMarker: null,
              aliases: {},
            },
          },
          {
            resolve: "gatsby-remark-external-links",
            options: {
              target: null,
              rel: "nofollow noopener noreferrer external",
            },
          },
          `gatsby-remark-slug`,
        ],
      },
    },
  ],

You can add any other markdown remark plugin using the gatsbyRemarkPlugins array.

Take note to query MDX content, the gatsby-source-filesystem plugin must be included to discover files from the filesystem.

The components

Nothing new here, but for the sake of the example, we are going to assume we have our components at the src/components directory.

Any component coming from a javascript file would be written like any other React component.

  • src/components/alert.js
import React from "react"

const Alert = ({ title, type, children }) => {
  return (
    <div className={`alert alert-${type}`}>
      <h4 className={type}>{title}</h4>
      {children}
    </div>
  )
}

export default Alert

  • src/components/callout.js
import React from "react"
import ExternalLink from "./externalLink"

const Callout = ({ type, children, title, link }) => {
  return (
    <div className="enablement">
      <h4 className={type}>
        <ExternalLink text={title} link={link} />
      </h4>
      {children}
    </div>
  )
}

export default Callout

  • src/components/layout.js
import React from "react"
import Header from "./header"
import Footer from "./footer"

const Layout = ({ children }) => {
  return (
    <>
      <Header />
      {children}
      <Footer />

  )
}

export default Layout

Adding components using short-codes

Within our template file src/templates/doc.js we have this code:

import React from "react"
import { graphql } from "gatsby"
import MDXRenderer from "gatsby-mdx/mdx-renderer"
import { MDXProvider } from "@mdx-js/react"

import Layout from "../components/layout"
import Alert from "../components/alert"
import Callout from "../components/callout"

const shortcodes = {
  Alert,
  Callout,
}

render() {
  const node = this.props.data.mdx;
  return (
    <Layout>
        <MDXProvider components={shortcodes}>
            <MDXRenderer>{node.code.body}</MDXRenderer>
        </MDXProvider>
    </Layout>
  )
}

export default DocTemplate

By providing an array of shortcodes and passing them to the MDXProvider component, we can support globally available components to avoid using import or require within our md/mdx files.

The GraphQL query

export const pageQuery = graphql`
  query DocBySlug($slug: String!) {
    mdx(fields: { slug: { eq: $slug } }) {
      code {
        body
      }
    }
  }
`

As you can see, we are passing the slug value to render query a single page but more important for the purpose of this example instead of using the html rendered version of the markdownRemark we are retrieving code > body from mdxto pass as children to the MDXRenderer component.

The markdown files

This example shows how the content of one md file looks like.

---
title: Lorem ipsum
authors: [foo, bar, baz]
---

<Callout title="Your Title here" link="https://example.org">
  Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</Callout>

<Alert title="Note" type="info">
  Aenean velit risus, auctor eu nunc vitae, gravida condimentum leo.
</Alert>

<Alert title="Note" type="danger">
  Proin et imperdiet sapien, non commodo lectus.
</Alert>

And this image shows the rendered version of the embedded components.

mdx components### Are you interested in knowing more about GatsbyJS?

This series does not end here. We invite you to come back as we continue. We will be sharing some nice tips & tricks that we learned during the development of this project. This is a list of the topics we are planning to share:

  • How to create a text/markdown node to process markdown content.
  • How to query all posts by an author when you have multiple authors per markdown file on your front-matter field.
  • How to load templates based on a front-matter field.
  • How to create page navigation based on a directory structure. Using parent directory as the base and showing all of the children files as clickable link pages.