DEV Community

Telmo Goncalves
Telmo Goncalves

Posted on • Updated on • Originally published at telmo.online

Build a markdown blog with NextJS

I've posted a Tweet about building my blog in less than an hour, and I'll be honest; it took me more time writing this post than actually putting the blog online.

I'll try to explain the steps I took.

If you don't want to follow the tutorial download the Source code.


I've decided to go ahead and create a personal page/blog for myself, and since I'm a massive fan of Zeit and Now, that meant no time wasted thinking about hosting and deployments.

I have a few projects running with using GatsbyJS, and to be honest, I love it, it's easy to use, and really powerful if you plug a third-party such as Contentful. Although this time, I wanted to try something different, and since I love hosting and deploy my projects with Zeit, why not give NextJS a try? First time using it, and let me tell you it's freaking amazing.


Let's get started

Run the following:

mkdir my-blog && cd my-blog
Enter fullscreen mode Exit fullscreen mode
npm init -y && npm install react react-dom next --save
Enter fullscreen mode Exit fullscreen mode

If you're wondering -y means you don't have to answer all the npm init questions

Now, in your package.json file replace scripts with:

"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start"
}
Enter fullscreen mode Exit fullscreen mode

If you go ahead and try to start the server npm run dev, it should throw an error, because NextJS is expecting to find a /pages folder.

So, let us take care of that, in the root of your project run:

mkdir pages && touch pages/index.js
Enter fullscreen mode Exit fullscreen mode

Now you should be able to run npm run dev and access your application on http://localhost:3000

If everything is going as expected you should see an error similar to the following:

The default export is not a React Component in page: "/"
Enter fullscreen mode Exit fullscreen mode

That's alright; keep going.


Our first view

In your pages/index.js file, paste the following code:

import React from 'react'

export default function Index() {
  return (
    <div>
      ✍️ My blog about Books
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Check http://localhost:3000 you should see My blog about Books

Getting props

NextJS comes with a function called getInitialProps; we can pass props into our Index component.

Let us start with something simpler; at the end of your component lets put the following code:

import React from 'react'

export default function Index() {
  return (
    <div>
      ✍️ My blog about Books
    </div>
  )
}

+ Index.getInitialProps = () => {
+   return {
+     blogCategory: 'Books'
+   }
+ }
Enter fullscreen mode Exit fullscreen mode

Here we're passing a blogCategory prop into our component, go ahead and change your component to look like the following:

export default function Index(props) {
  return (
    <div>
      ✍️ My blog about {props.blogCategory}
    </div>
  )
}

// ...
Enter fullscreen mode Exit fullscreen mode

If you refresh the page, it should look exactly the same, although, if you change the value of blogCategory you'll see that it changes your view with the new value. Give it a try:

// ...

Index.getInitialProps = () => {
  return {
    blogCategory: 'ReactJS'
  }
}
Enter fullscreen mode Exit fullscreen mode

The content of your view should now be: ✍️ My blog about ReactJS

Awesome, next!


Dynamic Routes

So, to build a blog, you want dynamic routes, according to the route we want to load a different .md file, which will contain our post data.

If you access http://localhost:3000/post/hello-world we'll want to load a file called hello-world.md, for that to happen let us follow the next steps:

First of all, NextJS is clever enough to allow us to create a [slug].js file, which is pretty awesome, let's go ahead and create that file:

mkdir pages/post
Enter fullscreen mode Exit fullscreen mode

Note the folder and file needs to be created inside /pages

Now create a file inside /post called [slug].js, it's exactly like that, with the brackets.

Inside this file we'll create our post template, to display the post title, contents, etc.

Go ahead and paste the following code, we'll go over it in a minute:

import React from 'react'

export default function PostTemplate(props) {
  return (
    <div>
      Here we'll load "{props.slug}"
    </div>
  )
}

PostTemplate.getInitialProps = async (context) => {
  const { slug } = context.query

  return { slug }
}
Enter fullscreen mode Exit fullscreen mode

In here we're accessing context.query to extract the slug from the URL, this is because we called our file [slug].js, let's say instead of a blog post you want to display a product page, that might contain an id, you can create a file called [id].js instead and access context.query.id.

If you access http://localhost:3000/post/hello-world you should see Here we'll load "hello-world"

Brilliant, let's keep going!


Loading Markdown Files

As a first step lets create a .md file:

mkdir content && touch content/hello-world.md
Enter fullscreen mode Exit fullscreen mode

In the hello-world.md file paste the following:

--------
title: "Hello World"
date: "2020-01-07"
--------

This is my first blog post!
Enter fullscreen mode Exit fullscreen mode

That's great; now we need to load the content of this file and pass it through props in our PostTemplate file.

Check the comments on the changed lines:

// ...

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`)

  return { slug }
}
Enter fullscreen mode Exit fullscreen mode

Now that we have the data, we'll be using [gray-matter (https://www.npmjs.com/package/gray-matter) to parse our file frontmatter data.

frontmatter data is the information between --- in our .md file

To install gray-matter run:

npm install gray-matter --save
Enter fullscreen mode Exit fullscreen mode

We can now parse the data and pass it to the PostTemplate props:

Don't forget to import matter

import matter from 'gray-matter'

// ...

PostTemplate.getInitialProps = async (context) => {
  // ...

  // Parse .md data through `matter`
  const data = matter(content.default)

  // Pass data to the component props
  return { ...data }
}
Enter fullscreen mode Exit fullscreen mode

Awesome, now we should be able to access data in our component props. Let's try it, refresh the page... Ah, snap!

Are you getting a TypeError: expected input to be a string or buffer error?

No worries, we need to add some NextJS configuration to tell it to load .md files, this is a simple process, in the root of your project run:

touch next.config.js
Enter fullscreen mode Exit fullscreen mode

Inside that new file paste the following code:

module.exports = {
  webpack: function(config) {
    config.module.rules.push({
      test: /\.md$/,
      use: 'raw-loader',
    })
    return config
  }
}
Enter fullscreen mode Exit fullscreen mode

This will be using the raw-loader package, so we'll need to install that as well:

npm install raw-loader --save
Enter fullscreen mode Exit fullscreen mode

Don't forget to restart your application

Now let's change our component to receive our new props:

// ...
export default function PostTemplate({ content, data }) {
  // This holds the data between `---` from the .md file
  const frontmatter = data

  return (
    <div>
      <h1>{frontmatter.title}</h1>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Refresh your page, you should see Hello World.

It's missing rendering the content, lets take care of that:

export default function PostTemplate({ content, data }) {
  // This holds the data between `---` from the .md file
  const frontmatter = data

  return (
    <div>
      <h1>{frontmatter.title}</h1>

      <p>{content}</p>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Ok, this is great, you should be able to see This is my first blog post!


Markdown Format

Now that we can render our markdown files fine, let's add some formatting to our post file, go ahead and change hello-world.md:

--------
title: "Hello World"
date: "2020-01-07"
--------

### Step 1

- Install dependencies
- Run locally
- Deploy to Zeit
Enter fullscreen mode Exit fullscreen mode

Hmmm, the format is not working like expected, it's just raw text.

Let's take care of that, we'll be using react-markdown to handle markdown formatting:

npm install react-markdown --save
Enter fullscreen mode Exit fullscreen mode

Now lets update our PostTemplate component:

import React from 'react'
import matter from 'gray-matter'
import ReactMarkdown from 'react-markdown'

export default function PostTemplate({ content, data }) {
  // This holds the data between `---` from the .md file
  const frontmatter = data

  return (
    <div>
      <h1>{frontmatter.title}</h1>

      <ReactMarkdown source={content} />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

That's it; we are done here! You can download the final code here.


If you liked this post, I would really appreciate if you could share it with your network and follow me on Twitter 👏

Top comments (12)

Collapse
 
kozakrisz profile image
Krisztian Kecskes

Thanks! This article is very helpful!

Collapse
 
telmo profile image
Telmo Goncalves

Thanks Krisztian 🎉

Collapse
 
kozakrisz profile image
Krisztian Kecskes

Do you have any idea how to implement MDX support into this logic?

Thread Thread
 
telmo profile image
Telmo Goncalves

I do not, but I can explore it.

Collapse
 
imranib profile image
Imran Irshad

That is very helpful. Thanks

Collapse
 
kerrytokyo profile image
Kerry

Thank you for writing this article!
It was helpful.

Collapse
 
telmo profile image
Telmo Goncalves

Thank you Kerry 😃

Collapse
 
en0ma profile image
Lawrence Enehizena

Hi Telmo, I think you have a typo in the code here - const content = import(../../content/${slug}.md) , it should be const content = await import(../../content/${slug}.md)

Collapse
 
telmo profile image
Telmo Goncalves

Hey Lawrence, you're right. Thanks for the heads up, will change it 👍

Collapse
 
mohansingh profile image
Mohansingh Omprakash

So i have a doubt , Suppose if i have a count of 500 posts in my website ,should i need to create 500 markdown files for that?
Please help in an efficient approach

Collapse
 
josiasaurel profile image
Josias Aurel

Awesome tutorial. This very helpful

Collapse
 
victeoteokw profile image
Victor Teo

Thanks this is very helpful!