Build a markdown blog with NextJS
Telmo Goncalves
Posted on January 12, 2020
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.
Less than 1 hour. I've used NextJS for the first time and it's pretty amazing. And it goes without saying I've used @zeithq for the hosting π₯
β Telmo Goncalves (@telmo ) January 6, 2020
I might keep it updated, just wanted to check how long would take me to get a blog up and running.https://t.co/XsFjLYP7MU
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
npm init -y && npm install react react-dom next --save
If you're wondering
-y
means you don't have to answer all thenpm init
questions
Now, in your package.json
file replace scripts
with:
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
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
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: "/"
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>
)
}
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'
+ }
+ }
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>
)
}
// ...
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'
}
}
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
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 }
}
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
In the hello-world.md
file paste the following:
--------
title: "Hello World"
date: "2020-01-07"
--------
This is my first blog post!
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 }
}
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
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 }
}
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
Inside that new file paste the following code:
module.exports = {
webpack: function(config) {
config.module.rules.push({
test: /\.md$/,
use: 'raw-loader',
})
return config
}
}
This will be using the raw-loader
package, so we'll need to install that as well:
npm install raw-loader --save
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>
)
}
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>
)
}
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
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
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>
)
}
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 π
Posted on January 12, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.