You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 09:31:05