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 :)