Create a blog using Next.js and Markdown/MDX
Tags: webdev, javascript, nextjs, lowcode
Published: 2021-12-30 21:42:13


In this article, we are going to learn how to build a blog and or add a blog section to an already existing [Next.js](https://nextjs.org/) project. To create the content of the blog we are going to be using an extension of the Markdown syntax: [MDX](https://mdxjs.com/).

When using Markdown in a website, there is always a compile step in which all the syntax markdown is converted into the HTML that the browser can understand. The problem with using plain Markdown is that you are limited to the handful amount of HTML elements that markdown is aware of. With MDX, you can extend those elements with your custom [React](https://reactjs.org/) components. It looks something like this:

```md
import {Chart} from './snowfall.js'
export const year = 2018

# Last year's snowfall In {year}, the snowfall was above average.

It was followed by a warm spring which caused flood conditions in many of the nearby rivers.
<Chart year={year} color="#fcb32c" />
```

In this post, we're going to show you two ways of integrating MDX into your project to create blog pages. The traditional way is the one with which we are going to get started, and a [bonus to show you how to get the job done in no time](#bonus).

## Setting up our app

First, you'll need to have Node.js installed on your computer. For this project we used Node.js 16 but anything up to 10.3 is going to be okay.
If you have already created your app and just want to add the blog section to it, skip this section, otherwise, create your app with:

```
npx create-next-app app-name
```

This should generate a new Next.js app that follows the following folder structure

```
src
├── components
├── pages
|  ├── index.js
|  └── _app.js
├── next.config.js
├── package.json
├── README.md
└── yarn.lock
```

## Create the necessary files

Now that we have an app to work with, let's get started with the routes and components that are going to make up our blog:

- `pages/blog` - where our blog posts are stored
- `pages/blog/index.js` - the page that lists all the blog posts that we have written and later redirects our readers to the corresponding pages
- `pages/blog/post-1/index.mdx` - a simple example post
- `next.config.js` - to work with mdx we have to tell nextjs how to work with the mdx pages, that's done here.
- `utils/getAllPosts.js` - to retrieve all the blog posts data from the `pages/blog` folder
- `components/BlogPost.js` - the blog post itself
- `components/PostCard.js` - a simple card to display post meta data used to index all posts 

You can start opening and creating these files, we'll come back later. First, we need to install the required dependencies.

## Install the necessary dependencies

To enable MDX in our app, we need to install the `@mdx-js/loader` library. To do so navigate to the root folder and in a console run the following command:

```
npm install @mdx-js/loader
```

That's generic to mdx, now we have to add a dependency exclusive to the nextjs framework. Just like before run the following command

```
npm install @next/mdx
```

These are fundamental to the functionality that we want to provide. For our case we also want to add some styling with [Tailwindcss](https://tailwindcss.com/) so install:

```
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

Add the paths to all of your template files in your `tailwind.config.js` file.

```js
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};
```

Add the `@tailwind` directives for each of Tailwind's layers to your `./styles/globals.css` file.

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

We'll also we using the [Tailwind CSS Typography plugin](https://tailwindcss.com/docs/typography-plugin) so let's install it:

```
npm install @tailwindcss/typography --save-dev
```

And add it to your `tailwdind.config.js`:

```js
//…
plugins: [
  require('@tailwindcss/typography'),
],
//…
```

## Add mdx support

By default, Next.js will just pick `.js` and `.jsx` files as routes from our pages directory, that's why we need to edit the `next.config.js`, to make all our content visible to Next.js.

```js
const withMDX = require("@next/mdx")({
  extension: /\.mdx?$/,
});

module.exports = withMDX({
  pageExtensions: ["js", "jsx", "md", "mdx"],
});
```

Now Next.js can not only render the `js` and `jsx` files inside our pages directory but also `.md` and `.mdx`.

## Fetch the blog posts

To render our `pages/blog/index.js` we'll need an array with all the pages that we've written and the links to them. To create it, in our `utils/getAllPosts.js` file add the following:

```js
function importAll(r) {
  return r.keys().map((fileName) => ({
    link: fileName.substr(1).replace(/\/index\.mdx$/, ""),
    module: r(fileName),
  }));
}

export const posts = importAll(
  require.context("../pages/blog/", true, /\.mdx$/)
);
```

## Building the components

Now that we have an array containing all the information about our pages, we are ready to create the `pages/blog/index.js` page so that users can navigate trough them, but first let's abstract our posts cards in the PostCard component

The PostCard component is just the component that we'll use to render metadata about the post and create a link straight to the post. You will later learn how to create the metadata for each post, but for now let's asume that we already have it. So in `components/PostCard.js` add:

```jsx
import Link from "next/link";

export const PostCard = ({ post }) => {
  const {
    link,
    module: { meta },
  } = post;

  return (
    <article className="my-4">
      <h1 className="text-xl font-bold text-center text-gray-900">
        {meta.title}
      </h1>
      <div className="mt-4 mb-6">
        <p className="text-center prose-p">{meta.description}</p>
        <div className="mt-2 text-center">
          <span className="text-sm text-gray-800">{meta.date}</span>
          <span
            className="text-sm text-gray-800"
            role="img"
            aria-label="one coffee"
          >
            ☕ {meta.readTime + " min read"}
          </span>
        </div>
      </div>
      <div className="flex justify-center">
        <Link href={"/blog" + link}>
          <a className="font-bold text-blue-500">
            <span className="text-sm underline uppercase">Read more</span>
          </a>
        </Link>
      </div>
    </article>
  );
};
```

In case you want to add the styles yourself here is the unstyled version:

```jsx
import Link from "next/link";

export const PostCard = ({ post }) => {
  const {
    link,
    module: { meta },
  } = post;

  return (
    <article>
      <h1>{meta.title}</h1>
      <p>{meta.description}</p>
      <div>
        <span>{meta.date}</span>
        <span>☕ {meta.readTime + " min read"}</span>
      </div>
      <Link href={"/blog" + link}>
        <a>Read more</a>
      </Link>
    </article>
  );
};
```

Now that we have how to render each post card, we can take all the posts information that we retrived with getAllPosts and render them all togheter in the `blog/index.js` page to let the reader explore the available posts. Let's do it:

```jsx
import { PostCard } from "../../components/PostCard";
import { posts } from "../../utils/getAllPosts";

export default function () {
  return (
    <div>
      <div className="max-w-3xl min-h-screen mx-auto">
        <div className="px-4 py-12">
          <h1 className="text-3xl font-bold text-center">Blog</h1>
          <p className="mt-4 text-lg text-center text-gray-800">
            All the lastest Devjet news from the devjet team itself
          </p>
        </div>
        <div className="px-4">
          <hr className="text-gray-300" />
          {posts.map((post) => (
            <div>
              <PostCard key={post.link} post={post} />
              <hr className="text-gray-300" />
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}
```

And the unstyled version:

```jsx
import { PostCard } from "../../components/PostCard";
import { posts } from "../../utils/getAllPosts";

export default function () {
  return (
    <div>
      <h1>Blog</h1>
      <hr />
      {posts.map((post) => (
        <div>
          <PostCard key={post.link} post={post} />
          <hr />
        </div>
      ))}
    </div>
  );
}
```

Good news!! we are almost there. We just have to create the BlogPost component and write some content.
Now, we previously talked about the metadata that each post would carry on, here is where we define it. Each post is going to look something like this:

```jsx
import BlogPost from "../../../components/BlogPost";
import { MdxCodeBlock } from "../../../components/MyMdxComponents";

export const meta = {
  title: "Create your blog with Next.js and MDX",
  description:
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque maximus pellentesque dolor non egestas. In sed tristique elit. Cras vehicula, nisl vel ultricies gravida, augue nibh laoreet arcu, et tincidunt augue dui non elit. Vestibulum semper posuere magna.",
  date: "Dec 04, 2021",
  readTime: 2,
};

export default ({ children }) => <BlogPost meta={meta}>{children}</BlogPost>;

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque maximus pellentesque dolor non egestas. In sed tristique elit. Cras vehicula, nisl vel ultricies gravida, augue nibh laoreet arcu, et tincidunt augue dui non elit. Vestibulum semper posuere magna.

Sed vehicula libero pulvinar
tincidunt ligula non, luctus massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas arcu purus, aliquam ac molestie ac, luctus eget sem. Sed pellentesque massa eros, condimentum commodo ligula cursus ut. Mauris sit amet molestie ipsum. Nullam venenatis est at purus mollis consectetur. Phasellus a ipsum a quam ullamcorper aliquet. Nunc gravida bibendum placerat.

## My Headline

Fusce lacinia mauris vel massa tincidunt dignissim. Proin tempus nunc sed efficitur porta. Nunc ornare tellus scelerisque velit euismod, ut mollis diam tristique. Phasellus vel diam egestas augue ullamcorper gravida. Sed id mattis ligula, id suscipit nisl. Ut placerat.

<MdxCodeBlock
  code={`const helloWorld = 'hello world'`}
  language="javascript"
/>
```

The `BlogPost` component is the one in charge of rendering each single post, and receives as parameter the metadata and the post content. Here is the code:

```jsx
import { ArrowNarrowLeftIcon } from "@heroicons/react/solid";
import Link from "next/link";

export default function BlogPost({ children, meta }) {
  return (
    <div>
      <div className="max-w-3xl min-h-screen px-4 mx-auto mb-14">
        <div className="mt-4 mb-10 cursor-pointer">
          <Link href="/blog">
            <a className="flex items-center">
              <ArrowNarrowLeftIcon className="h-4 mr-2" />
              Back to blog
            </a>
          </Link>
        </div>
        <div className="py-10">
          <h1 className="text-3xl font-bold text-center">{meta.title}</h1>
          <div className="mt-2 text-sm text-center text-gray-800">
            <span>{meta.date}</span>
            <span role="img" aria-label="one coffee">
              ☕ {meta.readTime + " min read"}
            </span>
          </div>
        </div>
        <hr className="my-10 text-gray-300" />
        <article className="max-w-3xl mx-auto prose text-justify">
          {children}
        </article>
      </div>
    </div>
  );
}
```

And without any styles

```jsx
import Link from "next/link";

export default function BlogPost({ children, meta }) {
  return (
    <div>
      <Link href="/blog">
        <a>Back to blog</a>
      </Link>
      <h1>{meta.title}</h1>
      <div>
        <span>{meta.date}</span>
        <span role="img" aria-label="one coffee">
          ☕ {meta.readTime + " min read"}
        </span>
      </div>
      <hr />
      <article>{children}</article>
    </div>
  );
}
```

That's it!! Your blog is done, just open a console and run `npm run dev` to explore it.

<div id="bonus" style={{paddingTop: "40px", marginTop: "-40px"}}></div>

## Bonus: How to get the job done in no time

If you've been following along the tutorial, you've probably notice how much work it all takes. Even more keeping in mind the fact that our blog is still missing a lot of very common features like sharing options, a search bar, commentary section, posts clasification, newsletter, etc. 

What if i tell you that you integrate all these features and more in minutes instead of hours, just run a couple of commands and get a bunch on code injected in your project codebase to cover all these common solutions. You don't just save a lot of time and resources but also, given the fact that you are in control of the code, there's nothig stopping you from customizing every single bit of it to meet your goals and allow you to focus on what trully makes you unique.

The tool that allows you to do all that and more is [devjet](https://www.usedevjet.com) and here we show you how to use it to recreate all the code that we've described along this post and even add more feeatures.
For now we are gonna stick to the blog generators, but feel free to explore the entire catalogue at [usedevjet.com/#modules](https://www.usedevjet.com/#modules).

Just like with `create-next-app` we first have to create our boilerplate app, in this case with:

```
devjet new nextjs [appname]
```

Note that nextjs is not the only base that we can use, there are other very great frameworks as well, like vue or react or nuxt among others.

We are gonna end up with a codebase similar to the one generated by `create-next-app`.

Now to add our blog pages we just have to get into the project folder and type in the console `devjet add blog-mdx`

```
devjet add blog-mdx
  ? Do you want to use styled components? - No
  ? Where do you want to store your blog posts? - pages/blog
  ? Do you want us to create an example blog or you prefer the docs? - yes
  ✓ Installed dependencies
  ✓ Created pages/blog folder
  ✓ Added utils/devjet/getAllPosts.js
  ✓ Added components/devjet/PostCard.js
  ✓ Added pages/blog/index.js
  ✓ Added components/devjet/BlogPost.js
  ✓ Edited next.config.js
  ✓ Created pages/blog/post-1 folder (example)
  ✓ Added pages/blog/post-1/index.mdx  (example)
```

And that's all you need to do to either create a blog from the scratch or add it to your already existing project.

Also note that in this case we only generated the logic, just in case that you want to add the styles yourself but we also provide some beatifull premade components to make your work even easier.

The best part, that's not all you can do with devjet's generators, there are hundreds of applications!! Focusing on the blog thematic, these are some generators that you may be interested in:

- `devjet add mailchimp` To create a [mailchimp](https://mailchimp.com/) newsletter and let your users get notified when you write new posts 
- `devjet add google-analytics` Use [google analytics](https://analytics.google.com/) get information about your users behaivoir and improve their expirience
- `devjet add algolia` To use [algolia](https://www.algolia.com/) as a search engine for your posts
- `devjet add google-ads` To monetize your blog with [google ads](https://ads.google.com/)
- `devjet add auth` Add authentication with diferent providers like [firebase](https://firebase.google.com/) or [auth0](https://auth0.com/) and let them save posts
- `devjet add share` Components to let your users share your content and even comment on it.
- `devjet add blog-mdx-prismjs` Highlight your code blocks with [prismjs](https://prismjs.com/).
- And much much more

## Conclusion

In this tutorial we've learned how to create a blog in Nextjs using MDX to add the capability to use custom React components in the markdown syntax. We've also learned that when it comes to web development, "reinventing the wheel" typically takes a lot of time for wich we may have a better use, and so discovered that [devjet](https://www.usedevjet.com) can help us be much more efficient by generating a lot of the code necessary to achive our gols and surpase them.