NextJS中如何用同一MDX模板实现基于Frontmatter的可选路由?
实现方案
完全可以实现这个需求,核心思路是复用同一MDX渲染模板,根据MDX文件Frontmatter中的type字段,在路由层面生成对应路径。下面分NextJS两种路由系统分别说明:
一、Pages Router(NextJS 12及更早版本)
1. 抽离共用MDX渲染模板
先创建一个可复用的组件,统一处理MDX内容和Frontmatter渲染:
// components/MDXRenderer.js import { MDXRemote } from 'next-mdx-remote'; export default function MDXRenderer({ frontmatter, content }) { return ( <div className="mdx-container"> <h1>{frontmatter.title}</h1> <MDXRemote source={content} /> </div> ); }
2. 根路径动态路由(处理非post类型页面)
创建pages/[slug].js,负责生成/<slug>路径:
import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import MDXRenderer from '../components/MDXRenderer'; // MDX文件存放目录,可根据实际调整 const MDX_DIR = path.join(process.cwd(), 'content'); export async function getStaticPaths() { const mdxFiles = fs.readdirSync(MDX_DIR).filter(file => file.endsWith('.mdx')); // 筛选出type不为post的文件,生成根路径参数 const paths = mdxFiles.map(file => { const rawContent = fs.readFileSync(path.join(MDX_DIR, file), 'utf8'); const { data } = matter(rawContent); if (data.type !== 'post') { return { params: { slug: file.replace('.mdx', '') } }; } return null; }).filter(Boolean); return { paths, fallback: false }; } export async function getStaticProps({ params }) { const { slug } = params; const filePath = path.join(MDX_DIR, `${slug}.mdx`); const rawContent = fs.readFileSync(filePath, 'utf8'); const { data: frontmatter, content } = matter(rawContent); // 校验类型,防止非法访问 if (frontmatter.type === 'post') { return { notFound: true }; } return { props: { frontmatter, content } }; } export default function Page({ frontmatter, content }) { return <MDXRenderer frontmatter={frontmatter} content={content} />; }
3. 博客路径动态路由(处理post类型文章)
创建pages/blog/[slug].js,负责生成/blog/<slug>路径:
import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import MDXRenderer from '../../components/MDXRenderer'; const MDX_DIR = path.join(process.cwd(), 'content'); export async function getStaticPaths() { const mdxFiles = fs.readdirSync(MDX_DIR).filter(file => file.endsWith('.mdx')); // 筛选出type为post的文件,生成博客路径参数 const paths = mdxFiles.map(file => { const rawContent = fs.readFileSync(path.join(MDX_DIR, file), 'utf8'); const { data } = matter(rawContent); if (data.type === 'post') { return { params: { slug: file.replace('.mdx', '') } }; } return null; }).filter(Boolean); return { paths, fallback: false }; } export async function getStaticProps({ params }) { const { slug } = params; const filePath = path.join(MDX_DIR, `${slug}.mdx`); const rawContent = fs.readFileSync(filePath, 'utf8'); const { data: frontmatter, content } = matter(rawContent); // 校验类型,防止非法访问 if (frontmatter.type !== 'post') { return { notFound: true }; } return { props: { frontmatter, content } }; } export default function BlogPost({ frontmatter, content }) { return <MDXRenderer frontmatter={frontmatter} content={content} />; }
二、App Router(NextJS 13+版本)
1. 抽离共用MDX渲染模板
创建app/components/MDXRenderer.jsx:
import { MDXRemote } from 'next-mdx-remote/rsc'; export default function MDXRenderer({ frontmatter, content }) { return ( <div className="mdx-container"> <h1>{frontmatter.title}</h1> <MDXRemote source={content} /> </div> ); }
2. 根路径动态路由(处理非post类型页面)
创建app/[slug]/page.js:
import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import MDXRenderer from '../components/MDXRenderer'; const MDX_DIR = path.join(process.cwd(), 'content'); export async function generateStaticParams() { const mdxFiles = fs.readdirSync(MDX_DIR).filter(file => file.endsWith('.mdx')); return mdxFiles.map(file => { const rawContent = fs.readFileSync(path.join(MDX_DIR, file), 'utf8'); const { data } = matter(rawContent); if (data.type !== 'post') { return { slug: file.replace('.mdx', '') }; } return null; }).filter(Boolean); } export default async function Page({ params }) { const { slug } = params; const filePath = path.join(MDX_DIR, `${slug}.mdx`); const rawContent = fs.readFileSync(filePath, 'utf8'); const { data: frontmatter, content } = matter(rawContent); if (frontmatter.type === 'post') { return notFound(); } return <MDXRenderer frontmatter={frontmatter} content={content} />; }
3. 博客路径动态路由(处理post类型文章)
创建app/blog/[slug]/page.js:
import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import MDXRenderer from '../../components/MDXRenderer'; const MDX_DIR = path.join(process.cwd(), 'content'); export async function generateStaticParams() { const mdxFiles = fs.readdirSync(MDX_DIR).filter(file => file.endsWith('.mdx')); return mdxFiles.map(file => { const rawContent = fs.readFileSync(path.join(MDX_DIR, file), 'utf8'); const { data } = matter(rawContent); if (data.type === 'post') { return { slug: file.replace('.mdx', '') }; } return null; }).filter(Boolean); } export default async function BlogPost({ params }) { const { slug } = params; const filePath = path.join(MDX_DIR, `${slug}.mdx`); const rawContent = fs.readFileSync(filePath, 'utf8'); const { data: frontmatter, content } = matter(rawContent); if (frontmatter.type !== 'post') { return notFound(); } return <MDXRenderer frontmatter={frontmatter} content={content} />; }
补充说明
- 先安装依赖:
npm install gray-matter next-mdx-remote - MDX文件需包含
type字段,示例:--- title: "关于我" type: "page" --- 这里是页面内容... - 若MDX文件按类型分目录存放(如
content/posts和content/pages),可直接按目录筛选文件,无需解析Frontmatter,提升构建效率。
内容的提问来源于stack exchange,提问作者redstuck
相关产品推荐
相关产品推荐

