In the world of web development, Markdown has become a popular choice for writing content due to its simplicity and readability.
Next.js, a powerful React framework, provides an efficient way to transform these Markdown files into fully functional blog posts using the next-mdx-remote/rsc package.
This blog will guide you through the process of setting up a Next.js 14 application to convert Markdown files into dynamic blogs.
First, ensure you have Node.js installed on your system. Then, create a new Next.js project by running:
npx create-next-app@latest markdown-blog
cd markdown-blog
To handle Markdown files, you’ll need to install next-mdx-remote/rsc along with @types/mdx for handling types:
npm install next-mdx-remote@rsc
Store your Markdown files in a directory within your project, typically named content. Each file represents a blog post. For example:
/content
- my-first-post.mdx
- my-second-post.mdx
Now, we have to create a utility file to handle the loading and compiling of these Markdown files.
Let’s talk about the methods to be used to convert the Markdown to HTML.
The getPost method fetches and compiles a single blog post specified by its slug. It begins by calling the loadPost function, which constructs the file path using the provided slug and reads the content of the corresponding Markdown file from the filesystem. This content, which includes both the raw Markdown and any frontmatter metadata, is then passed to the compileMDX function from the next-mdx-remote/rsc package.
The compileMDX function is crucial as it converts the Markdown into MDX (Markdown for the component era), allowing React components to be embedded within the Markdown content. The method specifies components (like custom headers) and options (such as parsing frontmatter) to be used during compilation. For example, you can add your custom H1 component using the components props like this:
components: {
h1: (props) => <H1 {...props} />
},
The result is a fully parsed and ready-to-render MDX content object, which includes both the React components and any metadata defined in the front matter.
The getPosts method retrieves multiple blog posts based on specified criteria such as tags, sorting order, and pagination. It first reads all the filenames from the content directory, then uses these filenames to fetch and compile each post asynchronously using the getPost method. This results in an array of posts, each containing structured data including frontmatter and a slug.
Post-filtering is applied if tags are specified, filtering out posts that do not contain the specified tags. Sorting is also handled, allowing posts to be ordered by date either from newest to oldest or vice versa. Finally, pagination is implemented by slicing the array of posts according to the specified page number and limit, ensuring that only a subset of posts is returned at a time.
Together, these methods provide a robust backend mechanism for handling blog content in a Next.js application, leveraging the power of MDX to seamlessly integrate dynamic React components within static Markdown content.
Here’s a simplified version of what this might look like:
// lib/posts.js
import fs from 'fs'
import path from 'path'
import { compileMDX } from 'next-mdx-remote/rsc'
import H1 from '@/components/h1'
export function loadPost(slug) {
const filename = slug.endsWith('.mdx') ? slug : `${slug}.mdx`
return fs.readFileSync(
path.join(process.cwd(), 'content', filename)
)
}
export async function getPost(slug) {
const source = loadPost(slug)
return await compileMDX({
source,
components: {
h1: (props) => <H1 {...props} />
},
options: {
parseFrontmatter: true
}
})
}
export async function getPosts({
newest = true, page = 1, limit = 4, tags
} = {}) {
const files = fs.readdirSync(
path.join(
process.cwd(), 'content'
)
)
const posts = await Promise.all(
files.map(async filename => {
const { frontmatter } = await getPost(filename)
return {
frontmatter,
slug: filename.replace('.mdx', '')
}
})
)
let filteredPosts = posts
if (tags) {
filteredPosts = filteredPosts.filter(
post => post.frontmatter.tags.some(
tag => tags.includes(tag)
)
)
}
if (newest) {
// by the newest
filteredPosts.sort(
(a, b) => {
const dateA = new Date(a.frontmatter.date)
const dateB = new Date(b.frontmatter.date)
return dateB - dateA
}
)
} else {
filteredPosts.sort(
(a, b) => {
const dateA = new Date(a.frontmatter.date)
const dateB = new Date(b.frontmatter.date)
return dateA - dateB
}
)
}
const startIndex = (page - 1) * limit // 0
const endIndex = page * limit // 10
return {
posts: filteredPosts.slice(startIndex, endIndex),
pageCount: Math.ceil(filteredPosts.length / limit)
}
}
Now it’s time to check if the methods are cooked properly. We need to set up a dynamic route to handle individual blog posts. Create a file under pages/posts/[slug].js:
import { notFound } from 'next/navigation'
import { getPost as getPostNotCached, getPosts } from '@/lib/posts'
import { cache } from 'react'
const getPost = cache(
async (slug) => await getPostNotCached(slug)
)
export async function generateStaticParams() {
const {posts} = await getPosts({ limit: 1000 })
return posts.map(post => ({
slug: post.slug
}))
}
Here, the generateStaticParams function fetches a list of blog posts and generates static parameters for each post. It uses the getPosts function to retrieve up to 1000 posts. The function then maps over the array of posts, extracting the slug of each post to create an array of objects. Each object contains a slug property, which is used as a parameter for static site generation in Next.js applications. This is particularly useful for generating static paths in getStaticPaths for dynamic routes.
export async function generateMetadata({ params }) {
try {
const { frontmatter } = await getPost(params.slug)
return frontmatter
} catch (e) {
// handle error
}
}
The generateMetadata function fetches metadata for a blog post based on a slug. It takes an object containing params as an argument, which includes the slug of the post. The function attempts to retrieve the post using the getPost function and returns the frontmatter of the post, which typically contains metadata such as title, date, tags, etc. If the post cannot be fetched, the function handles the exception silently and does not return any metadata. This function is useful for generating SEO-friendly metadata for each blog post page.
Now it’s time to fetch the posts using the getPost method:
export default async function BlogPage({ params }) {
const post = await getPost(params.slug)
return (
<article className="prose dark:prose-invert">
…
{post.content}
</article>
)
}
In conclusion, by setting up your Next.js application with next-mdx-remote/rsc, you can easily transform Markdown files into dynamic and interactive blog posts. This approach not only simplifies content management by allowing you to write posts in Markdown but also enhances the functionality of your blog by enabling the use of React components directly within your content. It’s a powerful combination that makes your blog both easy to maintain and rich in features, perfect for developers looking to create a modern, scalable blogging platform.
How to Effectively Hire and Manage a Remote Team of Developers.
Download NowThe Mindbowser team's professionalism consistently impressed me. Their commitment to quality shone through in every aspect of the project. They truly went the extra mile, ensuring they understood our needs perfectly and were always willing to invest the time to...
CTO, New Day Therapeutics
I collaborated with Mindbowser for several years on a complex SaaS platform project. They took over a partially completed project and successfully transformed it into a fully functional and robust platform. Throughout the entire process, the quality of their work...
President, E.B. Carlson
Mindbowser and team are professional, talented and very responsive. They got us through a challenging situation with our IOT product successfully. They will be our go to dev team going forward.
Founder, Cascada
Amazing team to work with. Very responsive and very skilled in both front and backend engineering. Looking forward to our next project together.
Co-Founder, Emerge
The team is great to work with. Very professional, on task, and efficient.
Founder, PeriopMD
I can not express enough how pleased we are with the whole team. From the first call and meeting, they took our vision and ran with it. Communication was easy and everyone was flexible to our schedule. I’m excited to...
Founder, Seeke
Mindbowser has truly been foundational in my journey from concept to design and onto that final launch phase.
CEO, KickSnap
We had very close go live timeline and Mindbowser team got us live a month before.
CEO, BuyNow WorldWide
If you want a team of great developers, I recommend them for the next project. Â
Founder, Teach Reach
Mindbowser built both iOS and Android apps for Mindworks, that have stood the test of time. 5 years later they still function quite beautifully. Their team always met their objectives and I'm very happy with the end result. Thank you!
Founder, Mindworks
Mindbowser has delivered a much better quality product than our previous tech vendors. Our product is stable and passed Well Architected Framework Review from AWS.
CEO, PurpleAnt
I am happy to share that we got USD 10k in cloud credits courtesy of our friends at Mindbowser. Thank you Pravin and Ayush, this means a lot to us.
CTO, Shortlist
Mindbowser is one of the reasons that our app is successful. These guys have been a great team.
Founder & CEO, MangoMirror
Kudos for all your hard work and diligence on the Telehealth platform project. You made it possible.
CEO, ThriveHealth
Mindbowser helped us build an awesome iOS app to bring balance to people’s lives.
CEO, SMILINGMIND
They were a very responsive team! Extremely easy to communicate and work with!
Founder & CEO, TotTech
We’ve had very little-to-no hiccups at all—it’s been a really pleasurable experience.
Co-Founder, TEAM8s
Mindbowser was very helpful with explaining the development process and started quickly on the project.
Executive Director of Product Development, Innovation Lab
The greatest benefit we got from Mindbowser is the expertise. Their team has developed apps in all different industries with all types of social proofs.
Co-Founder, Vesica
Mindbowser is professional, efficient and thorough.Â
Consultant, XPRIZE
Very committed, they create beautiful apps and are very benevolent. They have brilliant Ideas.
Founder, S.T.A.R.S of Wellness
Mindbowser was great; they listened to us a lot and helped us hone in on the actual idea of the app. They had put together fantastic wireframes for us.
Co-Founder, Flat Earth
Ayush was responsive and paired me with the best team member possible, to complete my complex vision and project. Could not be happier.
Founder, Child Life On Call
The team from Mindbowser stayed on task, asked the right questions, and completed the required tasks in a timely fashion! Strong work team!
CEO, SDOH2Health LLC
Mindbowser was easy to work with and hit the ground running, immediately feeling like part of our team.
CEO, Stealth Startup
Mindbowser was an excellent partner in developing my fitness app. They were patient, attentive, & understood my business needs. The end product exceeded my expectations. Thrilled to share it globally.
Owner, Phalanx
Mindbowser's expertise in tech, process & mobile development made them our choice for our app. The team was dedicated to the process & delivered high-quality features on time. They also gave valuable industry advice. Highly recommend them for app development...
Co-Founder, Fox&Fork