Build a markdown blog with NextJS

telmo

Telmo Goncalves

Posted on January 12, 2020

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 πŸ‘

πŸ’– πŸ’ͺ πŸ™… 🚩
telmo
Telmo Goncalves

Posted on January 12, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related