Integrating MDX in Next.js Site for Blogging

2024-03-21
....

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


Terminal
npm install next-mdx-remote

Example Blog Structure


Assuming your blogs are stored in the ~/data/blogs/ directory, here's how a sample blog file (first-blog.mdx) might look:

~/data/blogs/first-blog.mdx
---
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.

src/lib/utils.ts
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.

src/app/blogs/page.tsx
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 Page

Fetching 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.

src/lib/utils.ts
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

src/app/blogs/page.tsx
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 Page

Viewing Each Blog


To view each blog, we need to create another page.tsx in src/app/blogs/[slug]/ directory.

src/app/blogs/[slug]/page.tsx
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 Page

Here, 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.

© Shaunak 2024. All Rights Reserved.