Build a NextJS Blog with MDX and Tailwind.
Tags: react, javascript, nextjs, webdev
Published: 2022-01-02 06:44:58


Hello programmers, 

Do you want start your blog where you educate others, or may be you want a blog as a repository of all the information you’ve gathered over the years. Anyway, blogs can be a great source of information providers for others as well as yourself. It can really help you connect deep with the content you want to consume. Setting up a blog is easy, especially if you’re a programmer. You can create your own blog with **Next.JS** and **MDX.** In this article, I will show you exactly how to do that!

## What are we going to build?

By the end of this article, we will have a blog site for ourselves, which is going to look like this. You can off course make it look more beautiful, but for the sake of tutorial, I made it look very simple.

![Blog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/knali8ys7wcdh2rjvl5v.png)
## Introduction

## What do we need?

- A decent knowledge of Next.JS framework
- Dependencies - `path fs gray-matter next-mdx-remote`
- Tailwind CSS

## Let’s Start!

## Create a next project

First of al, we’ll start by creating a next project

`yarn create next-app blog`

`cd blog`

Install all the necessary dependencies.

`yarn add fs path gray-matter next-mdx-remote` 

| fs | Provides a way to work with files |
| --- | --- |
| path | Provides a way to work with directories and paths. |
| gray-matter | Parses the front-matter from a string or file |
| next-mdx-remote | To render your mdx content on the page |

## Setting up Tailwind

Run the following commands, in your terminal to install tailwind.

`yarn add tailwindcss postcss autoprefixer -D`

Run this command to create a tailwind.config.js file

`npx tailwindcss init -p`

Inside the tailwind.config.js, paste the following

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

Include these in your **styles/globals.css** file

```css
/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
```

## Homepage

In index.js file, create an **async** function **getStaticProps(). getStaticProps()** is used in data fetching and returning the result as a prop to the same component. Next.JS will render this page at build time. 

```jsx
// pages/index.js

export async function getStaticProps() {
  // Read the pages/posts dir
  let files = fs.readdirSync(path.join("pages/posts"));

  // Get only the mdx files
  files = files.filter((file) => file.split(".")[1] === "mdx");

  // Read each file and extract front matter
  const posts = await Promise.all(
    files.map((file) => {
      const mdWithData = fs.readFileSync(
        path.join("pages/posts", file),
        "utf-8"
      );

      const { data: frontMatter } = matter(mdWithData);

      return {
        frontMatter,
        slug: file.split(".")[0],
      };
    })
  );

  // Return all the posts frontMatter and slug as props
  return {
    props: {
      posts,
    },
  };
}

```

Inside **getStaticProps** we will use the **fs** and **path** module to read the **.mdx** stored inside the **/pages/posts** directory. 

We will then filter the result to only get the MDX files and not the **[slug.js]** file that we will create ahead.

```jsx
files = files.filter((file) => file.split(".")[1] === "mdx");
```

We will then map through each file using the **.map** array function and then read each individual file using the **fs** and **path** module and extract the front matter of the file using the **matter()** function (imported from gray-matter) and store the front matter along with slug of every file in the posts variable.

```jsx
// import matter from 'gray-matter';

// Read each file and extract front matter
  const posts = await Promise.all(
    files.map((file) => {
			// read file
      const mdWithData = fs.readFileSync(
        path.join("pages/posts", file),
        "utf-8"
      );
			
			// extract front matter
      const { data: frontMatter } = matter(mdWithData);

      return {
        frontMatter,
        slug: file.split(".")[0],
      };
    })
  );
```

`posts` variable will look somethings like this -

```jsx
posts = {
	frontMatter: {
		// frontMatter object extracted from the mdx file
	},
	slug: string
}[]
```

At last, we will map through each post (inside the props) and render it in the UI. We will also use the `Link` component from next to create a link to each post.

The final index.js file will look like this

```jsx
// pages/index.js
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import Link from "next/link";
import PostCard from "../components/PostCard";
import Layout from "../components/Layout";

const Home = ({ posts }) => {
  return (
      <div className="container w-[80%] md:w-[60%] mx-auto">
        <h1 className="text-blue-700 text-3xl font-bold my-12">My Blog 📙</h1>
        <div className="posts md:grid md:grid-cols-3 gap-8">
          {posts.map((post) => (
            <Link href={`/posts/${post.slug}`} key={post.slug}>
              <a>
                <PostCard post={post} />
              </a>
            </Link>
          ))}
        </div>
      </div>
  );
};

export default Home;

export async function getStaticProps() {
  // Read the pages/posts dir
  let files = fs.readdirSync(path.join("pages/posts"));

  // Get only the mdx files
  files = files.filter((file) => file.split(".")[1] === "mdx");

  // Read each file and extract front matter
  const posts = await Promise.all(
    files.map((file) => {
      const mdWithData = fs.readFileSync(
        path.join("pages/posts", file),
        "utf-8"
      );

      const { data: frontMatter } = matter(mdWithData);

      return {
        frontMatter,
        slug: file.split(".")[0],
      };
    })
  );

  // Return all the posts frontMatter and slug as props
  return {
    props: {
      posts,
    },
  };
}
```

## PostCard Component

Create a component **components/PostCard.js.** We will use this component to return card for each post.

```jsx
const PostCard = ({ post }) => {
  return (
    <div className="rounded-md w-72 border transition-all hover:text-blue-700 hover:shadow-lg hover-scale:105 cursor-pointer">
      <img src={post.frontMatter.cover_image} alt="Cover Image" />
      <div className="mt-2 p-2">
        <h2 className="font-semibold text-xl">{post.frontMatter.title}</h2>
      </div>
    </div>
  );
};

export default PostCard;
```

## Post page

Create a **/pages/posts/[slug].js** page to render each post separately on a different route.

We will use the **getStaticPaths** async function to generate separate routes according to the slug for each post at the build time.

```jsx
export async function getStaticPaths() {
  // Read the files inside the pages/posts dir
  const files = fs.readdirSync(path.join("pages/posts"));

  // Generate path for each file
  const paths = files.map((file) => {
    return {
      params: {
        slug: file.replace(".mdx", ""),
      },
    };
  });

  return {
    paths,
    fallback: false,
  };
}
```

We will the **getStaticProps** once again to read files and extract front matter as well as the content from it using the gray-matter module. The content of the mdx files need to be serailized in order to render it using the next-mdx-remote module. 

```jsx
export async function getStaticProps({ params: { slug } }) {
  // read each file
  const markdown = fs.readFileSync(
    path.join("pages/posts", slug + ".mdx"),
    "utf-8"
  );

  // Extract front matter
  const { data: frontMatter, content } = matter(markdown);

  const mdxSource = await serialize(content);

  return {
    props: {
      frontMatter,
      slug,
      mdxSource,
    },
  };
}
```

We wil then render the mdx source recieved inside the props.

```jsx
// pages/posts/[slug.js]
import path from "path";
import matter from "gray-matter";
import { serialize } from "next-mdx-remote/serialize";
import { MDXRemote } from "next-mdx-remote";
import styles from "../../styles/Post.module.css";

const Post = ({ frontMatter, slug, mdxSource }) => {
   return (
    <Layout title={frontMatter.title}>
      <div className={styles.post}>
        <h1 className="font-semibold my-8 text-3xl text-blue-700">
          {frontMatter.title}
        </h1>
        <MDXRemote {...mdxSource} />
      </div>
    </Layout>
  );
};
```

## Styling Post page

We will also add some basic styling for the post page using tailwind directives. Create a **styles/Post.module.css** file and include these styles for a better look.

```css
// styles/Post.module.css

.post {
  @apply container w-[90%] md:w-[60%] mx-auto my-12;
}

.post p {
  @apply leading-7 my-4;
}

.post img {
  @apply my-4 w-full;
}
```

## Bonus - Syntax Highlighting! 🎉

If you want to see, how to add syntax highlighting for your code elements in the mdx files, you can checkout my full video tutorial I did on my YouTube channel 

{% youtube c4ge3tgGZuw %}

## Connect with me

Twitter - [shaancodes](https://twitter.com/shaancodes)
Github - [shaan-alam](https://github.com/shaan-alam)
YouTube - [shaancodes](https://www.youtube.com/channel/UC9eR4oVH9IWR8SVvrw61Cog)
Instgram - [shaancodes](https://instagram.com/shaancodes)