javascript 64 lines · 7 steps

Static MDX blog pages in Next.js

A Next.js App Router page reads Markdown files at build time and renders them as MDX with plugins.

Explained by highlit
1import fs from 'node:fs/promises';
2import path from 'node:path';
3import { notFound } from 'next/navigation';
4import matter from 'gray-matter';
5import { MDXRemote } from 'next-mdx-remote/rsc';
6import remarkGfm from 'remark-gfm';
7import rehypeSlug from 'rehype-slug';
8 
9const POSTS_DIR = path.join(process.cwd(), 'content', 'blog');
10 
11async function getPost(slug) {
12 try {
13 const raw = await fs.readFile(path.join(POSTS_DIR, `${slug}.md`), 'utf8');
14 const { content, data } = matter(raw);
15 return { content, frontmatter: data };
16 } catch {
17 return null;
18 }
19}
20 
21export async function generateStaticParams() {
22 const files = await fs.readdir(POSTS_DIR);
23 return files
24 .filter((file) => file.endsWith('.md'))
25 .map((file) => ({ slug: file.replace(/\.md$/, '') }));
26}
27 
28export async function generateMetadata({ params }) {
29 const { slug } = await params;
30 const post = await getPost(slug);
31 if (!post) return {};
32 return {
33 title: post.frontmatter.title,
34 description: post.frontmatter.excerpt,
35 };
36}
37 
38export default async function BlogPostPage({ params }) {
39 const { slug } = await params;
40 const post = await getPost(slug);
41 if (!post) notFound();
42 
43 return (
44 <article className="prose mx-auto py-12">
45 <h1>{post.frontmatter.title}</h1>
46 <time dateTime={post.frontmatter.date}>
47 {new Date(post.frontmatter.date).toLocaleDateString('en-US', {
48 year: 'numeric',
49 month: 'long',
50 day: 'numeric',
51 })}
52 </time>
53 <MDXRemote
54 source={post.content}
55 options={{
56 mdxOptions: {
57 remarkPlugins: [remarkGfm],
58 rehypePlugins: [rehypeSlug],
59 },
60 }}
61 />
62 </article>
63 );
64}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Reading content from the filesystem in a Server Component lets you pre-render pages at build time with no runtime database.
  2. 2generateStaticParams and generateMetadata let one dynamic route file produce many fully static, SEO-ready pages.
  3. 3MDXRemote's rsc entry renders MDX on the server, so remark and rehype plugins run without shipping the parser to the browser.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Static MDX blog pages in Next.js — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code