我如何用 MDX 和 Next.js 构建完全自定义博客

发布: (2026年4月24日 GMT+8 18:33)
7 分钟阅读
原文: Dev.to

Source: Dev.to

小小的背景故事

我一直想把我觉得有趣的东西——我学到的东西、让我好奇的话题、经验教训、教育内容,或者只是一些搞笑的随机事物——发布出来。我对自己说,如果真的要做的话,就在 我的个人博客 上发布。

为什么是个人博客?

目前的平台在自定义和功能方面受到很大限制。有些平台甚至需要订阅才能使用某些功能(甚至阅读文章)。我想要完全掌控内容的呈现方式:设计、布局、字体、配色方案等。

Source:

日常工作

我最近大学毕业,幸运地在毕业后直接获得了工作机会。工作几个月后,我必须适应岗位的高强度要求,尤其是在试用期结束后,需要了解系统中极其复杂的部分。

这让我把写博客的想法暂时搁置在脑后。现在我对自己的角色更加熟悉,能够更好地应对压力和挑战,并且对自己的职责有了更清晰的认识,我终于可以回到那个未完成的博客构想上(写博客应该不难,对吧?)。

注意: 我在工作中做了很多很酷的事,想和大家分享,但那是另一个故事,留待以后再说。

我仍未达到我对这个应用设想的“完全自定义”水平。虽然有很多想法,但因为一直拖延构建,导致我失去了方向。现在在创建了一个简易版本后,我可以重新聚焦于最初的愿景。

从学生转变为全职员工本身就值得写一篇帖子——也许以后再写。

现在,让我们稍微技术一点。


Tech Stack

该博客是一个 monorepo 的一部分,monorepo 还托管了我的作品集(是的,有点多余)。它使用 Next.js 构建,并使用 MDX 来编写博客文章。文章以 Markdown 编写,并通过 remark 编译为 React 组件。

  • Styling: 使用 Tailwind CSS,并配合 @tailwindcss/typography 插件来处理标题、段落、列表等。
  • MDX: 系统的核心——没有它,要让博客上线运行将需要更多的工作。衷心感谢这款优秀工具的贡献者们。
  • Deployment: Vercel.

数据库(或缺失)

没有 使用数据库来存储博客文章的元数据(绝对不是因为想省钱)。有人可能会说 SQLite 或免费托管的数据库是合适的选择,但我不想让项目变得过于复杂;我只想把它从“愿望清单”里搬出来。

于是,我编写了一个 自定义脚本,它:

  1. 遍历 MDX 文件。
  2. 使用 gray‑matter 提取 front‑matter。
  3. 将收集到的数据保存到一个 TypeScript 文件中。

生成的文件在构建时被应用程序导入,因此不需要在运行时进行繁重的处理(比如读取文件)。数据只有在我重新构建应用时才会改变,这种做法是合理的。

生成对象的示例

// THIS FILE IS AUTO‑GENERATED BY compile‑mdx‑data SCRIPT, DO NOT EDIT

export const blogPostsObject: Record = {
  "mdx-nextjs-blog-setup": {
    title: "My MDX + Next.js Blog Setup",
    description: "This is a description",
    tags: ["nextjs", "mdx", "tailwind"],
    date: "2025-11-21",
    readingTime: "5 min",
  },
};

该脚本在 构建脚本之前 运行。

博客主页

列出所有文章的主页使用 blogPostsObject。这确保了一致性,并消除了在多个位置手动更新文章元数据的需求。

代码片段

我使用 Expressive Code 来高亮代码块。它是一个强大的插件,能够添加语法高亮、文件标签、行号,甚至差异视图。

  • 文件标签示例

    // file: src/components/Button.tsx
    export const Button = () => Click me;
  • 显示行号并高亮某行

    // file: server.ts
    const port = 3000; //  and let me know what you think!

使用 withTocExport 生成目录

withTocExport 函数负责触发导出目录(TOC)数据。

import withToc from "@stefanprobst/rehype-extract-toc";
import withTocExport from "@stefanprobst/rehype-extract-toc/mdx";

const withMdx = nextMdx({
  options: {
    // …
    rehypePlugins: [
      withToc,
      [withTocExport, { name: "toc" }],
    ],
  },
});

toc 变量随后会从 MDX 导入本身中导出,我们将其传递给 Toc 组件来渲染目录树:

const { default: Post, toc } = await import(`@/app/(blog)/mdx/${slug}.mdx`);

return (
  <>
    {/* render Post and Toc here */}
  </>
);
  • Post – 将作为博客文章渲染的组件。
  • toc – 包含生成的目录(table of contents)的变量。
  • Toc – 负责渲染目录 UI 的组件。

结束语

我对这篇博客的完成感到非常满意。虽然仍有许多工作要做,但我已经完成的部分值得花点时间欣赏其中的辛勤付出。

我对这个小项目的下一步充满期待,也希望你们阅读本文时感到愉快!如有任何错误或拼写失误,敬请见谅——我仍在学习如何撰写此类内容 😅

0 浏览
Back to Blog

相关文章

阅读更多 »