Blog under the hood
2024-01-25This post deals with key concepts enabling the functionality of markdown files as blog articles. It provides a clear demonstration of how everything works.
Creating a blog in Next.js is very straightforward. The official documentation of Next.js provides an example of how to create a blog. Considering this, we can say that Next.js is more than just a good technology for that case. Here is the link:
https://nextjs.org/learn-pages-router/basics/data-fetching/blog-data
In my process, I diverged from the official documentation on several occasions, so if you decide to follow the official Next.js documentation (which I recommend), you may end up with a different product.
When it comes to creating a blog, one of the fundamental problems is Dynamic Routing. It’s relly tough to imagine creating a blog with multiple articles without planning dynamic routing, which will allow managing URLs based on generated article names.
In this application, Next.js generates its routes at build time. So they are immediately available when the application is served as a whole.
In addition, Next.js also allows dynamic routes to be managed at runtime. However, in the case this is not necessary. Runtime dynamic routing is something that is more commonly used in e-commerce web applications.
Communication with the URL in Next.js is possible through the [slug].js file located within the folder whose route we want to append the blog name to. For example, if the blog folder, which has an index.js representing its page, receives the same-level file [slug].js, that file will be able to create a route for each generated slug, rendering the specified component.
Because it is necessary for the names of markdown files to be URL roots that enable the rendering of individual articles, we must use a file system library that will collect the names of all files from the folder containing individual articles and gather their titles. Using additional capabilities of the JavaScript language, we will remove the .md extension because we do not want it to be in the URL. For this purpose, collecting articles at build time, we will use functions that come with Next.js, getStaticPaths, and getStaticProps.
getStaticPaths will collect file names and allow us to manipulate them as needed and return them within that file.
export async function getStaticPaths() { const files = fs.readdirSync(path.join("posts")); const paths = files.map((filename) => ({ params: { slug: filename.replace(".md", ""), }, })); return { paths, fallback: false, }; }
Below or next to it, we will use getStaticProps, which will allow us to take those titles and further process them, taking additional information from files such as gray matter and content, and automatically distribute it to the component defined in this file. For both of these methods, include an expandable with a click add-on that shows how the function is defined in the official documentation and what it is used for.
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, }, }; }
Omitting content here for space conservation. The content is straightforward and does not require display.
After delivering the desired data to the component, we can immediately render everything on the spot, or we can continue to modularize the components to make our application easier to maintain. In our case, we have a special Article component that presents this article data in an appropriate way.
import ReactMarkdown from "react-markdown";
import styles from "../styles/Article.module.css";
import rehypeRaw from "rehype-raw";
import SyntaxHighlighter from "react-syntax-highlighter";
import { dark } from "react-syntax-highlighter/dist/cjs/styles/hljs";
export default function Article({
title,
content,
description,
date,
author,
category,
}) {
return (
<div className="flex flex-col h-full bg-white rounded-lg overflow-hidden my-4 p-5">
<h1 className="text-5xl font-bold">{title}</h1>
<span className="font-thin block pb-8">{date}</span>
<p className="font-light italic block pb-8">{description}</p>
<ReactMarkdown
rehypePlugins={[rehypeRaw]}
children={content}
components={{
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || "");
return !inline && match ? (
<SyntaxHighlighter
style={dark}
language={match[1]}
PreTag="div"
children={String(children).replace(/\n$/, "")}
{...props}
/>
) : (
<code className={className} {...props} />
);
},
}}
/>
<div className="flex gap-10 pt-8">
<span className="block font-thin">{author}</span>
<span className="block font-thin">{category}</span>
</div>
</div>
);
}
At this point, in this component, we performed additional manipulation of data by importing libraries, reactmarkdown, syntaxhighlighter, rehypeRaw. Each of these libraries has enabled us to do something that we could not do with simple markdown rendering. This was my first encounter with these libraries, so I won’t call myself an expert; you can find more details in the documentation of each individual library.
- Reactmarkdown - React component to render markdown.
- Syntaxhighlighter - Syntax highlighting component for React using the seriously super amazing lowlight and refractor by wooorm.
- rehypeRaw - rehype plugin to parse the tree (and raw nodes) again, keeping positional info okay.
In the upcoming blog post, we will continue to explore and explain these libraries in more detail.
In the end, we have a blog that is built without a database, and it is hosted as an asset within the web application directory. This is a very interesting approach, and I am very satisfied with the result. I hope you will be too.