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

Next.js 13 App Router搭建简易博客:MDX方案困惑求助

Next.js 13.4 App Router 本地MDX博客实现方案

一、首页:无额外依赖提取MDX元数据

Next.js 13+ App Router允许在服务器组件中直接用fs/promises和path模块读取文件,无需引入gray-matter。MDX的frontmatter是文件顶部的YAML块,可通过正则匹配手动提取:

  1. 在app/blog/page.tsx(服务器组件)中遍历content/blog目录下的MDX文件:
import fs from 'fs/promises';
import path from 'path';

export default async function BlogHome() {
  const postsDir = path.join(process.cwd(), 'content/blog');
  const fileNames = await fs.readdir(postsDir);

  const posts = await Promise.all(
    fileNames.map(async (fileName) => {
      const filePath = path.join(postsDir, fileName);
      const fileContent = await fs.readFile(filePath, 'utf8');
      
      // 正则匹配提取frontmatter
      const frontmatterMatch = fileContent.match(/^---\n([\s\S]*?)\n---/);
      if (!frontmatterMatch) return null;

      const frontmatterStr = frontmatterMatch[1];
      // 手动解析简单YAML结构
      const frontmatter = frontmatterStr.split('\n').reduce((acc, line) => {
        const [key, ...valueParts] = line.split(': ');
        if (key) acc[key.trim()] = valueParts.join(': ').trim();
        return acc;
      }, {} as { title: string; date: string; slug: string });

      return {
        ...frontmatter,
        slug: fileName.replace('.mdx', '')
      };
    })
  );

  // 过滤无效数据并按日期倒序排序
  const validPosts = posts.filter(Boolean).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());

  return (
    <div>
      <h1>博客首页</h1>
      <ul>
        {validPosts.map((post) => (
          <li key={post.slug}>
            <a href={`/blog/${post.slug}`}>{post.title}</a>
            <p>{post.date}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

若frontmatter有复杂结构(如数组、嵌套对象),再考虑引入轻量的yaml包,简单场景下手动解析足够满足需求。

二、博客详情页:动态slug渲染MDX

结合@next/mdx和动态路由可实现动态slug场景的MDX渲染:

  1. 安装必要依赖:
npm install @next/mdx @mdx-js/loader @mdx-js/react next-mdx-remote
  1. 在next.config.js中配置MDX支持:
/** @type {import('next').NextConfig} */
const nextConfig = {
  pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
};

const withMDX = require('@next/mdx')();
module.exports = withMDX(nextConfig);
  1. 创建动态路由app/blog/[slug]/page.tsx,读取并渲染对应MDX文件:
import fs from 'fs/promises';
import path from 'path';
import { MDXRemote } from 'next-mdx-remote/rsc';

type Props = {
  params: { slug: string };
};

export default async function BlogPost({ params }: Props) {
  const { slug } = params;
  const postsDir = path.join(process.cwd(), 'content/blog');
  const filePath = path.join(postsDir, `${slug}.mdx`);

  try {
    const fileContent = await fs.readFile(filePath, 'utf8');
    
    // 分离frontmatter与MDX内容
    const [_, frontmatterStr, mdxContent] = fileContent.split(/^---\n([\s\S]*?)\n---\n/);
    const frontmatter = frontmatterStr.split('\n').reduce((acc, line) => {
      const [key, ...valueParts] = line.split(': ');
      if (key) acc[key.trim()] = valueParts.join(': ').trim();
      return acc;
    }, {} as { title: string; date: string });

    return (
      <div className="max-w-3xl mx-auto p-4">
        <h1>{frontmatter.title}</h1>
        <p className="text-gray-500">{frontmatter.date}</p>
        <div className="mt-6 prose">
          <MDXRemote source={mdxContent} />
        </div>
      </div>
    );
  } catch (err) {
    return <div>文章不存在</div>;
  }
}

// 生成静态路径,构建时预渲染所有文章
export async function generateStaticParams() {
  const postsDir = path.join(process.cwd(), 'content/blog');
  const fileNames = await fs.readdir(postsDir);
  
  return fileNames.map((fileName) => ({
    slug: fileName.replace('.mdx', ''),
  }));
}

用next-mdx-remote/rsc的MDXRemote组件可在服务器组件中动态渲染MDX内容,完美适配动态slug场景。

三、统一布局实现

在app/blog/layout.tsx中定义博客板块的统一布局:

export default function BlogLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="min-h-screen bg-gray-50">
      <header className="bg-white shadow-sm">
        <div className="max-w-7xl mx-auto px-4 py-4">
          <a href="/blog" className="text-xl font-bold">我的博客</a>
        </div>
      </header>
      <main className="max-w-7xl mx-auto px-4 py-8">
        {children}
      </main>
    </div>
  );
}

这套方案满足:

  • 本地MDX文件作为文章源,编辑便捷
  • 首页自动提取所有文章元数据并排序展示
  • 动态slug渲染对应文章,布局统一
  • 构建时预渲染所有文章,适配低流量场景

内容的提问来源于stack exchange,提问作者Dave Feldman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 11:40:27