2026 年 Next.js 内容站的 MDX 选型(next-mdx-remote 已归档)

next-mdx-remote 已于 2026 年 4 月归档,Contentlayer 早已停维护。本文给出 Next.js 16 内容站现在真正能用的几条路——RSC 上的核心 MDX、next-mdx-remote-client、mdx-bundler、contentlayer2、Velite,从 DX、构建时间、锁定风险逐一对比。

这篇对比以前推荐的两个库,如今都是死路。Contentlayer 在 2023 年中失去维护者,从没支持过 React 19。而 next-mdx-remote 本身也在 2026 年 4 月 9 日被归档、变成只读——它自己的 README 现在都让你去别处。所以 2026 年真正要问的,已经不是「选 MDX bundler 还是 next-mdx-remote」,而是:在 Next.js 16 上、内容文件放在仓库里,哪条 MDX 路线还活着、够快,还能把大家怀念的 Contentlayer 那套类型安全 DX 补回来?

本文从决定一个内容站成败的几个点对比五个还在维护的方案:维护风险、构建时间、frontmatter 类型安全、产物体积。

一句话总结

  • Next.js 16 上做新的文件系统内容站,多数情况下根本不需要任何 MDX 封装库。核心 MDX 库(@mdx-js/mdx)在 React Server Component 里就能正常编译。这正是已归档的 next-mdx-remote README 现在推荐的做法。
  • 想要一层带错误处理和 frontmatter 辅助函数的薄封装,就用 next-mdx-remote-client(v2.x,仍在维护,支持 React 19 和 RSC)——它是被归档那个包的直接继任者。
  • 想把 Contentlayer「从 frontmatter 生成类型」的 DX 当成一个有人维护的依赖拿回来?用 Velite(构建时、Zod、框架无关)。注意:它的 webpack 插件在 Turbopack 下跑不起来。
  • 只有当 MDX 来自 CMS/数据库、每篇文章 import 不同组件时,才用 mdx-bundler。对普通内容它的产物大约比 next-mdx-remote 大 4 倍,给仓库文件用属于杀鸡用牛刀。
  • 只有在迁移已有 Contentlayer 项目、想要近乎无缝升级时,才考虑 contentlayer2timlrx 维护的 fork)。

各方案速览(截至 2026 年 6 月)

方案状态适合frontmatter 类型主要代价
RSC 上的核心 @mdx-js/mdx活跃新的文件系统站自己写 Zod自己写约 80 行
next-mdx-remote-client v2.1.11活跃(2026-05)薄封装 + 错误处理自己写 Zod多一个依赖
Velite活跃想拿回 Contentlayer DX由 Zod 生成webpack 插件在 Turbopack 下挂
mdx-bundler活跃MDX 来自 CMS/数据库自动解析产物约大 4 倍
contentlayer2 / next-contentlayer2社区 fork迁移老 Contentlayer 项目自动生成社区小,fork 有风险
next-mdx-remote2026-04-09 已归档不再适合新项目不再有修复

上表的版本和日期均为 2026 年 6 月的情况,动手前请到 npm 再确认一遍。

旧的默认选项为什么崩了

两个人气选手都是同样的死法:先是只有一个维护者,然后连维护者都没了。

  • Contentlayer:最后一次有意义的发布在 2023 年中,那时 Next.js 14 都还没出。它能从 frontmatter 生成 TypeScript 类型、还跨构建缓存解析后的 MDX——DX 确实好用——但它从没跟上 App Router / React 19 这一代。新装就会撞上 peer-dependency 和构建失败。
  • next-mdx-remote:HashiCorp 在 2026 年 4 月 9 日归档了仓库,最后一版是 v6.0.0(2026 年 2 月)。代码还能跑,但不会再有修复,README 明确建议改用 next-mdx-remote-clientmdx-bundler,或者干脆在 RSC 上用核心 MDX 库。

对一个想用好几年的内容站来说,教训很清楚:优先选依赖面最小的方案,这样一个仓库被归档也困不住你。

方案一:在 Server Component 里用核心 MDX(新的默认)

要干的事很小。一个 App Router 的 server component 读文件、跑官方 MDX 编译器、渲染结果。不需要任何第三方封装。

// app/articles/[slug]/page.tsx
import { compile, run } from '@mdx-js/mdx';
import * as runtime from 'react/jsx-runtime';
import matter from 'gray-matter';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { z } from 'zod';
import { mdxComponents } from '@/components/mdx';

const FrontmatterSchema = z.object({
  title: z.string(),
  description: z.string(),
  publishedAt: z.coerce.date(),
  tags: z.array(z.string()),
});

export default async function ArticlePage(
  { params }: { params: Promise<{ slug: string }> },
) {
  const { slug } = await params;
  const raw = await readFile(
    path.join(process.cwd(), 'content', `${slug}.mdx`),
    'utf8',
  );
  const { content, data } = matter(raw);
  const meta = FrontmatterSchema.parse(data);

  const compiled = await compile(content, { outputFormat: 'function-body' });
  const { default: MDXContent } = await run(compiled, runtime);

  return (
    <article>
      <h1>{meta.title}</h1>
      <time>{meta.publishedAt.toISOString()}</time>
      <MDXContent components={mdxComponents} />
    </article>
  );
}

generateStaticParams 读内容目录;generateMetadata 复用同一个 Zod schema。整条 pipeline 大约 80 行完全归你掌控的代码,零归档依赖。注意 Next.js 15 和 16 里 params 是个 Promise——要 await

方案二:next-mdx-remote-client(薄封装)

如果你不想手接 compile/run,next-mdx-remote 仍在维护的继任者是 next-mdx-remote-client(截至 2026 年 5 月底为 v2.1.11;v2.x 面向 React 19)。它封装 @mdx-js/mdx,支持 MDX v3,把服务端(rsc)和客户端(csr)入口完全隔离,还加了一层内部错误处理和「不编译就读 frontmatter」的辅助函数。

// app/articles/[slug]/page.tsx
import { MDXRemote } from 'next-mdx-remote-client/rsc';
import { mdxComponents } from '@/components/mdx';

export default async function ArticlePage(/* ...读 raw、校验... */) {
  return (
    <MDXRemote
      source={raw}
      components={mdxComponents}
      options={{ parseFrontmatter: true }}
    />
  );
}

MDXRemote 是异步 server component,必须在服务端渲染。对多数文件系统站来说,这和方案一是同一件事,只是 API 更友好、自带错误状态。

方案三:内容在仓库外时用 mdx-bundler

mdx-bundler(Kent C. Dodds 出品)把 MDX 编译成自包含的 JS 字符串,运行时再 eval。它的优势出现在 MDX 来自数据库或 headless CMS、每篇文章 import 的组件各不相同时——bundler 把每次编译相互隔离。

// MDX 内容来自远端
import { bundleMDX } from 'mdx-bundler';

const { code, frontmatter } = await bundleMDX({
  source: mdxStringFromCMS,
  cwd: process.cwd(),
});

代价是:对普通内容,mdx-bundler 的产物大约是 next-mdx-remote 的 4 倍,因为它给每篇文章塞了一份 esbuild 打出来的 bundle。仓库里的 MDX 用不上这层隔离,所以请改用方案一或方案二。

把 Contentlayer 的 DX 补回来(用 Velite,或自己搭)

大家舍不得 Contentlayer 的三件事,2026 年每件都有干净的答案。

  1. Frontmatter 类型生成。 自己写:z.infer<typeof FrontmatterSchema>,10 行 helper。或者交给 Velite 自动生成——它把你的 content/ 文件夹从 Zod schema 变成一个带类型的数据层(JSON + .d.ts),框架无关。Velite 编译普通内容是毫秒级的,但它的 VeliteWebpackPlugin 在开启 Turbopack 时不工作——那种情况下把 velite 当成单独一步跑。
  2. 计算字段。 Contentlayer 从文件名推出 urlSlug。同样的模式:在 lib/content.ts 写个函数,把原始 frontmatter 映射成更丰富的对象。Velite 在 schema 里就直接支持计算字段。
  3. 跨页面缓存解析结果。 Contentlayer 在构建时复用解析后的 MDX。替代方案是按文件 mtime 做内存缓存,或者干脆不做——Next.js 16 的缓存(含 Cache Components)对大多数站已经够用。

自己手搭一份最小的 lib/content.ts 大概 150-200 行,DX 不输 Contentlayer,又不用扛一份没维护的依赖。Velite 让你少写些代码,代价是多一个(但仍活跃的)受管依赖。

容易踩的坑

  • 服务端编译时 import 了仅限客户端的 MDX 组件,导致 hydration 不匹配。把交互组件放进 'use client' 文件,再通过 components map 传进去。
  • 忘了写 generateStaticParams 每篇文章变动态路由,静态 SEO 和 Lighthouse 全掉;在 Next.js 16 上还会丢掉预渲染。
  • 在 client component 里跑 MDX 编译。 RSC 的意义没了,运行时 bundle 还爆。
  • 只在运行时校验 frontmatter。 schema 一出错就在生产挂。build 时也跑同一个 Zod 校验,最好接进 CI。
  • 忘了 params 现在是 Promise。 Next.js 15 起必须 await params(和 searchParams)——从老教程抄来的代码会直接报错。
  • 残留没用的死依赖。 迁完执行 npm uninstall contentlayer next-contentlayer next-mdx-remote,清掉 peer-dep 报警、瘦一下 lockfile。

FAQ

  • Contentlayer 是不是凉了?:原始仓库从 2023 年起就没维护、从没支持 React 19。社区有个 fork contentlayer2 / next-contentlayer2timlrx 维护),API 兼容,能在 Next.js 15、React 19 上跑。只在迁移已有 Contentlayer 项目时才用它,而且上车前先看仓库活跃度。
  • 等等,next-mdx-remote 没了?:HashiCorp 在 2026 年 4 月 9 日归档了它。v6.0.0 还能装能跑,但不再有修复。新项目请用 next-mdx-remote-client,或直接用核心 MDX 库编译。
  • next-mdx-remote 和 next-mdx-remote-client 有什么区别?next-mdx-remote-clientipikuka 出品)是仍在维护的 fork。它支持 MDX v3、React 19、完全分离的 rsc/csr 入口、内置错误处理,还有只读 frontmatter 的辅助函数。
  • 直接用官方 @next/mdx 行吗?:当每个 MDX 文件就是一个真正的路由页面(app/articles/foo/page.mdx)时行。一旦需要从内容目录做动态路由就崩——而多数内容站都需要。
  • 这些都支持 remark / rehype 插件吗?:都支持。在 options 里传 remarkPluginsrehypePlugins 即可——核心 MDX、next-mdx-remote-client、mdx-bundler、Velite 都会转发它们。
  • Astro 呢?:Astro 原生支持 MDX,content collections 和 Zod schema 内置——本站就是这么用的。想要 Contentlayer 的 DX 又不想踩 Contentlayer 的坑、又不需要 React 应用壳,Astro 是维护成本更低的那条路。

相关阅读

标签: #独立开发 #Next.js #MDX #内容 #tooling