Integrating MDX in Next.js Site for Blogging
This post will walk you through how to use Markdown to write blogs on your website. We'll leverage MDX, allowing JSX integration with markdown files.
Installing Dependencies
npm install next-mdx-remoteExample Blog Structure
Assuming your blogs are stored in the ~/data/blogs/ directory, here's how a sample blog file (first-blog.mdx) might look:
---
title: 'This is a title'
date: '2024-03-21'
description: 'This is a description'
---
## Hello
Content...
Creating Utility Functions
Next, let's create some utility functions to manage your blogs effectively. Below is a function getSlugs to retrieve all the slugs (file names) present in ~/data/blogs, which will later serve as routes to visit your blog posts.
import fs from "fs"
import path from "path"
export const getSlugs = (): string[] => {
const slugs: string[] = []
const files = fs.readdirSync(path.join("data/blogs"))
const mdFxIles = files.filter((file) => file.endsWith(".mdx"))
mdFxIles.map((filename: string) => {
slugs.push(filename.replace('.mdx', ''))
})
return slugs
}Creating a Page with All the Blogs
Now, let's create a page that dynamically fetches and displays all your blog posts. We'll create a page.tsx file in src/app/blogs/ directory and use
const slugs = getSlugs() to gather all the blogs.
import Link from "next/link"
import { getSlugs } from "@/lib/utils"
const Page = async () => {
const slugs = getSlugs()
return (
<div>
{slugs.map((post) => (
<Link href={`blogs/${post}`} key={post}>
<div>
{post}
</div>
</Link>
))}
</div>
)
}
export default PageFetching Content and Metadata
Now, let's create another utility function to fetch content and metadata from your blogs. This function getPostDataBySlug will extract the content and front matter when provided with a slug.
import { compileMDX } from "next-mdx-remote/rsc"
export const getPostDataBySlug = async (slug: string): Promise<Post> => {
const file = fs.readFileSync(
path.join("data/blogs", `${slug}.mdx`), "utf-8"
)
const { content, frontmatter } = await compileMDX({
source: file,
options: { parseFrontmatter: true }
})
return {
content,
metaData: {
title: frontmatter.title,
date: frontmatter.date,
description: frontmatter.description
},
slug
}
}Enhancing the Blog Page
Now, let's utilize this function to dispaly blog titles in /blogs page instead of showing the file name as the title.
This is what your src/app/blogs/page.tsx file might look like
import Link from "next/link"
import { getPostDataBySlug, getSlugs } from "@/lib/utils"
const Page = async () => {
const slugs = getSlugs()
const posts = await Promise.all(slugs.map(async (slug) =>
await getPostDataBySlug(slug)))
return (
<div>
{posts.map((post) => (
<Link
href={`blogs/${post.slug}`}
key={post.metaData.title}>
<div>
{post.metaData.title}
</div>
</Link>
))}
</div>
)
}
export default PageViewing Each Blog
To view each blog, we need to create another page.tsx in src/app/blogs/[slug]/ directory.
import { getPostDataBySlug, getSlugs } from "@/lib/utils"
export async function generateStaticParams() {
const postSlugs = getSlugs()
return postSlugs.map((post) => (
{ slug: post }
))
}
const Page = async ({ params }: { params: { slug: string } }) => {
const post = await getPostDataBySlug(params.slug)
return (
<div>{post.content}</div>
)
}
export default PageHere, the generateStaticParams function is used to stacically generate the routes at build time, enabling efficient navigation to each blog post.
Thank you for reading through this blog post. I hope you found the information helpful.