Creating a Markdown Blog Powered by Next.js in Under an Hour
Tags: nextjs, react, javascript, tutorial
Published: 2022-01-23 20:46:24

_by author [Chris Bongers](https://blog.openreplay.com/authors/chris-bongers)_

I'm pretty sure 99% of the developers out there just love markdown. It's such a fantastic format to write in.

For those not familiar with markdown, it's a flat text format that looks like this:

```md
# Heading

Some text

- list item 1
- list item 2
- list item 3

[My website](https://daily-dev-tips.com)
```

Which would result in the following HTML

```html
<h1><a id="Heading_0"></a>Heading</h1>

<p>Some text</p>

<ul>
	<li>list item 1</li>
	<li>list item 2</li>
	<li>list item 3</li>
</ul>

<a href="https://daily-dev-tips.com">My website</a>
```

Sounds lovely, right? And yes, it is lovely, so how cool would it be if we could make a blog in Next.js that allows us to write markdown formatted posts?

If you are wondering what the result is for this article, here is a little demo video:

![Creating a markdown powered Next.js blog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1db9a6oare3fsa0ks0g.gif)

> Note: At the bottom, I linked the GitHub repo that you can fork 🥳

## Project setup

If you are new to markdown or Next.js, don't worry. This article will guide you completely through setting this project up from start to finish!

Let's start by creating a new blank Next.js project. We'll open up our favorite terminal and run the following command.

```bash
npx create-next-app next-blog
```

This command will create a blank next application in the `next-blog` folder.

Once it's done installing, you can navigate to the project and run it. (This will show the basic Next.js starter)

```bash
cd next-blog

# Run the project
npm run dev
```

## Adding Tailwind CSS for our styling

Tailwind CSS is a perfect CSS framework to make styling super easy for us, and it works super well in a Next.js project.

The first step we have to do is, install the dependencies that are required for Tailwind.

```bash
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
```

Then we can initialize Tailwind by running the following command. It will create all the side files we need.

```bash
npx tailwindcss init -p
```

Now head over to the `tailwind.config.js` file that the command above created for us.
Modify the `content:` array to look like this:

```javascript
content: [
	'./pages/**/*.{js,ts,jsx,tsx}',
	'./components/**/*.{js,ts,jsx,tsx}',
],
```

The last step to making everything work is modifying the existing `styles/global.scss` file.
Remove everything in this file and only add the following three lines.

```css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
```

And that's it. We can now leverage the power of Tailwind in our application.

## Creating a reusable Next.js layout

Next.js supports different pages out of the box, but we want to have one main layout that wraps around this.

We will define a header and footer in this layout, which means we don't need to add them in all the separate files.

To create a layout in Next.js, create a `components` folder in your project.

Inside that folder, create a `layout.js` file.

The layout is a component, which looks like this:

```javascript
import Link from 'next/link';

export default function Layout({ children }) {
  return (
    <div className='flex flex-col min-h-screen'>
      <header className='bg-fuchsia-100 mb-8 py-4'>
        <div className='container mx-auto flex justify-center'>
          <Link href='/'>
            <a>🏡</a>
          </Link>
          <span className='mx-auto'>Welcome to my blog</span>{' '}
        </div>
      </header>
      <main className='container mx-auto flex-1'>{children}</main>
      <footer className='bg-fuchsia-100 mt-8 py-4'>
        <div className='container mx-auto flex justify-center'>
          &copy; 2022 DailyDevTips
        </div>
      </footer>
    </div>
  );
}
```

For this article, I decided not to extract any further, but you could go ahead and move the header and footer into their components for better readability.

The critical part here is that we accept `children` as property and render them in the `main` element.

This allows us to render any component inside there.

We have to open up our `_app.js` file to use this newly created layout. This file is the entry point for your entire application.

By default, you will see it renders a `Component` with specific properties.

We want to wrap that component in our layout.

```javascript
import Layout from '../components/layout';
import '../styles/globals.css';

function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

export default MyApp;
```

And that's it. Any page we create now will be wrapped in our layout component, making it show the header and footer.

Let's try this quickly before moving on.

Modify the `index.js` file and add the following markup for now:

```javascript
return (<div>Hello world 👋</div>);
```

When you rerun the application you should see the following:

![Basic Next.js layout](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xvhkzepenaxrexpd4q63.png)

You see, the header and footer are automatically applied. We didn't need to say they should be used in any way.

## Creating the posts

Since we'll be using markdown for our posts, create a new folder at the root of your project called `posts`.

Inside, you can start by creating your post items. Each file will also become the URL of the article, so keep this in mind.

One example of my articles looks like this: `nextjs-page-options-and-how-they-work.md`

Then inside the file, you have two sections.

The top part, called `frontmatter`, it's a way to add non-rendered elements to your post.

They are divided by three dashes.
And below that is all the content that you want.

For example:

```md
---
title: 'Next.js page options and how they work'
metaTitle: 'Next.js page options and how they work'
metaDesc: 'How to use pages in Next.js exploring the options'
socialImage: images/22-09-2021.jpg
date: '2021-09-22'
tags:
  - nextjs
---

# The main content
```

You can add anything you want in the frontmatter, so you don't have to use this example specifically.

We'll only be using the following for this article:

- title
- socialImage

It's up to you to enhance the website with other cool elements.

> Note: Add the images in the `public/images` directory.

Create a couple of markdown posts (or use the ones from the demo). This makes it easier to test what we are building.

## Loading the markdown posts on the homepage

Now that we have our markdown posts in place, we want to retrieve them and show them on the homepage.

We want to show the image and the title and link to the actual post.

Since the image and title are defined in the markdown file's frontmatter section, we need to find a way to extract them.

Luckily for us, there is a fantastic [NPM package](https://www.npmjs.com/package/gray-matter) that can help us with this. Let's install it.

```bash
npm install gray-matter
```

This package allows us to parse the frontmatter section and the content section from a content string.

Then we want to open up our `index.js` file and start importing the packages we will need.

```javascript
import fs from 'fs';
import matter from 'gray-matter';
import Image from 'next/image';
import Link from 'next/link';
```

Then we need to leverage the Next.js `getStaticProps` method, which allows us to define variables the page can use on build time.

> Note: Check out this article for more information on the [rendering modes of Next.js](https://daily-dev-tips.com/posts/nextjs-page-options-and-how-they-work/)

The main frame for this function looks like this:

```javascript
export async function getStaticProps() {
  // Get all our posts
}
```

We need to work on retrieving all the posts from our `posts` folder.

We can leverage the `fs` (filesystem) module.

```javascript
const files = fs.readdirSync('posts');
```

Then we want to loop over all those posts to extract the frontmatter part to get the title and image.

```javascript
const posts = files.map((fileName) => {
	const slug = fileName.replace('.md', '');
	const readFile = fs.readFileSync(`posts/${fileName}`, 'utf-8');
	const { data: frontmatter } = matter(readFile);
	
	return {
	  slug,
	  frontmatter,
	};
});
```

There is quite a lot going on here, so let's look step by step.

First, we define the slug (URL) for the page, which is the filename without the `.md` part.

Then we read the file by using the `fs` module again.
And once it's loaded, we use the `matter` package to read the file and extract the `data` object, but we destructure it as the variable `frontmatter`.

Basically, we change the `data` variable to `frontmatter`.

The last thing we need to do is return the props that our component will receive.

```javascript
return {
	props: {
	  posts,
	},
};
```

This sends the `posts` variable to our component.

So let's modify our `Home` component to receive this variable.

```javascript
export default function Home({ posts }) {
```

That's it! Yes, super simple, right? We now have access to the posts inside the component.

Let's create a grid and render our posts in there.

```javascript
export default function Home({ posts }) {
  return (
    <div className='grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 p-4 md:p-0'>
      {posts.map(({ slug, frontmatter }) => (
        <div
          key={slug}
          className='border border-gray-200 m-2 rounded-xl shadow-lg overflow-hidden flex flex-col'
        >
          <Link href={`/post/${slug}`}>
            <a>
              <Image
                width={650}
                height={340}
                alt={frontmatter.title}
                src={`/${frontmatter.socialImage}`}
              />
              <h1 className='p-4'>{frontmatter.title}</h1>
            </a>
          </Link>
        </div>
      ))}
    </div>
  );
}
```

The CSS part is rendering a grid, where we show one post on mobile and up to four for big screens.

Then we loop over each of the posts using the `map` method.

Each post will be one big link, including the image of the posts and the title.
And on click, it links to `/post/{slug}`, which we'll start working on next.

Let's quickly look and admire what we have made so far:

![Next.js reading markdown files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l4olb9bugg8ssz3zd5au.png)

## Creating individual markdown posts in Next.js

Now that we have a list of our posts on the homepage, we want to show more details and the actual content on each page.

We can leverage Next.js dynamic routing, allowing one file to render all our posts!

To create this file, create a `post` directory inside the `pages` directory and inside this folder, add a file called `[slug].js`.

The brackets define the file as a dynamic file.

This file will also use the `getStaticProps` to retrieve the data for a single post.

But it needs another function called `getStaticPaths` to create each path (URL).

Let's start by importing the modules we need.

```javascript
import fs from 'fs';
import matter from 'gray-matter';
```

And from here, we can work on the `getStaticPaths` function to ensure all our files get their URL.

```javascript
export async function getStaticPaths() {
  // Retrieve all our slugs
}
```

Again, the first thing we need to do is get all our posts.

```javascript
const files = fs.readdirSync('posts');
```

And you guessed it right, map the filenames to get the slugs.

```javascript
const paths = files.map((fileName) => ({
	params: {
	  slug: fileName.replace('.md', ''),
	},
}));
```

Then the critical part here is this function should return all valid paths, so we simply need to return them.

I also included a `fallback: false`, which ensures non-existing URLs will fail and show a 404.

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

The static part is much like what we did on the homepage. 
But for this one, we also want to retrieve the data from the file.
This is because the file contains all our actual content.

```javascript
export async function getStaticProps({ params: { slug } }) {
  const fileName = fs.readFileSync(`posts/${slug}.md`, 'utf-8');
  const { data: frontmatter, content } = matter(fileName);
  return {
    props: {
      frontmatter,
      content,
    },
  };
}
```

Before we start working on the component for this page, we want to install another NPM package called `markdown-it`.
This package can convert markdown into HTML.

```bash
npm install markdown-it
```

Then load it on this page:

```javascript
import md from 'markdown-it';
```

Now we can start working on the actual component, which is super simple as it only renders the title and the content.

```javascript
export default function PostPage({ frontmatter, content }) {
  return (
    <div className='prose mx-auto'>
      <h1>{frontmatter.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: md().render(content) }} />
    </div>
  );
}
```

As you can see, we need to use `dangerouslySetInnerHTML` as we are setting HTML.
Don't be scared by the name, as it's only HTML we allow to be in there.

Almost there!

Let's quickly try it out and see what we got.
Run your app and open a page.

![Markdown in Next.js post](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xa6o6o0s3my05appyzeg.png)

Cool, all our data is there, but it doesn't look great yet.

Luckily we decided to use Tailwind CSS, and they have a fantastic plugin: [Typography plugin](https://daily-dev-tips.com/posts/make-your-life-easy-with-the-tailwind-typography-plugin/)

Let's install this plugin and see what happens.

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

Open the `tailwind.config.js` file and add it under the plugins section.

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

Now reload your app and see what happens.

![Tailwind Typography rendering markdown](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fdjm23ygeugyhaev2m7i.png)

## Open Source Session Replay
Debugging a web application in production may be challenging and time-consuming. [OpenReplay](https://github.com/openreplay/openreplay) is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue.
It’s like having your browser’s inspector open while looking over your user’s shoulder.
OpenReplay is the only open-source alternative currently available.

![OpenReplay](https://raw.githubusercontent.com/openreplay/openreplay/main/static/replayer.png)

Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay).



## Conclusion

And that's it. We now have a super cool markdown-powered Next.js blog!

This is only a basic starter, but enough to get your blogging career started.

Not bad for an hour worth of work!

Not it's up to you to make this blog even more unique and share with the world what you created.

Some ideas to make it more excellent:

- SEO aspect, add meta titles/description
- Add a tag overview
- Add some coolers to the code blocks
- Refactor elements into components

As promised, you can download and use this starter template for free.

[Download the Next.js Markdown blog start from GitHub](https://github.com/rebelchris/next-markdown-blog)