Rendering Markdown using MDX Remote renders
2/15/2025 12:00:00 AM
joe-jngigiExploring the challenges and solutions of rendering markdown content in a personal blog, integrating Frontmatter metadata, and optimizing components in Next.js.
I was struggling to render markdown content on my personal site, which led me to a solution that would allow me to display years of markdown writings as a blog. My goal was to satisfy my urge for a personal blog while also enhancing my portfolio. This article details the challenges I faced and the solutions I implemented.
What is this about?
This is about the lessons I’ve learned throughout this process. I even used an LLM — haha! I also pinned a post on my Twitter: Senior Prompt Engineer. But in the end, I achieved what I wanted:
- Styling the markdown components
- Solving server and client component errors (Still working on this one!)

Initially, I wrote the code like this when rendering a single MDX component before considering extracting Frontmatter content, which is essentially metadata:
import React from "react";
import { promises as FS } from "fs";
import path from "path";
import { compileMDX, MDXRemote } from "next-mdx-remote/rsc";
import {
Page,
Checklist,
ProjectHeader,
PreCode,
ProjectContent,
ProjectSidebar,
} from "@/src/components/mdx";
interface PostPageProps {
params: { blog: string };
}
const PostPage = async ({ params }: PostPageProps) => {
const content = await FS.readFile(
path.join(process.cwd(), "/docs", "blog.md"),
"utf-8"
);
console.log(content);
if (!content) {
return <div>Welcome to my blog!</div>;
}
return (
<Page title="Good Life">
<MDXRemote
source={content}
components={{
Checklist,
ProjectContent,
ProjectSidebar,
ProjectHeader,
PreCode,
}}
/>
<footer>footer</footer>
</Page>
);
};
export default PostPage;
Fetching the Files
- File System Import & Async Function
In the above code, I import the fs
, which allows me to use the asynchronous file system operation. Asynchronous file operations ensures that the file system reading the files does not block the event loop. This means that while the file is being read, the server can continue processing other tasks, or handle other incoming requests.
Why are the files fetched asynchrously?
Node.js is built around an event-driven, non-blocking architecture. This means that the application can handle many operations at the same time without waiting for each one to finish, leading to efficient and scalable performance.
In the path.join()
, we pass the process.cwd()
argument so we can create an absolute path to the file; that means it joins the cwd
-> current working directory with the target folder and file. Using the readFile
function, we can read the content of the target file, as a UTF-8 encoded string
const content = await FS.readFile(
path.join(process.cwd(), "/docs", "blog.md"),
"utf-8"
);
check out the the process of constructing the file path. The reference breaks down the process.
The New Approach Using compileMDX()
The compileMDX Function is the simplest way I found to access Frontmatter metadata, which is crucial for structuring my blog posts.
By using compileMDX()
, we eliminate the need for the <MDXRemote>
component, as we can now directly render the data returned by the function.
const data = await compileMDX({
source: content,
options: {
parseFrontmatter: true,
},
components: {
Checklist,
ProjectContent,
ProjectSidebar,
ProjectHeader,
PreCode,
},
});
// Return statement
return (
<Page title="Good Life">
{data.content}
<footer>footer</footer>
</Page>
);