Managing Markdown content doesn't have to be hard!
Tags: nextjs, react, javascript, node
Published: 2022-06-28 19:31:44

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](https://github.com/ekqt/mdx-project-starter) and here's the [live demo](https://mdx-project-starter.vercel.app/).

Don't forget to visit the demo's [Blog page](https://mdx-project-starter.vercel.app/blog) and view the sample entries. They are full-written articles also featured on my [website](https://hectorsosa.me).

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

-   Next.js setup for TypeScript and TailwindCSS
-   Layout (for navs and footers) and Meta (for dynamic SEO) components
-   Project directory for storing local blog files
-   Utility functions to parse your blog content

## How to use

1. From your terminal clone the repo,
2. Navigate to the project's directory and install all its npm dependencies

```bash
$ 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`:

```javascript
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";
```

-   We are using `fs` from Node.js which enables us to interact with the file system. The [fs.readFileSync](https://nodejs.org/api/fs.html#fsreadfilesyncpath-options) method returns the files content.
-   Similarly, `path` allows us to work with files and directory paths, the [path.joins()](https://nodejs.org/api/path.html#pathjoinpaths) method joins all the given segments.
-   `serialize` runs on the **server-side** or/and at build time to generate an object that can be passed directly into our frontend `<MDXRemote />` component.
-   Then we are left with the `rehypePlugins` which _ARE NOT REQUIRED_ but as an added bonus helps us do the following:
    -   `rehypeSlug` generates ids to all headings that do not yet have one.
    -   `rehypeAutolinkHeadings` looks through all headings that have ids and injects a link to them.
    -   `rehypePrism` provides classes to your code for syntax highlighting and line numbers functionalities (does require additional [CSS](https://github.com/timlrx/rehype-prism-plus#styling)).

### 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:

```javascript
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.

```javascript
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:

-   Having the utility function really cleans up your code. You can choose to use Markdown files to power content blocks from any given page OR generate an entire page (such as a blog post) with it too.
-   **TailwindCSS** comes into play here, by installing the pluging `@tailwindcss/typography` you have access to the `prose` class name, which formats the entire content more information on it, [here](https://tailwindcss.com/docs/typography-plugin).

### Generating your project's paths

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

```javascript
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:

-   Using the additional libraries to enable the plugins aren't requireq but a great addition for your content
-   Syntax highlighting is subject to configuring CSS classess to a desired theme
-   If your MDX files have custom component you would need to map those components when invoking `<MDXRemote />` from the client-side
-   With this setup you cannot use `.mdx` as standalone pages

Thanks for reading!