Building a Markdown blog using Next.js and Tailwind Typography
Tags: nextjs, tailwindcss, markdown, tutorial
Published: 2023-05-24 18:30:35

## Project Overview

In this tutorial, we will create a Markdown blog using Next.js, a powerful React framework for building web applications. We will use **`Tailwind CSS`**, a utility-first CSS framework, to style the blog. Additionally, we will leverage the **`Tailwind Typography plugin`** to enhance the typography of the Markdown content.

To parse the Markdown files and extract metadata, we will use the gray-matter package. The nextjs-mdx-remote package will help us seamlessly render the Markdown content on the server and client sides. Lastly, we will utilize the date-fns package for working with dates and formatting them in the blog posts.

The current webpage has been developed using the instructions provided in this blog.

You can find the updated code for this project [here](https://github.com/albac/dev-blogs/tree/main).

This guide follows  [Harry Wolf's YouTube tutorial video](https://www.youtube.com/watch?v=vu9gPcPs3mY) with some personal modifications we have added. Please watch the tutorial and consider subscribing to [his YouTube Channel.](https://www.youtube.com/watch?v=vu9gPcPs3mY)

[![IMAGE_ALT](https://img.youtube.com/vi/vu9gPcPs3mY/2.jpg)](https://www.youtube.com/watch?v=vu9gPcPs3mY)

You will find the code for this page on my public [repository](https://github.com/albac/dev-blogs).

Additionally, I have opened a [pull request](https://github.com/hswolff/blog-with-nextjs-and-tailwind/pull/2) to the original author with the changes made to this blog.

---

## Getting Started

To begin, ensure that you have the following software and packages installed on your machine:

- [**Node.js**](https://nodejs.com) (version 19.4.0)
- [**Next.js**](https://nextjs.org/) (version 13.3.4)
- React (version 18.2.0)
- Tailwind CSS (version 3.3.2)
- @tailwindcss/typography (version 0.5)
- nextjs-mdx-remote (version 1.5.0)
- gray-matter (version 4.0.3)
- date-fns (version 2.23.0)


* **Tailwind Typography**: Tailwind Typography is a plugin for Tailwind CSS that provides a set of typographic styles and utilities for Markdown content. It helps improve the readability and visual appeal of text by applying consistent and responsive typographic styles.

* **nextjs-mdx-remote**: The nextjs-mdx-remote package is a library that enables rendering of MDX (Markdown and JSX) content in Next.js applications. It allows you to fetch and render MDX files on both the server and client sides, making it suitable for building blogs and documentation websites.

* **gray-matter**: gray-matter is a popular JavaScript library used for parsing and extracting metadata from Markdown files. It allows you to access front-matter data (metadata) in your Markdown files, such as titles, dates, or custom fields. It simplifies working with Markdown files that contain additional structured data.

* **date-fns**: date-fns is a lightweight JavaScript library for working with dates. It provides various utility functions for parsing, formatting, manipulating, and comparing dates. In the context of a blog, date-fns can be used to format dates from metadata in a human-readable format, handle time zones, calculate time differences, and perform other date-related operations.


---

## The setup

Make sure you have the latest version of [Node](https://nodejs.org/en/download/current/) installed:

```bash
$ node --version
v19.4.0
```

---

### Creating Next.js project

We recommend starting from a new project and adding the changes described in the YouTube video. However, you can also clone the [original author's GitHub](https://github.com/hswolff/blog-with-nextjs-and-tailwind).

To create a new project just run the following for creating new [**`Next.js`** Project with **`Tailwind`**](https://tailwindcss.com/docs/guides/nextjs):

```bash
$ npx create-next-app@latest dev-blog --typescript --eslint
```

---

### Installing and configuration for tailwindcss

Next install **`tailwind`** and other dependencies by running:

```bash
$ cd dev-blog
$ npm install -D tailwindcss postcss autoprefixer
$ npx tailwindcss init -p
```

Change the file **`styles/globals.css`** with the following content:

```javascript
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Optionally, I added some base style to styles/globals.css for links and headings:

```javascript
@layer base {
    a {
        @apply text-blue-600 hover:text-blue-500 dark:text-sky-400;
    }
    h1 {
        @apply dark:text-gray-300 text-3xl;
    }
    h2 {
        @apply text-2xl;
    }
    h3 {
        @apply text-xl;
    }
}
```

---

### Tailwindcss Typography setup

The Tailwind CSS [Typography plugin](https://tailwindcss.com/docs/typography-plugin) will allow us to have more control over the styling of Markdown.

Run the following command to install the plugin:

```bash
$ npm install -D @tailwindcss/typography
```

Also, add the plugin to the Tailwind CSS configuration file, **`tailwind.config.js`**, to look like this:

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

```

---

### Install nextjs-mdx-remote and other npm packages

Now install [nextjs-mdx-remote](https://github.com/hashicorp/next-mdx-remote) and all other NPM package dependencies:

```bash
$ npm install gray-matter --save
$ npm install date-fns --save
$ npm install next-mdx-remote --save
```

---

## The Code

Here we will explain the changes we made to improve and fix current issues due to some package upgrades. 
First, we follow almost the same changes as the original author.
Second, during the video instruction the author was using **`renderString`** and **`hydrate`** functions, that changed on the future releases of **Next-MDX-Remote**.
Finally, we added [Tailwind Typography](https://tailwindcss.com/docs/typography-plugin) which helps to improve the styles of the markdown and show a more stylish look to our blog.
See all the code changes for this project at my [github repo](https://github.com/albac/dev-blogs).

---

### Generating date and creating mdx file:

To generate the date for the MDX content, Harry uses a Node command line, which we will also be using. However, we want to create the file with a name that can be sorted by date on the home page.

Using the command below, we can get the date from the **`toISOString()`** output and the name for the file from the **`getTime()`** output:

```bash
$ node -e 'console.log(new Date().toISOString(), new Date().getTime())'
2022-04-27T23:34:37.161Z 1651102477161
```
Create a directory called **`mdxfiles`** and place  there all your markdowns.

We can then create the file using the name from **`getTime()`** and the date from **`toISOString()`**:

```bash
$ vim mdxfiles/1651102477161.mdx
---
title: Blog with NextJs, Tailwind and Markdown
summary: |
  Short description
date: 2022-04-27T23:34:37.161Z
---

Some content here....

```

Now let's modify the [`index.js`](https://github.com/albac/dev-blogs/blob/main/pages/index.js#L35) file and reverse array order, so we get the latest blog on top.

Use **`slice(0).reverse.map`** to reverse the array:

To display the latest blog post on top, we need to modify the [`index.js`](https://github.com/albac/dev-blogs/blob/main/pages/index.js#L35) file and reverse the order of the array. We can use slice(0).reverse().map() to reverse the array, as shown below:

```javascript
posts: allPosts
    .slice(0)
    .reverse()
    .map(({ data, slug }) => ({
        ...data,
        date: data.date.toISOString(),
        content: data.summary,
        slug,
    })),

```

---

### Get Posts

And let's create the [`GetAllPost library`](https://github.com/albac/dev-blogs/blob/main/lib/data.js) that will get all post from our mdxfiles directory.

```javascript
export function getAllPosts() {
  const allPosts = fs.readdirSync(contentDirectory);
  // console.log(allPosts);

  return allPosts.map((fileName) => {
    const slug = fileName.replace(".mdx", "");
    const fileContents = fs.readFileSync(
      path.join(contentDirectory, fileName),
      "utf8"
    );
    const { data, content } = matter(fileContents);
    // console.log(data, content);
    return {
      data,
      content,
      slug,
    };
  });
}
```

---

### List Posts

Now we edit our [`index`](https://github.com/albac/dev-blogs/blob/main/pages/index.js) page to list all posts, we use getAllPost library on the getStaticProps.

```javascript
export default function Home({ posts }) {
    return (
        <div className="h-screen">
            <Head>
                <title>albac: home</title>
                <meta
                    name="description"
                    content="Generated by create next app"
                />
                <link rel="icon" href="/favicon.ico" />
            </Head>

            <main>
                <h1>Projects</h1>
            </main>
            <div className="space-y-8 mt-4">
                {posts.map((item) => (
                    <BlogListItem key={item.slug} {...item} />
                ))}
            </div>
        </div>
    );
}

export async function getStaticProps() {
    const allPosts = getAllPosts();

    return {
        props: {
            posts: allPosts
                .slice(0)
                .reverse()
                .map(({ data, slug }) => ({
                    ...data,
                    date: data.date.toISOString(),
                    content: data.summary,
                    slug,
                })),
        },
    };
}

```

---

### Changes for next-mdx-remote

The [`breaking release Next-mdx-remote V3`](https://github.com/hashicorp/next-mdx-remote/releases/tag/3.0.0) included internal rewrites from V2. This blog is using next-mdx-remote 4.4.1, so we need to change the following file: [`pages/blog/[slug].js`](https://github.com/albac/dev-blogs/blob/main/pages/blog/%5Bslug%5D.js).

```javascript
-import renderToString from 'next-mdx-remote/render-to-string';
-import hydrate from 'next-mdx-remote/hydrate';
+import { serialize } from 'next-mdx-remote/serialize';
+import { MDXRemote } from 'next-mdx-remote';
```

The hydrate function is no longer necessary, and you can use MDXRemote directly to hydrate the markdown content:

```javascript
<MDXRemote {...content} />
```

The **`renderToString`** function has been replaced with **`serialize`**:

```javascript
-  const mdxSource = await renderToString(content);
+  const mdxSource = await serialize(content);
```

---

### Using typography plugin and prose

We can also use Tailwind's typography plugin to style the markdown. Follow the instructions in the [`tailwindcss/typography`](https://tailwindcss.com/docs/typography-plugin) documentation. We can use the **`prose`** class to add styles and also use HTML tags in the markdown.

```javascript
<article className="prose dark:prose-invert text-slate-600 dark:text-slate-300 font-light font-sans">
    <MDXRemote {...content} />
</article>

```

Here's an example of how we use **`h2 tags`** on the markdown:

```javascript
<h2>The setup</h2>

Make sure you have the latest [node](https://nodejs.org/en/download/current/) installed:

```

We're also using **`dark:prose-inverse`** to enable the use of our dark theme on the markdown.

You can see all changes on our dynamic pages [here](https://github.com/albac/dev-blogs/blob/main/pages/blog/%5Bslug%5D.js).

---

### Build and Start NextJs

While working on development you can start the app by only running **`npm run dev`**. However at this point you can generate an optimize Next.js version by running `npm run build` and then run **`npm run dev`** or **`npm run start`**


```bash
$ npm run build
> dev-blogs@0.1.0 build
> next build

info  - Linting and checking validity of types
info  - Creating an optimized production build
info  - Compiled successfully
info  - Collecting page data
info  - Generating static pages (7/7)
info  - Finalizing page optimization

Route (pages)                              Size     First Load JS
┌ ● /                                      672 B          83.4 kB
├   /_app                                  0 B            75.6 kB
├ ○ /404                                   182 B          75.8 kB
├ ○ /about                                 400 B            76 kB
├ λ /api/hello                             0 B            75.6 kB
└ ● /blog/[slug]                           1.31 kB          84 kB
    ├ /blog/1651102477161
    ├ /blog/1651103589178
    └ /blog/1651104045510
+ First Load JS shared by all              79.3 kB
  ├ chunks/framework-2c79e2a64abdb08b.js   45.2 kB
  ├ chunks/main-dda1ec63a16662d1.js        26.8 kB
  ├ chunks/pages/_app-5bca0e82a983c558.js  2.76 kB
  ├ chunks/webpack-8fa1640cc84ba8fe.js     750 B
  └ css/b6c8a4e05cb9e5d4.css               3.7 kB

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)
●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
```

This output indicates that there are no issues currently with compiling and generating the static content. Now, just run **`yarn dev`** to start the server and start coding:

```bash
$  npm run dev

> dev-blogs@0.1.0 dev
> next dev

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
event - compiled client and server successfully in 1090 ms (188 modules)
wait  - compiling...
event - compiled successfully in 104 ms (139 modules)
wait  - compiling /_error (client and server)...
event - compiled client and server successfully in 44 ms (189 modules)
```

Lastly, the end result can be seen on this page style. Compare it to the [GitHub link](https://github.com/albac/dev-blogs/blob/main/mdxfiles/1651104045510.mdx) to see the difference.

<article className="prose xl:prose-2xl bg-white space-y-2 space-x-2 p-4 rounded">
<div>
[![IMAGE_ALT](https://github.com/albac/dev-blogs/raw/main/images/typography.png)](https://raw.githubusercontent.com/albac/dev-blogs/main/images/typography.png)
</div>
</article>

---

### What is next?

- Host Blog using AWS Amplify
- Manage Blog with Amplify Studio
- Host Next.js images in S3 with AWS Amplify.
- Add Highlights and code language recognization to code blocks
- Improve NexJs performance and SEO best practices.

---

## Conclusion
In this tutorial, we learned how to create a Markdown blog using Next.js and Tailwind Typography. We covered the concepts of rendering Markdown content, styling it using Tailwind CSS, and leveraging packages like nextjs-mdx-remote, gray-matter, and date-fns. By following the steps outlined in this tutorial, you now have the foundation to create your own dynamic and stylish Markdown blog using Next.js and Tailwind CSS. 

---

## References
* Code for this arcticle: [https://github.com/albac/dev-blogs](https://github.com/albac/dev-blogs/tree/main)
* Author original blog: [Building a Markdown blog using Next.js and Tailwind Typography](https://albac.dev/blog/0b54da03-f63a-465d-8966-65c9defe2d64)
* Next MDX Remote GitHub: [https://github.com/hashicorp/next-mdx-remote](https://github.com/hashicorp/next-mdx-remote)
* Tailwind Typography plugin: [https://tailwindcss.com/docs/typography-plugin](https://tailwindcss.com/docs/typography-plugin)
* Usefull tool to checkout markdown syntax issues: [https://mdxjs.com/playground/](https://mdxjs.com/playground/)

---