Blog posting with MDX & Gatsby

March 08, 2024
article featured image
There are huge advantages to using MDX as opposed to regular markdown, including the ability to use and create components to increase styling and improve just overall experiences with your blog.

Table of Contents

    §  Using gatsby-plugin-mdx

    First begin by installing the plugin.

    I don't think I had seen this working in a Gatsby 5 project with TypeScript, so I have provided my configuration below. My configuration includes use of PrismJS, LocalSearch, and Remark Classes.

    // gatsby-config.ts
       {
          resolve: `gatsby-plugin-mdx`,
          options: {
            extensions: [`.md`, `.mdx`],
            gatsbyRemarkPlugins: [
              {
                resolve: `gatsby-remark-images`,
                options: {
                  // It's important to specify the maxWidth (in pixels) of
                  // the content container as this plugin uses this as the
                  // base for generating different widths of each image.
                  maxWidth: 590,
                },
              },
              {
                resolve: `gatsby-remark-prismjs`,
                options: {
                  // Class prefix for <pre> tags containing syntax highlighting;
                  // defaults to 'language-' (e.g. <pre class="language-js">).
                  // If your site loads Prism into the browser at runtime,
                  // (e.g. for use with libraries like react-live),
                  // you may use this to prevent Prism from re-processing syntax.
                  // This is an uncommon use-case though;
                  // If you're unsure, it's best to use the default value.
                  classPrefix: 'language-',
                  // This is used to allow setting a language for inline code
                  // (i.e. single backticks) by creating a separator.
                  // This separator is a string and will do no white-space
                  // stripping.
                  // A suggested value for English speakers is the non-ascii
                  // character '›'.
                  inlineCodeMarker: null,
                  // This lets you set up language aliases.  For example,
                  // setting this to '{ sh: "bash" }' will let you use
                  // the language "sh" which will highlight using the
                  // bash highlighter.
                  aliases: {},
                  // This toggles the display of line numbers globally alongside the code.
                  // To use it, add the following line in gatsby-browser.js
                  // right after importing the prism color scheme:
                  //  require("prismjs/plugins/line-numbers/prism-line-numbers.css")
                  // Defaults to false.
                  // If you wish to only show line numbers on certain code blocks,
                  // leave false and use the {numberLines: true} syntax below
                  showLineNumbers: false,
                  // If setting this to true, the parser won't handle and highlight inline
                  // code used in markdown i.e. single backtick code like `this`.
                  noInlineHighlight: false,
                  // This adds a new language definition to Prism or extend an already
                  // existing language definition. More details on this option can be
                  // found under the header "Add new language definition or extend an
                  // existing language" below.
                  languageExtensions: [
                    {
                      language: 'superscript',
                      extend: 'javascript',
                      definition: {
                        superscript_types: /(SuperType)/,
                      },
                      insertBefore: {
                        function: {
                          superscript_keywords: /(superif|superelse)/,
                        },
                      },
                    },
                  ],
                  // Customize the prompt used in shell output
                  // Values below are default
                  prompt: {
                    user: 'root',
                    host: 'localhost',
                    global: false,
                  },
                  // By default the HTML entities <>&'" are escaped.
                  // Add additional HTML escapes by providing a mapping
                  // of HTML entities and their escape value IE: { '}': '&#123;' }
                  escapeEntities: {},
                },
              },
              {
                resolve: 'gatsby-plugin-local-search',
                options: {
                  // A unique name for the search index. This should be descriptive of
                  // what the index contains. This is required.
                  name: 'pages',
    
                  // Set the search engine to create the index. This is required.
                  // The following engines are supported: flexsearch, lunr
                  engine: 'flexsearch',
    
                  // Provide options to the engine. This is optional and only recommended
                  // for advanced users.
                  //
                  // Note: Only the flexsearch engine supports options.
                  engineOptions: 'speed',
    
                  // GraphQL query used to fetch all data for the search index. This is
                  // required.
                  query: `
                    {
                      allMdx {
                        nodes {
                          id
                          frontmatter {
                            slug
                            title
                          }
                          body
                        }
                      }
                    }
                  `,
    
                  // Field used as the reference value for each document.
                  // Default: 'id'.
                  ref: 'id',
    
                  // List of keys to index. The values of the keys are taken from the
                  // normalizer function below.
                  // Default: all fields
                  index: ['title', 'body'],
    
                  // List of keys to store and make available in your UI. The values of
                  // the keys are taken from the normalizer function below.
                  // Default: all fields
                  store: ['id', 'slug', 'title'],
    
                  // Function used to map the result from the GraphQL query. This should
                  // return an array of items to index in the form of flat objects
                  // containing properties to index. The objects must contain the `ref`
                  // field above (default: 'id'). This is required.
                  normalizer: ({ data }) =>
                    data.allMdx.nodes.map((node: any) => ({
                      id: node.id,
                      path: node.frontmatter.slug,
                      title: node.frontmatter.title,
                      slug: node.frontmatter.slug,
                      body: node.body,
                    })),
                },
              },
              {
                resolve: `gatsby-remark-classes`,
                options: {
                  classMap: {
                    'heading[depth=1]': 'remark__h1',
                    'heading[depth=2]': 'remark__h2',
                    'heading[depth=3]': 'remark__h3',
                    'heading[depth=4]': 'remark__h4',
                    'heading[depth=5]': 'remark__h5',
                    paragraph: 'remark__para',
                    'list[ordered=true]': 'remark__orderedList',
                    'list[ordered=false]': 'remark__unorderedList',
                  },
                },
              },
            ],
          },
        },

    §  Using MDXProvider

    Install mdx-js/react.

    Now you will need to go into your gatsby-node.ts file and change your queries to call AllMdx.

        query Alpine {
          publishedPosts: allMdx(
            sort: { frontmatter: { date: ASC } }
            filter: { frontmatter: { status: { in: ["published"] } } }
          ) {
            nodes {
              id
              frontmatter {
                tags
              }
              fields {
                slug
              }
              internal {
                contentFilePath
              }
            }
          }

    In your queries, you will call mdx and its frontmatter very much the same way as before. Some of the items have changed.

    export const pageQuery = graphql`
      query BlogPostBySlug(
        $id: String!
        $previousPostId: String
        $nextPostId: String
      ) {
        site {
          siteMetadata {
            blogListPath
            postPath
            tagsName
            tagsNamePlural
          }
        }
        mdx(id: { eq: $id }) {
          id
          body
          frontmatter {
            title
            date(formatString: "MMMM DD, YYYY")
            description
            tags
            slug
            author
            showTOC
            status
            prismThemes
            featuredImage {
              childImageSharp {
                gatsbyImageData(quality: 100, placeholder: BLURRED, formats: [WEBP])
              }
            }
          }
        }
        previous: mdx(id: { eq: $previousPostId }) {
          fields {
            slug
          }
          frontmatter {
            title
          }
        }
        next: mdx(id: { eq: $nextPostId }) {
          fields {
            slug
          }
          frontmatter {
            title
          }
        }
      }
    `;

    In your templates, you will import the provider.

    import { MDXProvider } from '@mdx-js/react';

    Then insert it as you would with Remark.

      <div className="mt-4">
        <MDXProvider>{children}</MDXProvider>
      </div>