Build a Simple Blog Site with Next.js and Markdown

About the author

Telmo Goncalves is a software engineer with over 13 years of software development
experience and an expert in React. He’s currently Engineering Team Lead at Marley Spoon.

Check out more of his work on telmo.is

When I was building my own website and blog, I chose to use Markdown in conjunction with Next.js
because it’s easy, fast, and, to be honest, I enjoy writing in Markdown. In this article, I’ll walk through how I did
this so you can produce a great, content-driven site of your own.

This article assumes the reader is generally unfamiliar with setting up web applications with tools like npm and the
system Terminal. Several steps involve encountering errors and backtracking; this was intentional for the purposes of
educating the reader on how the application is architected and the interdependencies of each component.

Those more familiar will be able to skip through some of the explanatory details for each step.

Setting up the project

First, create a folder for your project. Do this by running:

mkdir blog 

&&

cd

blog

For those new to UNIX commands, this produces a new directory named ‘blog’ (mkdir blog) and navigates to it (cd blog).

Next, install the following dependencies using:

npm init -y 

&&

npm install react react-dom next --save

Running npm init will create a package.json file in our project, using -y will skip npm’s default questions. If
you like to include this information, just remove it.

Since the site will use next, the scripts in package.json will need to be changed to the following:

"scripts"

:

{

"dev"

:

"next"

,

"build"

:

"next build"

,

"start"

:

"next start"

}

Next, run npm run dev which should start the project on port 3000. Open your browser and navigate to
http://localhost:3000.

At this point, an error may be thrown (or the build didn’t complete at all). This is because Next.js is expecting to
find a pages directory. This is where you’ll create content pages.

Create the pages directory with an index.js file by running the following:

mkdir pages 

&&

touch pages/index.js

Now if you run npm run dev and open http://localhost:3000 in your browser, the following error should be shown:

The

default

export

is

not

a

React

Component

in

page

:

"/"

This is because the index.js file exists, but it’s empty.

Adding a homepage

First, create a React component index.js at the top of the file:

import

React

from

'react'

function

Homepage

()

{

return

(

<

h1

>

Welcome

to

the

Homepage

!<

/h1>

)

}

export

default

Homepage

Once saved, Welcome to the Homepage! should be displayed on http://localhost:3000.

You can create many pages as you want (ie – author.js).

Do this by running:

touch pages/author.js

And adding a React component (shown using author.js):

import

React

from

'react'

function

Author

()

{

return

(

<>

<

h1

>

About

the

Author

<

/h1>

<

p

>

I

'

m

a

full

-

stack

engineer

with

13

+

years

of

experience

.

I

started

my

career

back

in

2007

as

a

designer

,

then

quickly

started

coding

as

well

.

<

/p>

<

/>

)

}

export

default

Author

Save, and you’ll be able to see the content by opening http://localhost:3000/author in your browser.

Sending props

Here, we’ll dive into how getInitialProps from Next.js works. Above the export default Homepage line on the index .js homepage, add:

Homepage

.

getInitialProps

=

()

=>

{

return

{

blogTitle

:

'Rookie for life!'

}

}

Collective, it should look like this:

import

React

from

'react'

function

Homepage

(

props

)

{

return

(

<

h1

>

Welcome

to

the

blog

:

{

props

.

blogTitle

}

!<

/h1>

)

}

Homepage

.

getInitialProps

=

()

=>

{

return

{

blogTitle

:

'Rookie for life!'

}

}

export

default

Homepage

Props (which stands for “property”) are used for passing data from one component to another.

Here a prop is created called blogTitle and passed into the Homepage component. When you access
http://localhost:3000, the content should now read: Welcome to the blog: Rookie for life!

The app is passing a prop saying what the value of blogTitle is. If you change it from Rookie for life! to
Rockstar! and save, you should see the page update with the new title.

Dynamic routes

Next, let’s create blog articles using Markdown .md files and dynamically load them. Using Markdown files for the
content is beneficial because it’s easier to write in them and manage the content.

That way, when a user opens a page like http://localhost:3000/blog/article-name it will render with content written
in an article-name.md file.

A great feature of Next.js is that it allows developers to create dynamic files.

Create a dynamic file called [slug].js in a pages/posts/ directory:

mkdir

pages

/

posts

&&

touch

pages

/

posts

/

[

slug

].

js

Note that the posts directory will be inside of the pages directory.

Now create a posts template using a React component:

import

React

from

'react'

function

PostTemplate

(

props

)

{

return

(

<

div

>

Here

we

'

ll

load

"{props.slug}"

<

/div>

)

}

PostTemplate

.

getInitialProps

=

async

(

context

)

=>

{

const

{

slug

}

=

context

.

query

return

{

slug

}

}

export

default

PostTemplate

This queries the URL and extracts the slug from it. slug is used because that’s the name of the file ([slug].js). If the file were named [id].js, the query would be context.query.id instead.

Now, if you access http://localhost:3000/posts/hello-world you should see: Here we’ll load “hello-world”

If you change hello-world to in the URL awesome-nextjs-blog, the page will display: Here we’ll load
“awesome-nextjs-blog”

Rendering Markdown files

The content Markdown files should be stored in a separate directory so they’re easier to manage. Create a content
directory and a Markdown file by running:

mkdir content 

&&

touch content/awesome-nextjs-blog.md

Upon opening the content directory, there should be a blank awesome-nextjs-blog.md file.

Open the awesome-nextjs-blog.md file and add the following:

---
title: "Build an awesome Next.js blog"
date: "2020-10-01"
---

We're building an awesome Next.js blog using Markdown.

Now open [slug].js and add the following above return { slug }:

  // Import our .md file using the `slug` from the URL
  const content = import(`../../content/${slug}.md`)

So collectively, it looks like this:

PostTemplate

.

getInitialProps

=

async

(

context

)

=>

{

const

{

slug

}

=

context

.

query

// Import our .md file using the `slug` from the URL

const

content

=

import

(

`../../content/

${

slug

}

.md`

)

return

{

slug

}

}

The application is now querying slug from the URL and loading an .md file with that slug.

Markdown files contain frontmatter data. This is information defined in the section divided off using the ---
markers in the .md file. To parse that in some way. To parse that, use gray-matter.

This can be installed by running:

npm install gray-matter --save

Now update the [slug].js template to import matter and parse the .md content:

import

React

from

'react'

import

matter

from

'gray-matter'

function

PostTemplate

(

props

)

{

return

(

<

div

>

Here

we

'

ll

load

"{props.slug}"

<

/div>

)

}

PostTemplate

.

getInitialProps

=

async

(

context

)

=>

{

const

{

slug

}

=

context

.

query

// Import our .md file using the `slug` from the URL

const

content

=

await

import

(

`../../content/

${

slug

}

.md`

)

// Parse .md data through `matter`

const

data

=

matter

(

content

.

default

)

// Pass data to our component props

return

{

...

data

}

return

{

slug

}

}

export

default

PostTemplate

If you reload the page, you should see an error: TypeError: expected input to be a string or buffer

This will need to be resolved by adding a Next.js configuration file. Do this by running:

touch next.config.js

Open the newly created next.config.js and add the following configuration:

module

.

exports

=

{

webpack

:

function

(

config

)

{

config

.

module

.

rules

.

push

({

test

:

/\.md$/

,

use

:

'raw-loader'

,

})

return

config

}

}

This requires the raw-loader package, so that will need to be installed
as well by running:

npm install raw-loader --save

Keep in mind that any time an update is made to the next.config.js configuration file, it’s best to restart the
application.

Now reopen the post template and update the PostTemplate component to handle the frontmatter data:

function

PostTemplate

({

content

,

data

})

{

// This holds the data between `---` from the .md file

const

frontmatter

=

data

return

(

<>

<

h1

>

{

frontmatter

.

title

}

<

/h1>

<

/>

)

}

Then in the same component, add <p>{content}</p> for rendering the Markdown content:

function

PostTemplate

({

content

,

data

})

{

// This holds the data between `---` from the .md file

const

frontmatter

=

data

return

(

<>

<

h1

>

{

frontmatter

.

title

}

<

/h1>

<

p

>

{

content

}

<

/p>

<

/>

)

}

You should now see the title: Build an awesome Next.js blog
As well as: We’re building an awesome Next.js blog using Markdown

Markdown formatting

Finally, the application will need to convert the content we have written in Markdown so it renders to users in the
expected format.

Add the following to awesome-nextjs-blog.md:

---
title: "Build an awesome Next.js blog"
date: "2020-10-01"
---

## Step 1

-

Install dependencies

-

Run locally

-

Deploy to Vercel

This will display the content as plain text in the browser. To render the content written in Markdown; install
react-markdown by running:

npm install react-markdown --save

Then in the [slug].js file, import ReactMarkdown and update the PostTemplate component to replace
<p>{content}</p> with <ReactMarkdown source={content} />.

Collectively it should look like this:

import

React

from

'react'

import

matter

from

'gray-matter'

<

ReactMarkdown

source

=

{

content

}

/>

function

PostTemplate

({

content

,

data

})

{

// This holds the data between `---` from the .md file

const

frontmatter

=

data

return

(

<>

<

h1

>

{

frontmatter

.

title

}

<

/h1>

<

ReactMarkdown

source

=

{

content

}

/>

<

/>

)

}

PostTemplate

.

getInitialProps

=

async

(

context

)

=>

{

const

{

slug

}

=

context

.

query

// Import our .md file using the `slug` from the URL

const

content

=

await

import

(

`../../content/

${

slug

}

.md`

)

// Parse .md data through `matter`

const

data

=

matter

(

content

.

default

)

// Pass data to our component props

return

{

...

data

}

return

{

slug

}

}

export

default

PostTemplate

Now you’re all set! If you’re new to Markdown, check out this handy cheat sheet which is great to reference as you’re learning the ropes.

Telmo regularly posts helpful React development tips and guides on Twitter. Be sure to follow him at @telmo