Using MDX the easy way

Power your Next.js content with MDX and TailwindCSS with minimal plugins and configuration

Managing Markdown content doesn't have to be hard!

I've created this project starter that uses MDX to power your Next.js content only using next-mdx-remote and @tailwindcss/typography packages.

NOTHING else is required, but nice to have are these plugins for your parser: rehype-autolink-headings, rehype-prism-plus and rehype-slug to make your life easier (more on these later on).

Here's the link to the GitHub repo and here's the live demo.

Don't forget to visit the demo's Blog page and view the sample entries. They are full-written articles also featured on my website.

First let's review what I'm including in this project starter:

How to use

  1. From your terminal clone the repo,
  2. Navigate to the project's directory and install all its npm dependencies
$ git clone https://github.com/ekqt/mdx-project-starter.git <FOLDER_DIRECTORY>
$ cd <FOLDER_DIRECTORY>
$ npm install

That's all you need. You can change the <Meta /> and remove the GitHubCorner component to your own and write your own content in the /content and /blog directories, everything else is already set.

How does it work?

The magic happens within the /util folder. getMdx and getPaths. They both illustrate how we can use next-mdx-remote to process our MDX files in a Node.js context to statically serve our content. Keep in mind this is the ONLY required package for this. All the others after it are just to jazz up our content.

Let's break down each of these util functions, starting with getMdx:

import fs from "fs";
import path from "path";
import { serialize } from "next-mdx-remote/serialize";
import rehypeSlug from "rehype-slug";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypePrism from "rehype-prism-plus";

How do we use all of these packages?

The function takes two parameters: (a) the directory's path you are targeting and (b) the slug or filename you want to read. Then it goes ahead and gets the file's content and parses it with the options provided. Here's how the function looks:

export default async function getMdx(dirPath: string, slug: string) {
    const source = fs.readFileSync(path.join(dirPath, slug + ".mdx"), "utf8");

    return await serialize(source, {
        parseFrontmatter: true,
        mdxOptions: {
            rehypePlugins: [
                rehypeSlug,
                rehypePrism,
                [
                    rehypeAutolinkHeadings,
                    {
                        properties: {
                            className: ["anchor"],
                        },
                    },
                ],
            ],
            format: "mdx",
        },
    });
}

How do we render it on the client-side?

It's all downhill from here. You just need to call the util getMdx from the server side and pass it as a prop.

import { MDXRemote } from "next-mdx-remote";

export async function getStaticProps() {
    const mdxSource = await getMdx("content", "index");
    return { props: { source: mdxSource } };
}


export default Home ({ source }){
    return(
        <article className='prose'>
            <MDXRemote {...source} />
         </article>
    )
}

Notice a couple of things:

Generating your project's paths

Now changing gears let's review how to generate your project's paths using the getPaths function:

import fs from "fs";
import path from "path";

export default function getPaths(dirPath: string) {
    const files = fs.readdirSync(path.join(dirPath));
    return files.map((file) => ({
        params: {
            slug: file.replace(".mdx", ""),
        },
    }));
}

Once again, we use the same Node.js functions and provide the paths for our getStaticPaths() data fetching function.

Conclusion

Powering your project's content with MDX cannot be any easier. There are multiple solutions and libraries out there for this, however. I've found that this is the most flexible solution I've been able to come up with. Clone the GitHub repo and experiment with it to create your own solutions. A couple of final thoughts:

Thanks for reading!