NextJS + Markdown + Blog

Build the static blog with Next.js and Markdown

Build an entire static blog with markdown—full support of sitemap, category or tags, and SEO support. #Series One

Rajdeep Singh
FrontEnd web

--

I start the entire static blog series. Today I start my first article on the series. First, I build a Static site generation (SSG) demo website with all functionality like SEO, sitemap, category, tags, and RSS page in the series.

Static site generation blog with markdown and nextjs
Static site generation(SSG) blog with markdown and nextjs

All the codes are available on GitHub Repo. You can also check the final website demo with vercel.

Following the nextjs and markdown series, Firstly, I recommended watching Traversy media. after following the article for a better understanding

Content

  1. Project Setup
  2. Project Structure
  3. Posts And Images Dummy data
  4. Pages
  5. Layout
  6. Home Page
  7. Reading page
  8. Category Page
  9. Tag Page
  10. Search Page
  11. SEO

Note

In the next article, we create a Category, tag and search page. In this article, we set up and build the website layout, home and Reading page for a website..

Project Setup

In our project, we need only four NPM packages.

  1. gray-matter
  2. marked
  3. next-sitemap
  4. next-seo

Gray-matter and marked npm package work with markdown files and next-sitemap and next-seo help for website index and SEO.

Project Structure

For starting time, you need a basic setup for your project. Firstly install the nextjs with the latest version.

The second most important is folder structure. So I describe the folder structure and NPM package File.

.
├── components
│ ├── Banner.js
│ ├── Footer.js
│ ├── Header.js
│ ├── ItemPost.js
│ ├── Post.js
│ └── Sidebar.js
├── next.config.js
├── next-seo.config.js
├── next-sitemap.js
├── package.json
├── package-lock.json
├── pages
│ ├── _app.js
│ ├── blog
│ │ └── [slug].js
│ ├── category
│ │ └── [slug].js
│ ├── index.js
│ ├── Search.js
│ └── tag
│ └── [slug].js
├── posts
│ ├── how-is-npm-install-command.md
│ ├── how-to-add-css-in-next-js.md
│ ├── how-to-add-search-bar-functionality-in-ghost-cms-help-of-searchinghost.md
│ ├── how-to-capture-screenshots-in-raspberry-pi-4.md
│ ├── how-to-check-the-snap-store-package-available-for-raspberry-pi-4-or-not.md
│ ├── html-version-history.md
│ ├── keyboard-shortcut-keys-for-linux-terminal.md
│ ├── npm-outdated-command.md
│ ├── text-highlighting-in-html-5.md
│ ├── title-tag-in-html-5.md
│ └── what-is-next-js.md
├── public
│ ├── banner.png
│ ├── favicon.ico
│ ├── imagesnext-seo
│ │ ├── firstway-2.png
│ │ ├── firstway.png
│ │ ├── geenome.png
│ │ ├── gnome-screenshots.png
│ │ ├── How-to-capture-screenshots-in-Raspberry-PI-4.png
│ │ ├── HTML-Version-History.jpg
│ │ ├── Linux-Basic-Introduction--1-.png
│ │ ├── next.js-add-css-code.jpg
│ │ ├── next.js.png
│ │ ├── npm-commands.png
│ │ ├── npm-init-command-1.png
│ │ ├── secondway.png
│ │ ├── Text-Highlighting-In-HTML-5.png
│ │ ├── the-snap-store.png
│ │ └── Title-tag-In-HTML-5.jpg
│ ├── robots.txt
│ ├── sitemap-0.xml
│ ├── sitemap.xml
│ └── vercel.svg
├── README.md
├── search.json
├── styles
│ └── globals.css
├── utils
│ └── index.js
└── yarn.lock
10 directories, 54 files

The Most Important file and folders are components, posts, next-seo.config.js, next-sitemap.js, and search.json files in my folder structure.

Posts And Images Dummy data

I provide demo markdown articles and image data in the Github repo. You can use it.

Pages

We need five pages or a path for our blog website. The path is straightforward for a simple blog.

paths List

  1. pages/index.js Home Page
  2. pages/blog/[slug].js Reading Page
  3. pages/category/[slug].js Category Page
  4. pages/tag/[slug].js Tag Page
  5. pages/search.jsSearch Page

Layout

I’m building the basic layout with bootstrap, custom components, and nextjs _app.js

import '../styles/globals.css'import Head from "next/head";import Header from "../components/Header";import Footer from "../components/Footer";function MyApp({ Component, pageProps }) {return ( <><Head>   <link   href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossOrigin="anonymous" /></Head>   <Header/>     <Component {...pageProps} />   <Footer /></>)}export default MyApp

Home Page

The home page is a specific page on a static website. In the home firstly import some library on-page.

import fs from 'fs'import path from 'path'import matter from 'gray-matter'

Then, we need nextjs getStaticProps to help pass props into components in a second step.

First, we access files in the page folder.

// Get markdown files from the posts dirconst files = fs.readdirSync(path.join('posts'))

In the second step, we collect the post-frontmatter data and slug.

// Get slug and frontmatter from postsconst tempPosts = files.map((filename) => {
// Create slugconst slug = filename.replace('.md', '')// Get frontmatterconst markdownWithMeta = fs.readFileSync( path.join('posts', filename), 'utf-8')convert data in json formatconst { data: frontmatter } = matter(markdownWithMeta)// base frontmatter data we find the publish articles(draft : false)if (frontmatter.draft === false) { return { slug, frontmatter, }} else { return null}})

Filter the articles and remove null value in tempPosts variables

//  when we remove draft article we receve a return null and  now remove null in tempPosts []const posts = tempPosts.filter(      post => {  return post && post  })

In the end, return the post data with props in the component. sortByDate Function help to sort article based on date.

return {    props: {            posts: posts.sort(sortByDate),  },}

Summaries all above the home page code in the down below section.

export async function getStaticProps() {// Get markdown files from the posts dirconst files = fs.readdirSync(path.join('posts'))
// Get slug and frontmatter from posts
const tempPosts = files.map((filename) => {// Create slugconst slug = filename.replace('.md', '')// Get frontmatterconst markdownWithMeta = fs.readFileSync( path.join('posts', filename), 'utf-8')const { data: frontmatter } = matter(markdownWithMeta)// base frontmatter data we find the publish articles(draft : false)if (frontmatter.draft === false) { return { slug, frontmatter, } } else { return null}})// when we remove draft article we receve a return null and now remove null in tempPostsconst posts = tempPosts.filter( post => { return post && post })return { props: { posts: posts.sort(sortByDate), }, }}

Home UI Design

export default function Home({ posts }) {return (<div>        <Banner /> <div className="container">    <div className="row">        <div className="col-lg-8">             {posts.map((post, index) => (                 <Post key={index} post={post} />              )}        </div>           <Sidebar />        </div>  </div></div>)}

Reading page

Reading the page help to read the entire post. reading a file on the side blog/[slug].js . We read the article base on the slug. We use two next.js functionality for a static generation, getStaticPaths, and getStaticProps.

  1. getStaticPaths: Generate all the pages on build time.
  2. getStaticProps: Get the post data and pass the component.

For the reading page, first import the npm and node packages on top of the file.

import fs from 'fs'import path from 'path'import matter from 'gray-matter'

getStaticPaths

In getStaticPaths help to generate all paths on build time. It is a simpler code that we discussed previously.

getStaticPaths Help pass the path as props into components. So you can easily access all the slugs and blog data based on a slug.

First, we access files in the page folder.

// Get markdown files from the posts dirconst files = fs.readdirSync(path.join('posts'))

We collect the post-frontmatter data and slug only publish posts in the second step.

// Get slug and frontmatter from postsconst temppaths = files.map((filename) => {// Get frontmatterconst markdownWithMeta = fs.readFileSync(path.join('posts', filename),'utf-8')// convert data into json const { data: frontmatter } = matter(markdownWithMeta)// get all publish articles and pass slug into paramsif (frontmatter.draft === false) {return {        params: {           slug: filename.replace('.md', ''),       },    }} else {        return null}})

Filter the articles and remove null value in tempPosts variables

//  when we remove draft article we receve a return null and  now remove null in tempPosts []const posts = tempPosts.filter(post => {  return post && post  })

In the end, return all the path data with props.

return {           paths,           fallback: false,}

Summaries all getStaticPaths Reading page code in the down below section.

export async function getStaticPaths() {//  Get files from the posts dirconst files = fs.readdirSync(path.join('posts'))// Get slug and frontmatter from postsconst temppaths = files.map((filename) => {// Get frontmatterconst markdownWithMeta = fs.readFileSync(path.join('posts', filename),'utf-8')const { data: frontmatter } = matter(markdownWithMeta)if (frontmatter.draft === false) {return {params: {slug: filename.replace('.md', ''),},}} else {return null}})//   remove null in tempPostsconst paths = temppaths.filter(path => {return path && path})return {paths,fallback: false,}}

getStaticProps

getStaticProps help to access a single post and pass it into the component.

Firstly we get a post base on a slug. We use the join inbult method with the pass a slug to find a post. Based on a slug, we access a single post.

const markdownWithMeta = fs.readFileSync(path.join('posts', slug + '.md'),'utf-8')

In the second step, we convert data into json format using the gray-matter npm package.

const { data: frontmatter, content } = matter(markdownWithMeta)

In the last step, return the data.

return {   props: {            frontmatter,            slug,            content,         },}

Summaries all the getStaticProps reading page code in the down below section.

export async function getStaticProps({ params: { slug } }) {const markdownWithMeta = fs.readFileSync(     path.join('posts', slug + '.md'),     'utf-8')const { data: frontmatter, content } = matter(markdownWithMeta)return {          props: {                     frontmatter,                     slug,                     content,                  },}}

Reading Page UI Design

export default function PostPage({ content, frontmatter }) {const date = new Date(frontmatter.date)return (<><div className="container my-5">     <div className="row">         <div className="col-lg-10 m-auto">            <div className='card card-page'><a href={`/blog/${frontmatter.slug}`} > <img className="card-img-top" src={ImageUrl(frontmatter.image)} alt={frontmatter.title} /></a><h1 className='post-title mt-2 p-2'>{frontmatter.title}</h1><div className='post-date m-1 p-2'>    <div><h6>{`${date.getMonth() + 1} - ${date.getDate()} - ${date.getFullYear()}`} </h6>  </div><div> {frontmatter.categories.map(category => {const slug = slugify(category)return (<Link key={category} href={`/category/${slug}`}><a className='btn'><h6 className=' post-title'>#{category}</h6></a></Link>)})} </div></div><div className='post-body p-5 m-auto' dangerouslySetInnerHTML={{ __html: marked.parse(content) }}></div></div></div></div></div></>)}

In the next article we create catgory and tag page for our blog.

All the codes are available on GitHub Repo. You can also check the final website demo with vercel.

Conclusion

I know my article is very dull. Suppose you have a basic knowledge of nextjs. I suggest understanding the code and bookmarking the article. If you have any problem with it, then read my article.

--

--

Rajdeep Singh
FrontEnd web

JavaScript | TypeScript | Reactjs | Nextjs | Rust | Biotechnology | Bioinformatic | Frontend Developer | Author | https://linktr.ee/officialrajdeepsingh