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.