Dynamic Markdown Blogs in Next.js/React

You are looking for a sustainable, scalable solution to create dynamic blogs whether it be for your website, or someone else’s, then react-markdown is the solution for it. This blog assumes, obviously, you are working with Next.js/React.

To get started with, create a directory say ‘_snippets’ and in this directory create a sample markdown file, say ‘Dynamic Markdown Blogs in Next.js using gray-matter, react-markdown and react-syntax-highlighter.md. The slug of the route for the particular blog would be derived from the markdown file name itself.

Here’s the sample markdown for you to copy:

---  
title: 'Dynamic Markdown Blogs in Next.js/React using gray-matter, react-markdown and react-syntax-highlighter'  
excerpt: 'Implementing markdown blogs in Next.js/React with remark-markdown in just 5 minutes. Thinking of using strings? Nah, they are too old, use markdown. Export statically to achieve great perfomance.'  
date: '2021-01-06'  
---  

You are looking for a sustainable, scalable solution to create dynamic blogs whether it be for your website, or someone else's, then react-markdown is the solution for it. This blog assumes, obviously, you are working with Next.js/React.

Done with the content 🚀 & proceed by executing the following on your terminal:

npm i react-syntax-highlighter gray-matter react-markdown

Done with the installation 🚀, proceed by creating the following component:

// File: /components/MarkdownHighlight.js  

import React from 'react'  
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'  
import {  
  materialDark,  
  materialLight,  
} from 'react-syntax-highlighter/dist/cjs/styles/prism'  

const MarkdownHighlight = ({ darkMode, language, value }) => {  
  return (  
    <SyntaxHighlighter  
      language={language}  
      style={darkMode ? materialLight : materialDark}  
    >  
      {value}  
    </SyntaxHighlighter>  
  )  
}  

export default MarkdownHighlight

This component is gonna take care of highlighting your markdown code blocks whether html, css, js or markdown. Browse react-syntax-highlighter/dist/cjs/styles/prism for exploring different kind of filters such as {coy}, {dark} apart from {materialDark}, {materialLight}.

The code below will be handling formation of blog data from the markdown files. Explanations for each function has been added.

// File: /lib/markdown.js  

import { join } from 'path'  
import fs from 'fs'  
import matter from 'gray-matter'  

// Directory of snippets  
const pagesDirectory = join(process.cwd(), '_snippets')  

// Form slugs from the markdown names  
export function getSlugsFromDirectory(dir) {  
  return fs.readdirSync(dir)  
}  

/**  
 * Gets the contents of a file  
 * The gray-matter (metadata at the top of the file) will be  
 * added to the item object, the content will be in  
 * item.content and the file name (slug) will be in item.slug.  
 */  
export function getBySlug(dir, slug, fields = []) {  
  const realSlug = slug.replace(/\.md$/, '')  
  const fullPath = join(dir, `${realSlug}.md`)  
  const fileContents = fs.readFileSync(fullPath, 'utf8')  
  const { data, content } = matter(fileContents)  
  const items = {}  
  fields.forEach((field) => {  
    if (field === 'slug') {  
      items[field] = realSlug  
    }  
    if (field === 'content') {  
      items[field] = content  
    }  
    if (data[field]) {  
      items[field] = data[field]  
    }  
  })  
  return items  
}  

// Returns contents of a page in the _snippets directory  
export function getPageContentBySlug(slug, fields = []) {  
  return getBySlug(pagesDirectory, slug, fields)  
}  

// Returns pages in the _snippets directory  
export function getAllSnippets(fields = []) {  
  const slugs = getSlugsFromDirectory(pagesDirectory)  
  const pages = slugs.map((slug) => getPageContentBySlug(slug, fields))  
  return pages  
}

Done with the creating helping functions 🚀, let’s proceed by creating the dynamic blog page:

// File: /pages/snippet/[slug].js  

import { useRouter } from 'next/router'  
import ReactMarkdown from 'react-markdown'  
import MarkdownHighlight from '@/components/MarkdownHighlight'  
import { getAllSnippets, getPageContentBySlug } from '@/lib/markdown'  

const Snippet = ({ page, darkMode }) => {  
  const router = new useRouter()  
  return router.isFallback ? (  
    <div>Loading...</div>  
  ) : (  
    <div className="container">  
      <h1>{page.title}</h1>  
      <div className={styles['mt-3']}>  
        <div className={darkMode ? 'text-white' : 'text-dark'}>  
          <ReactMarkdown  
            source={page.content}  
            renderers={{  
              code: ({ language, value }) => {  
                /* Automatically takes in the language & value from the markdown file when: ```<html/css/js>  
                  Content here  
                  ``` in the markdown file*/  
                return (  
                  <MarkdownHighlight  
                    language={language}  
                    value={value}  
                    darkMode={darkMode}  
                  />  
                )  
              },  
            }}  
          />  
        </div>  
      </div>  
    </div>  
  )  
}  

export default Snippet  

export async function getStaticProps({ params }) {  
  const { slug } = params  
  const page = getPageContentBySlug(slug, [  
    'title',  
    'image',  
    'slug',  
    'content',  
    'date',  
  ])  
  return {  
    props: {  
      page: {  
        ...page,  
        markdown: page.content,  
      },  
    },  
  }  
}  

export async function getStaticPaths() {  
  const posts = getAllSnippets(['slug'])  
  const paths = posts.map(({ slug }) => ({  
    params: {  
      slug,  
    },  
  }))  
  return {  
    paths,  
    fallback: 'blocking'  
  }  
}

All set! Hope you liked it :)