How I Implemented My Blog Section Using MDX, Next.js, and React

Why Start Writing a Blog?

A few months ago, I pondered this question: Why should I write a blog? Initially, I hesitated. As I faced various coding challenges, I sought solutions. I often turned to ChatGPT, Google, and Stack Overflow.

Reading others' experiences helped me resolve my issues. It provided insights into solving similar problems. That's when I realized I could share my journey. Writing could help others and sharpen my skills.

Eventually, I decided to add a blog section. You may wonder, "Why not use a different platform?" There’s something special about your own space. It feels personal, rewarding, and fulfilling. 😄

How I Implemented the Blog Section

When implementing the blog, I noticed diverse styles. After researching, I discovered integrating Markdown (MDX) is possible. I drew inspiration from several developers and resources. Ultimately, I chose to implement MDX using Next.js.

Tech Stack

The core technologies I used for this blog section were:

  • Next.js
  • React
  • MDX (Markdown with JSX)

Now, let me walk you through the setup process.

Step 1: Install Dependencies

First, I installed the necessary packages to work with MDX in Next.js. You can do this with the following command:

npm install next-mdx-remote @next/mdx

Step 2: Configure MDX with Next.js

Next, I set up MDX in my Next.js configuration. Here's how you can configure it:

import createMDX from '@next/mdx';

const withMDX = createMDX({
  // Add any MDX options here if needed
});

/** @type {import('next').NextConfig} */
const nextConfig = {
  pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
  // Add any other Next.js config options here
};

export default withMDX(nextConfig);

This configuration allows Next.js to recognize .mdx files and use them as pages.

Step 3: Define Custom Blog Styles

To style the blog content, I created a mdxComponents.tsx file inside the components folder. In this file, I defined custom styles for various elements like headers, paragraphs, images, and more.

Here's an example of how the MDXComponents object looks:

import React from 'react';
import Link from 'next/link';
import Image from 'next/image';

const MDXComponents = {
  h1: ({ children, ...props }) => (
    <h1 className="text-3xl font-bold mt-8 mb-4" {...props}>{children}</h1>
  ),
  p: ({ children, ...props }) => (
    <p className="text-lg leading-relaxed my-4" {...props}>{children}</p>
  ),
  a: ({ href, children, ...props }) => {
    if (href?.startsWith('/')) {
      return (
        <Link href={href} className="text-blue-600 hover:underline" {...props}>
          {children}
        </Link>
      );
    }
    return (
      <a href={href} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline" {...props}>
        {children}
      </a>
    );
  },
  img: ({ src, alt, width, height, ...props }) => (
    <div className="my-8">
      <Image
        src={src || ''}
        alt={alt || ''}
        width={width || 800}
        height={height || 400}
        className="rounded shadow-lg"
        {...props}
      />
    </div>
  ),
  pre: ({ children, ...props }) => (
    <pre className="bg-gray-800 text-white p-4 rounded-lg overflow-x-auto" {...props}>{children}</pre>
  ),
};

export default MDXComponents;

In this code, I've styled h1, p, a, img, and pre elements to look visually appealing while maintaining responsiveness across different screen sizes.

Step 4: Using MDX Components in Blog Posts

Once the components were ready, I created a folder named blog inside the Next.js app directory. Inside the blog folder, I added a [slug] directory with a page.tsx file for dynamically loading blog posts.

To store my posts, I created a posts folder at the root of the project. Each post is written in .mdx format, allowing me to write in Markdown while incorporating JSX components.

For example, a blog post might look like this:

---
title: "How Your Code Runs on Platforms Like LeetCode"
date: "2024-09-14"
excerpt: "Explore the backend processes of coding platforms like LeetCode."
---

# How Your Code Runs on Platforms Like LeetCode

Have you ever wondered what happens when you hit "Run" or "Submit" on coding platforms like LeetCode? It's an interesting process involving several backend steps to ensure your code is executed correctly and efficiently. Let's break it down in simple terms.

In the post, I can use the MDXComponents styles for headings, paragraphs, and more. Plus, since I'm using MDX, I can even embed React components directly into the post!

Step 5: Loading Blog Content Dynamically

In this step, I set up dynamic routing to load each blog post by its slug. Using Next.js' app router, I structured the folder as follows:

  • Dynamic Route Structure:
    • pages/blog/[slug]/page.tsx: Handles dynamic blog content.
    • posts/: Contains individual blog posts in .mdx format.

Dynamic Blog Post Component

To load the content of each blog post dynamically based on its slug, I created the following code in pages/blog/[slug]/page.tsx:

import { getPostBySlug, getAllPosts } from '@/lib/mdx'
import { MDXRemote } from 'next-mdx-remote/rsc'
import MDXComponents from '@/components/MDXComponents'

// Generates static parameters for dynamic routes
export async function generateStaticParams() {
  const posts = await getAllPosts() // Ensure to await for async data fetching
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

// The BlogPost component fetches and displays the post content
export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug) // Await the post content retrieval

  return (
    <article className="prose prose-lg max-w-none">
      <MDXRemote source={post.content} components={MDXComponents} />
    </article>
  )
}
  • Static Params Generation: The generateStaticParams function fetches all posts and maps their slugs, which allows Next.js to generate static pages for each post at build time.
  • Dynamic Component: In the BlogPost component, the params.slug is used to fetch the specific post's content. The MDXRemote component is then used to render the content with the defined MDX components.

Blog Post Home Page

To facilitate easy navigation for visitors, I created a homepage that lists all blog posts with their titles, dates, and excerpts. Here's how the code for the homepage looks:

import Link from 'next/link'
import { getAllPosts } from '@/lib/mdx'

// Home component that lists all blog posts
export default async function Home() {
  const posts = await getAllPosts() // Await fetching all posts

  return (
    <div className="min-h-screen px-4 py-12 sm:px-6 lg:px-8">
      <div className="max-w-3xl mx-auto">
        <h1 className="text-3xl font-mono mb-12 text-center">Read My Blogs</h1>
        <ul className="space-y-8">
          {posts.map((post) => (
            <li key={post.slug} className="border-l-4 border-green-300 pl-6 transition-all hover:border-green-400 rounded-r-lg shadow-md">
              <Link href={`/blog/${post.slug}`} className="group block">
                <article>
                  <h2 className="text-xl font-semibold group-hover:text-green-400 transition-colors mb-2">{post.metadata.title}</h2>
                  <time dateTime={post.metadata.date} className="text-gray-400 text-sm block mb-2">
                    {new Date(post.metadata.date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}
                  </time>
                  <p className="text-gray-600 group-hover:text-gray-500 transition-colors">{post.metadata.excerpt}</p>
                </article>
              </Link>
            </li>
          ))}
        </ul>
      </div>
    </div>
  )
}
  • Post Fetching: The getAllPosts() function retrieves all blog post metadata asynchronously.
  • Mapping Posts: Each post is rendered as a list item. The Link component creates a navigable link to the corresponding blog post using its slug.

Useful Resources and Custom MDX Components

While implementing my blog, I found several valuable resources and tools that you might find helpful:

  1. Josh Comeau's Blog Implementation
  2. The official @next/mdx package
  3. MDX Docs

Conclusion

Creating a blog section with MDX, Next.js, and React was both a learning experience and a lot of fun. I now have a fully functioning blog with dynamic content, custom styles, and support for Markdown and React components. Plus, I have full control over my content and design!

If you're thinking of starting a blog, I'd highly recommend this approach. It allows for flexibility and customization, which is perfect for developers who want to build their own platform.

Feel free to reach out if you have any questions or want to share your experiences.