Dynamic Markdown Blogs in Next.js/React using gray-matter, react-markdown and react-syntax-highlighter

Rishi Raj JainRishi Raj Jain / January 6, 2021/ ––– views

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: false, // False for production, true for testing on local
  }
}

 

All set! Hope you liked it :)