;

NextJs Markdown Blog

Learn How To create Markdown Blog with nextjs
ReactNextjsJavascriptmarkdownblog
30-September-2020

Build A Next.js Markdown Blog.

Note: This is a advanced Topic So I assume you are already familiar with React, JavaScript and basics of web development.

Next.Js

Nextjs is a React framework. It is the most popular framework because it is easy to use, very flexible and has great file based routing system. It gives you server side rendering out of the box.

Let's Dive in

if You don't want to code along and only want to see the code please checkout source code

I Had to create blogs for my personal portfolio site. And There are few posts on the internet but I could not find any simple solution. So I decided To write a simple post about this. Lets Start

To Create a nextjs app run following Command in terminal

1
2
3
npm init next-app
# or
yarn create next-app

You can use npm or yarn package manager but I will be using yarn

Give your project a name. The Package manager will install all the necessary packages.

Run this command

cd YOUR_PROJECT_NAME

start the Project

yarn dev

Your project should be online on port 3000 and you should see something like this

next-start

Great. In pages/index.js remove everything and paste following code

1
2
3
4
5
6
7
import React from "react";

const Index = () => {
  return <h1>My First Blog</h1>;
};

export default Index;

create a file config.json in the root of the folder and provide site title and description. (This step is for SEO purpose).

1
2
3
4
{
  "title": "Nextjs Blog Site",
  "description": "A Simple Markdown Blog build with Nextjs."
}

create a folder in the root directory called content. This is where our .md files will go.

Now Your Folder structure should look like this

file-structure

components directory will contain our blogs logic

content directory will contain our markdown files

pages directory contains our pages (routes)

public directory for serving static files (assets)

Lets open pages/index.jsand bring in the site title and description from config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react";

const Index = (props) => {
  console.log("Index -> props", props);

  return <h1>My First Blog</h1>;
};

export default Index;

export async function getStaticProps() {
  const siteData = await import(`../config.json`);

  return {
    props: {
      title: siteData.default.title,
      description: siteData.default.description,
    },
  };
}

After You Save this page you should see something like this in your browsers console.

Index -> props {title: "Nextjs Blog Site", description: "A Simple Markdown Blog build with Nextjs."}.

Ok So What just Happened here. Let's Break it down

getStaticProps getStaticProps is Nextjs Function which we can call from our page . It will return the props to our component. just like we have props to our index

We will use this method to fetch our posts later.

The content will be generated at the build time. If you don't know what it means don't worry about it just keep that in mind that the content will be available prebuild and we will not be fetching posts every time user visits our site. Pretty Cool right.

we are importing our config.json file and returning title and description as props to our index components

Next.js also gives us Head component that we can append elements to head of the page. like site title, meta tags links and such.

import Head from 'next/head'

1
2
3
4
<Head>
  <title>My page title</title>
  <meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>

Let's add this to our Index page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from "react";
import Head from "next/head";

const Index = (props) => {
  return (
    <>
      <Head>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charSet="utf-8" />
        <meta name="Description" content={props.description}></meta>
        <title>{props.title}</title>
      </Head>
      <h1>My First Blog</h1>;
    </>
  );
};

export default Index;

export async function getStaticProps() {
  const siteData = await import(`../config.json`);

  return {
    props: {
      title: siteData.default.title,
      description: siteData.default.description,
    },
  };
}

After adding Head take a look at tab of your browser, what do you see. The Title of the site has been updated.

site-title

Ideally you would want to put this to Layout component but in our case I think this is fine.

Now back to our Blogs. We Need To add some packages to our project. Run The Following command

yarn add react-markdown gray-matter raw-loader

OR

npm install react-markdown gray-matter raw-loader

react-markdown will help us parse and render markdown files

gray-matter will parse front matter of our blogs. (the part at the top of file between --- )

We will need this metadata for title , data and description and slug. You can add anything you like here (maybe hero image URL)

raw-loader will help us import our markdown files.

After we are done with installation we need little bit of web pack configuration. create a file next.config.js in root directory

and paste the following code.

1
2
3
4
5
6
7
8
9
module.exports = {
  webpack: function (config) {
    config.module.rules.push({
      test: /\.md$/,
      use: "raw-loader",
    });
    return config;
  },
};

NOTE: After you Create This file You Must Restart you dev server.

In content directory create two markdown files

content/blog-one.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
slug: blog-one
title: My First Blog
description: This Description Of My First Blog.
date: 25-September-2020
---

# h1

## h2

### h3

Normal text

content/blog-two.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
slug: blog-two
title: My Second Blog
description: This Description Of My Second Blog.
date: 25-September-2020
---

# h1

## h2

### h3

Normal text

First we will render list of blogs with title and description.

in our index.js replace the getStaticProps function with

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export async function getStaticProps() {
  const siteData = await import(`../config.json`);
  const fs = require("fs");

  const files = fs.readdirSync(`${process.cwd()}/content`, "utf-8");

  const blogs = files.filter((fn) => fn.endsWith(".md"));

  const data = blogs.map((blog) => {
    const path = `${process.cwd()}/content/${blog}`;
    const rawContent = fs.readFileSync(path, {
      encoding: "utf-8",
    });

    return rawContent;
  });

  return {
    props: {
      data: data,
      title: siteData.default.title,
      description: siteData.default.description,
    },
  };
}

fs is nodejs module that helps us read and write files. we will use fs.readdirSync to read the files.

process.cwd() will give us the directory where Next.js is being executed. From Our current directory (root) we want to go in /content and read all the files and store them in variable files

endsWith endsWith is JavaScript string method which determines whether a string ends with the characters of a specified string, returning true or false as appropriate.

we will map over the blogs and get path and rawContent

Now Our index components will receive data prop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React from "react";
import Head from "next/head";
import matter from "gray-matter";
import Link from "next/link";

const Index = ({ data, title, description }) => {
  const RealData = data.map((blog) => matter(blog));
  const ListItems = RealData.map((listItem) => listItem.data);

  return (
    <>
      <Head>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charSet="utf-8" />
        <meta name="Description" content={description}></meta>
        <title>{title}</title>
      </Head>
      <h1>My First Blog</h1>;<div>
        <ul>
          {ListItems.map((blog, i) => (
            <li key={i}>
              <Link href={`/${blog.slug}`}>
                <a>{blog.title}</a>
              </Link>
              <p>{blog.description}</p>
            </li>
          ))}
        </ul>
      </div>
    </>
  );
};

We are mapping over data and and formatting each blog with gray-matter;

At this point Your should see something like this

blogs-list

if You click on My First Blog it should take you to /blog-one or whatever you named your blog

Dynamic Routes

we May have fifty different blogs. we Don't want to page for each blog. if we make a file in pages directory blog we can navigate to localhost:3000/blog. But if add square brackets around blog (name of file) like so [blog].js we have a dynamic route.

the route will end up to localhost:3000/:blog

Create a new page [blog].js in pages directory

1
2
3
4
5
6
7
import react from "react";

const Blog = () => {
  return <h1>Blog</h1>;
};

export default Blog;

Now Lets Get the file from content Directory

1
2
3
4
5
6
7
8
Blog.getInitialProps = async (context) => {
  const { blog } = context.query;

  const content = await import(`../content/${blog}.md`);
  const data = matter(content.default);

  return { ...data };
};

You Should have content and data prop available in Blog component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import react from "react";
import matter from "gray-matter";
import ReactMarkdown from "react-markdown";

const Blog = ({ content, data }) => {
  const frontmatter = data;

  return (
    <>
      <h1>{frontmatter.title}</h1>
      <h3>{frontmatter.description}</h3>
      <ReactMarkdown escapeHtml={true} source={content} />
    </>
  );
};

export default Blog;

Blog.getInitialProps = async (context) => {
  const { blog } = context.query;
  // Import our .md file using the `slug` from the URL
  const content = await import(`../content/${blog}.md`);
  const data = matter(content.default);

  return { ...data };
};

Oh My Goodness. It is working.

What about Code

For Code formatting we will use react-syntax-highlighter package

yarn add react-syntax-highlighter

Now Create a code Block in [blog].js and pass it to ReactMarkdown

1
2
3
4
5
6
7
8
9
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";

const CodeBlock = ({ language, value }) => {
  return (
    <SyntaxHighlighter showLineNumbers={true} language={language}>
      {value}
    </SyntaxHighlighter>
  );
};

Now Your [blog].js should look like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import react from "react";
import matter from "gray-matter";
import ReactMarkdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";

const CodeBlock = ({ language, value }) => {
  return (
    <SyntaxHighlighter showLineNumbers={true} language={language}>
      {value}
    </SyntaxHighlighter>
  );
};

const Blog = ({ content, data }) => {
  const frontmatter = data;

  return (
    <>
      <h1>{frontmatter.title}</h1>
      <h3>{frontmatter.description}</h3>
      <ReactMarkdown
        escapeHtml={true}
        source={content}
        renderers={{ code: CodeBlock }}
      />
    </>
  );
};

export default Blog;

Blog.getInitialProps = async (context) => {
  const { blog } = context.query;
  // Import our .md file using the `slug` from the URL
  const content = await import(`../content/${blog}.md`);
  const data = matter(content.default);

  return { ...data };
};

Create a new File in content directory conding-blog.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
slug: coding-blog
title: Coding blog
author: Imran Irshad
description: Coding Post For Beautiful Code
date: 30-September-2020
---

# React Functional Component

​```jsx
import React from "react";

const CoolComponent = () => <div>I'm a cool component!!</div>;

export default CoolComponent;
​```

Now If Click coding-blog

coding-image

Images

Create a new file in content named image-blog

1
2
3
4
5
6
7
8
9
10
---
slug: image-blog
title: Image Blog
description: See How Images Looks in our Blog
date: 30-September-2020
---

# Image

![Landscape](https://images.unsplash.com/photo-1501785888041-af3ef285b470?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60)

image-blog

Conclusion

Nextjs is awesome and very flexible . You can create really cool stuff with it. I hope you learned a thing or two from this post.

Get In Touch

Wanna get in touch or talk about a project?

Feel free to contact me via email at imran.ib@live.com

or leave a Message