Making MDX blog with Nextjs - Part 2
Tags: nextjs, react, mdx
Published: 2020-10-19 00:00:00

---
title: "Making MDX blog with Nextjs - Part 2"
date: "2020-10-19"
author: "Jashn Maloo"
description: "Friendly tutorial of building mdx blog witth Nextjs and some problems discussed. Rendering front-matter, components and content altogether in part 2."
series: "MDX blog with Next.js"
---

In the last post we finished with adding mdx files, getting slug and other details for index page and completed index page for posts.
I recommend you to get going by completing the part-1 first if you haven't done it. It's the base for what we're going to do now.


All we gotta do now is to add that dynamic page we talked in the last post. I know I'm moving directly in the building part without discussing anything right now, but it's better this way.
So let's get going.

> Repo: [mdx-blog-with-nextjs](https://github.com/jashnm/mdx-blog-nextjs)
  

> Live: [Next MDX Blog](https://next-mdx-blog.onrender.com/blog)

## 1. Adding post fetching logic

Before we start making our dynamic page for posts, we've to add some logic regarding how and what are we fetching in that file. So in the end of `/lib/posts.js` file,
we'll be adding two functions, one for fetching slugs to attact to each page and one for all the content for each page we are fetching in the first function.

```js
//Get slugs
// ./lib/posts.js

//...
export const getSortedPosts = () => {
  //...
};

//Get Slugs
export const getAllPostSlugs = () => {
  const fileNames = fs.readdirSync(postDirectory);

  return fileNames.map((filename) => {
    return {
      params: {
        slug: filename.replace(".mdx", "")
      }
    };
  });
};

//Get Post based on Slug
export const getPostdata = async (slug) => {
  const fullPath = path.join(postDirectory, `${slug}.mdx`);
  const postContent = fs.readFileSync(fullPath, "utf8");

  return postContent;
};
```

Here,

- `getAllPostSlugs` is creating and fetching slugs from all the posts
- `getPostData` is used to find content of the post by navigating to the file using slug it gets as parameter and returns `post content`.

These two functions are the master functions because using these two functions only, we'll be getting all our content and pages.

## 2. Making [slug].js page

Now that we've the logic to get slug and post from that slug, let's finally build the `[slug].js` page.

If you're familiar with dynamic routing is react, we use `:id` or something like that for dynamic pages, and render page by matching URL parameters with all the data available. Once data is found,
it dispatches to the page according to the page design.
Nextjs has a better way to handle this(atleast what I feel). As you know nextjs has file based routing, wouldn't it be challenging to make a different page for each posts with similar styles and components?
That's where the `[dynamicPageName].js` types of files come in action. Such file name tells next that the content of this file depends on the URL parameter user is visiting so next handles it that way only.

In the `/blog` directory make a file named `[slug].js` and add the following content to it -

```js
// ./blog/[slug].js

/** @jsx jsx */
import { getAllPostSlugs, getPostdata } from "../../lib/posts";
import { Box, jsx, Text, Divider, Flex } from "theme-ui";
import matter from "gray-matter";

export default function Posts({ source, frontMatter }) {
  return (
    <Box sx={{ variant: "containers.page" }}>
      <Box sx={{ mt: "4rem" }}>
        <h1>{frontMatter.title}</h1>
        <Divider color="muted" />
        <Box>{source}</Box>
      </Box>
    </Box>
  );
}
export async function getStaticPaths() {
  const paths = getAllPostSlugs();
  return {
    paths,
    fallback: false
  };
}
export async function getStaticProps({ params }) {
  const postContent = await getPostdata(params.slug);
  const { data, content } = matter(postContent);

  return {
    props: {
      source: content,
      frontMatter: data
    }
  };
}
```

Isn't this mostly like the index page we built earlier? So what makes it dynamic apart from the filename?
This time we've another function provided by nextjs, `getStaticPaths` and the role this plays is simple but quite important.

As we know that all the posts will be created at build time, so that means our dynamic page will be statically generated, interesting right?
So `getStaticPaths` returns an array of all the URL paramaters possible for our dynamic page based on the data/posts we've created.
Here, it fetches all the slugs from the `getAllPostSlugs` function we added in `./lib/posts.js` file and returns an array of it. Now all the URL parameters in thsi array are pre-rendered by nextjs.
That means Next.js will generate all the posts route in the build time only.
And fallback here is false to give 404 error for paths not returned by `getStaticPaths`. You can read more about it in [official documentation](https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required).

For all the paths pre-rendered, URL parameter is passed into `getStaticProps`, which fetches `post content` belonging to that param, thus pre-rendering all the paths and pages with their content statically.
Here, We are collecting front-matter details in `data` variable and post content in `content` variable with `gray-matter`.
And as usual, all this data is passed onto the page component above.

![Simple MDX post rendering](https://dev-to-uploads.s3.amazonaws.com/i/et3rdag84bjnqc4lo3f9.png)

Messy, right?

## 3. Adding Components to MDX

One of the main aspect differring mdx with md is using components within itself. So let's create two simple custom components.
Make a `components` folder in the root directory and add the following two components-

```js
// ./components/MyButton.js

/** @jsx jsx */
import { Button, jsx } from "theme-ui";

const MyButton = ({ text, check }) => {
  return (
    <Button sx={{ width: "100%", bg: check ? "steelblue" : "coral", my: 2 }}>
      {text}
    </Button>
  );
};

export default MyButton;
```

```js
// ./components/MyBackground.js

/** @jsx jsx */
import { jsx } from "theme-ui";

const MyBackground = ({ children }) => {
  return <div sx={{ bg: "cornsilk", py: 1 }}>{children}</div>;
};

export default MyBackground;
```

Let's add these components to our posts.

```js
// getting-started-with-mdx.mdx

---
//...front-matter
---
import MyButton from "../components/MyButton.js"
import MyBackground from "../components/MyBackground.js";

//...rest of the content

<MyBackground>

 [MDX](https://mdxjs.com) is markdown for component era.

</MyBackground>

<MyButton text="Click"/>

```

```js
// some-random-points.mdx

---
//...
---
import MyButton from "../components/MyButton.js"


//...

<MyButton check text="Click"/>

```

And this is how the post will look now

![Post with component](https://dev-to-uploads.s3.amazonaws.com/i/piw0n1j1k6uu6xeca9ef.png)

Unable to understand what's written in the post? Yeah, ditto!
If it would've been a markdown file, we could've used remark, remark-html or react-markdown to convert markdown to html.
But it's an mdx file and we're using components in it, how can we show our file the way it is meant to be shown?

And that's where a problem arises. Natively we can render mdx files with components easily, but first, we're rendering them through a dynamic route to save ourselves from repitition and drastic memory usage. Secondly,
we've front-matter in it, and MDX does not support rendering of front-matter natively.
So what's the solution now, we want our mdx files to show content, components and front-matter.
This is where I got lost for few days, but you don't have to.

There are two workarounds for this -

1. [`next-mdx-enhanced`](https://github.com/hashicorp/next-mdx-enhanced): It overcomes with some of the problems of `@next/mdx` and renders MDX files with a common layout, provides a way to get components and front-matter render in the post and few extra features that we probably don't need. But it does require little bit of extra config for a super smooth rendering experience.
2. [`next-mdx-remote`](https://github.com/hashicorp/next-mdx-remote): By the same developer, but ~50% faster, more flexible and easier to use. It refines some of the issues of `next-mdx-enhanced`. But this is what we'll be using.

Although `next-mdx-remote` is awesome, it does have one caveat which we'll understand once we start using it.

## 4. Using next-mdx-remote

Install `next-mdx-remote`

```
npm i next-mdx-remote
```

And now it's time to modify our champ `[slug].js`. We'll be adding and modifying a good amount of code, so let's just rebuild it

```js
// ./blog/[slug].js

/** @jsx jsx */
import Head from "next/head";
import { getAllPostSlugs, getPostdata } from "../../lib/posts";
import { Box, jsx, Text } from "theme-ui";
import renderToString from "next-mdx-remote/render-to-string";
import hydrate from "next-mdx-remote/hydrate";
import matter from "gray-matter";
import MyBackground from "../../components/MyBackground";
import MyButton from "../../components/MyButton";

const components = { MyBackground, MyButton };

export default function Posts({ source, frontMatter }) {
  const content = hydrate(source, { components });
  return (
    <>
      <Head>
        <title>{frontMatter.title}</title>
      </Head>
      <Box sx={{ variant: "containers.page" }}>
        <Box sx={{ mt: "4rem", textAlign: "center" }}>
          <h1>{frontMatter.title}</h1>
          <Text
            sx={{
              width: ["80%", "50%"],

              mx: "auto"
            }}
          >
            {frontMatter.author}
            {" / "}
            <span>{frontMatter.date}</span>
          </Text>
        </Box>
        <Box sx={{ mt: "4rem" }}>
          <Box>{content}</Box>
        </Box>
      </Box>
    </>
  );
}
export async function getStaticPaths() {
  const paths = getAllPostSlugs();
  return {
    paths,
    fallback: false
  };
}
export async function getStaticProps({ params }) {
  const postContent = await getPostdata(params.slug);
  const { data, content } = matter(postContent);
  const mdxSource = await renderToString(content, {
    components,
    scope: data
  });
  return {
    props: {
      source: mdxSource,
      frontMatter: data
    }
  };
}
```

We added `next-mdx-remote` and two functions from it, `renderToString` and `hydrate`.

- `renderrToString` runs at build time, so it's included in `getStaticProps`. It returns an object of MDX content with components it utilizes.
- The object returned by `renderToString` now gets passed into `hydrate` along with the location of components we're using inside our MDX.This `hydrate` function initially renders static content and hydrate it when browser's not busy with other tasks.

If you now visit your `http://localhost:3000/blog/getting-started-with-mdx` route, you'll get an error  

![require is not definer error](https://dev-to-uploads.s3.amazonaws.com/i/imemgj077h2bmt6ba74y.png)

It is pointing that error is in our `[slug].js` file in line 52. And that's because it is the line that preapres MDX file for rendering and for determining components in it. So that means we've a problem in our MDX files? Hell Yeah.
And this is where we discuess the limitations of `next-mdx-remote`.

`next-mdx-remote` does not allow adding `import` inside MDX files, therefore to use components, we've to pass them in second argument in `hydrate` and `renderToString` functions and that's what we did in the code above.
So if we remove the `import` lines from our MDX files, and visit our post, we'll have this -  

![MDX rendered with front-matter and components](https://dev-to-uploads.s3.amazonaws.com/i/ijk7rf3ev3cn6p28osd5.png)

Pretty amazing, right?

- Front-matter ✔️
- Formatted content ✔️
- Components rendering ✔️

So we've completed our blog? Kind of, but there's one problem left.
Remember how we cannot add `import` in MDX file while working with `next-mdx-remote` and that we've to import components while we're rendering it. According to the [official docs](https://github.com/hashicorp/next-mdx-remote) of `next-mdx-remote`,
while adding components to `hydrate` function, components _should be the exact same components that were passed to `renderToString`_.
And in that case, if we've to make different pages for each post to render, what's the point of doing all this hard work? I totally get you, and so I've a workaround here, it works decently with the things we've setup in 2 lengthy posts.

Currently, we're passing the components of `getting-started-with-mdx` post in the `hydrate` function by importing them in `[slug].js`, now suppose you've few more components being used by several posts. So what simple step we're gonna take is,
create `AllComponents.js` file in `components` folder and add all the components in there. Once exported, `AllComponents` will pass required components to the posts which utilize them.

```js
// ./components/AllComponents.js

import MyBackground from "./MyBackground";
import MyButton from "./MyButton";
//import as many components you're using collectively in all your posts

const AllComponents = {
  MyButton,
  MyBackground
  // Any other component you want
};

export default AllComponents;
```

And now, replace the components you added in `[slug].js` with `AllComponents`

```js
// ./blog/[slug].js

//... Other import statements

//Replace MyButton, Mybackground import with AllComponents
import AllComponents from "../../components/AllComponents";

//Replace {MyButton, MyBackground} with AllComponents
const components = AllComponents;

//Rest of the file remains same
export default function Posts({ source, frontMatter }) {
  //...
}
```

<br />

**Voila!** our blog is ready.
You're good to go. Use n number of components in your MDX, all you gotta do is to add that component in your `AllComponents` file and _wuhoo!_, you can render n number of posts without any issue.

<br />

---

####  Optional

<br />
<br />

Apart from the whole process we just completed, if you want to provide custom styles/components to native markdown components like H1, H2, lists, link, Image, etc. You can use `MDXProvider`.

## Working with MDXProvider

```
npm i @mdx-js/react
```

Because I'm using `theme-ui`, I'll be using it to provide custom styling to my markdown components.
In your components folder, add `MDXCompProvider.js` and add the following

```js
// ./components/MDXProvider.js

/** @jsx jsx */
import { MDXProvider } from "@mdx-js/react";
import { Heading, Text, jsx, Box, Link, Flex } from "theme-ui";

export default function MDXCompProvider(props) {
  const state = {
    h1: (props) => <Heading as="h1" sx={{ mt: "3", mb: "2" }} {...props} />,
    h2: (props) => <Heading as="h2" sx={{ mt: "3", mb: "2" }} {...props} />,
    h3: (props) => <Heading as="h3" sx={{ mt: "3", mb: "2" }} {...props} />,
    h4: (props) => <Heading as="h4" sx={{ mt: "3", mb: "2" }} {...props} />,
    p: (props) => <Text as="p" sx={{ mb: "2", lineHeight: "2" }} {...props} />,
    a: (props) => (
      <Link as="a" sx={{ color: "secondary", fontWeight: "bold" }} {...props} />
    )
  };

  return (
    <MDXProvider components={state}>
      <Box {...props} />
    </MDXProvider>
  );
}
```

Here we are providing our components to be used instead of native markdown h1, h2, p, etc. You can do a lot of customizations here according to your need.

### Wrapping blog with MDXProvider

Last step, We just need to wrap our Next.js blog with MDXProvider so that it can be applied automatically to our MDX files.
Open `_app.js` and wrap `<Component {...pageProps} />` with the `MDXCompProvider` we just created.


```js
// ./pages/_app.js

import "../styles/globals.css";
import { ThemeProvider } from "theme-ui";
import theme from "../theme";
import MDXProvider from "../components/MDXProvider";

function MyApp({ Component, pageProps }) {
return (
  <ThemeProvider theme={theme}>
      <MDXProvider>
        <Component {...pageProps} />
      </MDXProvider>
    </ThemeProvider>
  );
}
export default MyApp;
```


So we're finally done with creating our MDX blog with Next.js.

It's a lengthy process if you're new to it. Once you know the stuff, it'll be smooth af!

<br/>

---

It's my first tutorial/technical blog, hope you like it.

Peace ✌