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, theparams.slug
is used to fetch the specific post's content. TheMDXRemote
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:
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.