November 17, 2023
O. Wolfson
See the live demo and GitHub repository for this project.
Creating a static MDX blog in Next.js 14 involves several steps, including setting up your Next.js environment, creating MDX files, and configuring your pages to render these files. Below is a comprehensive tutorial to guide you through this process:
Initialize the Next.js Project:
npx create-next-app@latest my-mdx-blog to create a new Next.js project.cd my-mdx-blog.Install Dependencies:
fs, path, gray-matter, and next-mdx-remote using npm or yarn. For example:
npm install gray-matter next-mdx-remote
Create a Blog Directory:
blogs where you'll store your MDX files.Write MDX Files:
blogs directory. These files can contain JSX and markdown content.Example MDX file (sample.mdx):
markdown---
title: "Sample MDX File"
date: "2023-11-17"
description: "This is a sample MDX file."
---
# This is a sample MDX file
This is a sample MDX file. It contains JSX and markdown content.

```javascript
console.log("Hello, world!");
```
<YouTube videoId="dQw4w9WgXcQ" />
Import Necessary Modules:
Home component will import modules like fs, path, matter, and Next.js components.Read and Process MDX Files:
fs to read the files from the blogs directory.gray-matter to extract front matter (metadata) and content.Render Blog List:
javascriptimport fs from "fs";
import path from "path";
import matter from "gray-matter";
import Link from "next/link";
export default function Home() {
const blogDirectory = path.join(process.cwd(), "blogs");
const fileNames = fs.readdirSync(blogDirectory);
const blogs = fileNames.map((fileName) => {
const slug = fileName.replace(".mdx", "");
const fullPath = path.join(blogDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, "utf8");
const { data: frontMatter } = matter(fileContents);
const date = new Date(frontMatter.date);
// Format the date to a readable string format
// For example, "October 1, 2021"
const formattedDate = date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
return {
slug,
formattedDate,
: frontMatter,
};
});
(
);
}
Dynamic Routes:
app/blog/[slug]/page.tsx, and insert the code below.javascriptimport fs from "fs";
import path from "path";
import matter from "gray-matter";
import { MDXRemote } from "next-mdx-remote/rsc";
import YouTube from "@/components/mdx/youtube";
import Code from "@/components/mdx/code-component/code";
// Content for these pages will be fetched with getPost function.
// This function is called at build time.
// It returns the content of the post with the matching slug.
// It also returns the slug itself, which Next.js will use to determine which page to render at build time.
//For example, { props: { slug: "my-first-post", content: "..." } }
async function getPost({ slug }: { slug: string }) {
const markdownFile = fs.readFileSync(
path.join("blogs", slug + ".mdx"),
"utf-8"
);
const { data: frontMatter, content } = matter(markdownFile);
return {
frontMatter,
slug,
content,
};
}
// generateStaticParams generates static paths for blog posts.
// This function is called at build time.
// It returns an array of possible values for slug.
// For example, [{ params: { slug: "my-first-post" } }, { params: { slug: "my-second-post" } }]
export async function generateStaticParams() {
files = fs.(path.());
params = files.( ({
: filename.(, ),
}));
params;
}
() {
props = (params);
components = {
: ,
,
};
(
);
}
Get Static Props:
getPost and generateStaticParams functions to fetch the content for each MDX file based on the slug.fs to read the MDX file content and gray-matter to parse it.Render MDX Content:
MDXRemote from next-mdx-remote to render the MDX content on the page.Code and YouTube for MDX rendering.Styling:
MDX Components:
YouTube and Code to enhance your MDX content.YouTube component:
javascriptimport React from "react";
const YouTube = ({ videoId }: { videoId: string }) => {
const videoSrc = `https://www.youtube.com/embed/${videoId}`;
return (
<iframe
width="560"
height="315"
src={videoSrc}
allow="autoplay; encrypted-media"
allowFullScreen
></iframe>
);
};
export default YouTube;
Code component:
javascriptimport React from "react";
import AdminBar from "@/components/mdx/code-component/admin";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { nightOwl } from "react-syntax-highlighter/dist/cjs/styles/prism";
const Code = (props: any) => {
const codeContent =
typeof props.children === "string"
? props.children
: props.children.props.children;
const className = props.children.props.className || "";
const matches = className.match(/language-(?<lang>.*)/);
const language = matches?.groups?.lang || "";
return (
<div className="text-sm flex flex-col gap-0">
<AdminBar code={codeContent} language={language} />
<SyntaxHighlighter
className="rounded-lg"
style={nightOwl}
language={language}
>
{codeContent}
);
};
;
MDXRemote to render them within your MDX content.javascript<MDXRemote source={props.content} components={components} />
Choose a Host:
Deploy:
Continuous Deployment:
By following these steps, you will have a static MDX blog running on Next.js 14. The key is to effectively manage your MDX files and ensure they are rendered correctly on your Next.js pages. The combination of MDX for content and Next.js for rendering provides a powerful and flexible blogging platform.