> ## 文章索引
> 在这里获取全部文章的索引：https://lrd.im/blog/llms.txt
> 这是 lrd.im 的个人博客，记录设计、技术与思考。

# 用 Next.js 重构个人网站 (一)：概述

![封面图](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/01.png "封面图：Maxime Bourgeois")

我的个人网站从 2017 年底（大四上学期的期末）进行 “研发”，2018 年初部署运行，现在已经过去了 5 年。中途经过大大小小近 300 次迭代和内容更新。

从原本只是用来放置我做 Side Project 成果的内容，到后来变成我对外展示的窗口，用来展示我在实际工作中的项目，以及设计博客和翻译的文章等。LRD.IM 的页面布局、风格样式、内容、功能等等随着我的设计生涯发展有着很大变化。

![LRD.IM 个人网站从 2018 年到 2022 年四个阶段的界面截图：早期为黄色调作品集风格，2019 年改为卡片式网格布局，2021 年增加项目详情页，2022 年演变为简洁的设计师个人简历风格。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/02.png)

> 互联网档案馆 [Internet Archive](https://web.archive.org/web/20230000000000*/lrd.im) 保留了 2019 年之后的存档。之后每一次改版我也会在该网站留下存档。不知 5 年后又会是啥样呢。🤔

唯一不变的是，它仍然是用纯粹的 HTML + CSS + JavaScript 来实现的。访问我的网站就像是打开一个文件夹，浏览器只是将我的 html 打开而已。

网站内部没有组件，没有数据源。改动一个公用的地方，如页面顶部导航栏或博客的文章添加一个模块时，**我需要在编辑器里打开数十个 html 文件逐个修改**。

自己一直有想着改造一波，但自己当前的水平，想做出一个现代化的个人网站还差很多。所以这个计划也就一直搁置，自己日常中最多是观察下其他优秀的个人网站及其实现方式，储备点灵感。

直到两个动机和一个条件的出现，我觉得是时候了。

## 动机和条件

### 动机：迭代效率

最开始我的博客页面只是一个列表，点击里面的文章都会跳转到 Medium 平台里面。后面我觉得这样做体验巨差，原因：

- 自己创作的内容全部都放在别的平台上面去了。这是一个令我太不舒服的感受。我的想法是可以放到其他平台内，获取一点点流量和 SEO 的优势，但自己的站点是最优先的；
- Medium 需要科学上网才能较好访问。读者的体验未必会好，从稳定性上考虑也不利于长期发展；
- Medium 一直没有进步。感觉这几年这个平台在原地踏步，或者甚至是下坡路。这是我作为长期用户的主观感受。

所以后面我将 Medium 的文章导出成 HTML 文件（平台的功能），作出相应的调整后贴回自己的网站上面，这样文章就能又回到自己那儿了。

**但这个转换的过程并不简单**，Medium 导出的 HTML 是带有他们的一些标签和样式的，当时我是将每一篇文章的 HTML 都筛掉没用的内容，然后 CSS 里编写了一套自己的格式文本的样式，使其适应网站的设计风格。由于步骤太多我甚至还将做法[记录](https://github.com/Pudge1996/LRD.IM#%E4%B8%83%E5%90%8C%E6%AD%A5-medium-%E6%96%87%E7%AB%A0 "Github 中我将处理方法记录了下来")了下来，发布文章之前任何一步有纰漏都会出问题。

![改造 Medium 导出文章的步骤清单截图，包含处理文档（替换标签、更新导航、清理多余属性）、处理图片（调整属性、添加懒加载、上传 OSS）、使文章可被目录抓取共三大类、十余个繁琐步骤。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/03.png)

这套混乱的秩序维持了至少一年半，这期间我的每一篇文章都经过这样的处理。包括后面添加的「目录功能」、「博客功能区」等有新增或调整代码的时候，我都要在每一篇文章（40+ 篇）的对应位置里增删改。

这种手动、机械式、重复的工作不仅导致迭代网站的**时间成本极高**，也**很容易出错**。

比如某篇博客的功能区，本身应该所有页面的这块内容都应该长得一样，只是日期不同，但就是因为我将**数十个 html 文件逐个修改**的时候出现了纰漏，导致某些页面的样式异常。

![两个同类型博客页面的对比截图：上方标注「异常的样式」，文章信息区域显示了多余的「Pudge」姓名和「首次发布于」字段；下方标注「正常的样式」，仅显示作者、日期和发布平台，布局简洁正确。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/04.png "我将某段代码复制到该页面时可能某个地方出现了疏漏，就出现了样式异常。")


> 这次重构我抛弃了 HTML，改成了用 MDX 来支持我的博客内容。之后的文章会介绍到我是如何用 mdx-bundler 来重构整个博客功能模块。以及我为了提升阅读体验所做的工作。

### 动机：体验问题

我对网站里面有一个非常刺眼的体验问题一直耿耿于怀。

#### 暗色主题下刷新页面会出现闪烁

原本的做法下，亮/暗主题色的切换功能是写在 JS 文件里面。而用户在跳转页面时会重新加载一次文件，偶尔会出现令人难以接受的闪烁。

<video alt="视频展示了深色模式下跳转页面会有闪烁的现象，一瞬间会切换到亮色模式下后再回到深色模式，这种明显的 BUG 令人难以接受。" src="https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/05.mp4">

我也带着这个问题咨询了前端工程师朋友，他认为如果将网站做成 SPA，应该就能解决这个问题。

> 单页应用（英语：single-page application，缩写SPA）是一种网络应用程序或网站的模型，它通过动态重写当前页面来与用户交互，**而非传统的从服务器重新加载整个新页面**。 —— [维基百科](https://zh.wikipedia.org/zh-hans/%E5%8D%95%E9%A1%B5%E5%BA%94%E7%94%A8 "维基百科对 SPA 的介绍")

而且对于主题色，其实最好是能给用户手动来切换，并且记录上次更改后的结果。但这个功能要做在静态网站上，而且想到有代码更改的话又得将**数十个 html 文件逐个修改**，实在是没有研究的动力了。

### 条件：离职之后

2022 年 11 月我从欢聚离职，回到老家，暂时离开城市和朋友圈。有充足的时间给我去学习这方面的知识，以及去做相关的实践。

---

## 技术栈

这次的重构我的个人网站并不是一时兴起，平常自己也会了解相关的资讯。既然选择了行动，就已经做好跳出舒适圈的准备，接触一些以往从没有实践过的内容。

### Next.js

整个 2022 年，通过 Next.js 构建的网站挤满了我的推特信息流。时不时都会看到有些设计师/开发者在推特上发布自己的个人网站。也发现许多充满设计感的网站都是通过 Next.js 来做的，比如 [Linear](https://linear.app/)、[Pipe](https://pipe.com/)、[Fey](https://www.feyapp.com)、[Frame.io](https://frame.io/) 等。

![Frame.io 和 Linear 两个网站的首页截图并排展示，均采用深色设计风格，配以大号粗体标题和简洁布局，体现了由 Next.js 构建的高品质网站的视觉风格。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/06.png)

在一众先驱者的引导之下，我也去了解了下相关介绍，甚至还看了点 [Next.js 初学者教程](https://stackdiary.com/next-js-tutorial-getting-started)，里面大部分内容我大部分都能够 Get 到。感觉上手难度不大，又有这么多大佬级的网站做背书，于是乎，就是你了——Next.js!

### Tailwind CSS

我选择使用 [Tailwind CSS](https://tailwindcss.com/) 的原因主要有两个：

第一个是这个网站做得很好看，包括它给出来的 Template，这让我有了进一步了解的兴趣；

第二个原因是这确实有效解决了在迭代原网站时让我烦恼的地方：

1. 我基本上再也不用绞尽脑汁去想某一个元素的类名（还得是英文的，时间久了想不起来哪个样式用在哪）；
2. 在实际写代码的过程中也不用经常在页面和 CSS 文件中来回切换了。现在写反馈效果、媒体查询、深色样式时很方便；
3. 最关键：我再也不用维护一个 **2,000+** 行的层叠样式表了！重构后的 CSS 文件轻量了很多，因为都写进网页和组件中了。

![代码编辑器截图对比：使用 Tailwind CSS 前，Sass 样式文件超过 2,000 行；重构后，CSS 文件缩减至约 500 行，代码量减少约 75%。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/07.png)

### mdx-bundler

之前将 Medium 文章迁移过来的时候，就发现了 HTML 的做法只适合有富文本编辑器的平台。而像我格式简单的博客，用 Markdown 来承载反而更合适。

然后我是在 Tailwind CSS 的 [Protocol](https://tailwindui.com/templates/protocol) 模版介绍当中了解到原来 MDX 是可以让 Markdown 和 Next.js 联动起来，然后也在 Josh Comeau 介绍自己的博客时。也有介绍到[相关内容](https://www.joshwcomeau.com/blog/how-i-built-my-blog/#mdx-the-secret-ingredient-2)，于是就选择了 mdx-bundler 来实现我的博客模块。

![两张截图展示 MDX 的来源：Tailwind CSS Protocol 模版介绍页标注 "Powered by MDX"，以及 Josh Comeau 博客文章《MDX, The secret ingredient》说明 MDX 是支持嵌入 React 组件的 Markdown 扩展格式。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/08.png)

MDX 允许我们安装各种插件来增强博客的能力。比如支持到 Github Markdown 语法的插件 [remark-gfm](https://github.com/remarkjs/remark-gfm) 和代码高亮插件 [Rehype Pretty Code](https://rehype-pretty-code.netlify.app/) 等。

![MDX 插件展示对比：左侧 remark-gfm 插件支持 GFM 语法（自动链接、脚注、删除线、表格、任务列表等）；右侧 Rehype Pretty Code 插件基于 VS Code 引擎实现代码语法高亮，视觉效果精美。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/09.png)

而且还能**嵌入自己编写的 React 组件**，这是让人感到惊奇的能力。这意味着我的博客文章可以不仅是图文、视频和 iframe 了，还能有一些互动元素，有很大的想象空间。

---

## 过程概述

由于我属于边做边学，所以整个重构的过程并非从头到尾一次性完成的。而是一个螺旋式上升的过程。

![网站重构的三个阶段示意图：第一阶段构建核心功能（通用布局、数据源、MDX 渲染），第二阶段填充内容（作品、博客文章、更新记录），第三阶段填坑优化（改进代码、设计组件、检查边界场景）。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/10.png "插图素材来自 egghead.io")

#### 第一步：核心功能
当时先完成核心或通用的功能部分（遇到难搞的功能先记录并跳过），然后将原站点的所有内容填充进去。
这时确保网站在本地环境中基本能正常跳转和响应点击，深色模式和响应式等适配工作需要完整做好，因为这方面是相对简单的工作。具体来说：

1. 先学会 Tailwind CSS 和 Next.js 的配合使用，设定好通用的布局和主要样式。以及调试主要页面之间的路由跳转；
2. 开始构建主要页面，学会运用数据源和循环遍历来呈现页面内容（完整的内容先不填充，确保循环能跑通，数据能正常获取）；
3. 尝试使用 mdx-bundler 来读取博客列表和渲染 Markdown 文章。这个模块特别重要，当时花了不少时间。

#### 第二步：内容填充
这时会带着早些时候记录下来的问题和卡点，去耐心查阅并参考尝试其他开发者博客里面他们的实现方案，同时也进入 [The Joy of React](https://www.joyofreact.com/) 课程当中系统地学习一遍 React 的基础内容。具体来说：

1. 填充所有数据源和遍历中用到的内容，如某个字段的文本、配图的 URL 等；
2. mdx-bundler 跑通之后，我用 Markdown 重写了所有的博客文章，并且每篇文章都填入相应的元数据；
3. 填充所有作品详情页里的内容。

#### 第三步：填坑优化
有了知识储备之后，最后再回头把原有的一些卡点和问题填补上，或者优化下原本的代码。甚至会改良下组件的命名、文件夹结构等等，这些会参考到其他开发者他们所总结出来的的**最佳实践**。

1. 带着过程中遇到的卡点和问题，在其他开发者博客或课程中学习理论知识；
2. 解决之前的卡点和问题，完成所有功能，以及边缘场景的处理；
3. 看看原有代码有没有哪些地方可以改良；
4. 重复「学习→解决→改良」步骤，直到网站达到了我认为能够部署的标准。

---

## 解决问题的方法

做一个网站实属不容易。尤其像我一样在自己的能力之外，选择用框架来实现，过程更加是绝非一帆风顺。不止跳出了舒适圈，甚至还跳进了「痛苦圈」。

技术方面的指引我不敢乱写，倒是可以分享下在这次重构的过程中，我用到的几个解决问题的方法：

### 搜索引擎和编程社区

一些广泛的问题我会找 Google 来帮忙，比如我想看看别人是怎么做亮/暗模式切换的，我会搜索类似 "next.js darkmode switch"，搜索结果中可能会出现一些开发者的博客、Stack Overflow 或  Github Issue 中提出相似问题的人，这些内容是我可以拿来参考的。

![搜索 "next.js darkmode switch" 的 Google 结果页面，右侧展示了其中一篇搜索结果——开发者博客文章《Dark Mode with Tailwind CSS and Next.js》，介绍了通过 next-themes 库实现暗模式的方法。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/11.png)

### ChatGPT

在去年 12 月 ChatGPT 横空出世，当时有看到很多推文有展示它的神奇功力。我甚至觉得它很多的回答比人来回答还要好，很清晰和有条理，让人感到意外。

我也利用上这股强大的力量，用 ChatGPT 来解决一些具体的问题。比如现在博客文章里面的目录，会查找页面内所有的`H2` `H3`标签并生成为一个列表，这就是我向 ChatGPT 提出我的需求之后，它来帮我写出来的。

![ChatGPT 针对「博客目录功能」的回答截图：建议使用 React 的 useEffect 和 document.querySelectorAll 查找 H2、H3 标签，并附上完整的 Next.js TOC 组件示例代码。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/12.png)

另外我也会让 ChatGPT 帮忙解释某段代码的意思或作用。我从搜索引擎或编程社区里获得的一些解决方案，有时候某些语句自己没看懂的时候，会需要 ChatGPT 来帮忙解释一下，好让我针对自己网站的特点作出些许改造，或者了解报错原因和解决方法。

### Joy of React

即便 ChatGPT 能解决部分具体的问题，但也不是万能的。实际开发中有些特别具体的问题，始终还是要靠自己。我对 JavaScript 掌握的程度是属于半滴水（半桶水都算不上），对 React 以及里面的 JSX 语法更是完全没了解过。

简单的内容靠查阅文档 + 观察规律可以学会，比如我通过看其他人的案例，我大概知道了每个组件里面都要有一个`export`、`return()`里面只能有一个子标签、JSX 里面有一个特殊的标签是`<> ... </>`等等...

但稍微再具体一点的问题，比如根据某个条件来更改样式，或者甚至想直接做一个需要传递参数来改变展示方式的组件，这些特别个性化的内容想要光靠搜索引擎和 ChatGPT，效率太低了。很可能搜半天还是一头雾水，浪费光阴。

刚好在今年 1 月 24 日，前端大佬 Josh W. Comeau 发布了 [The Joy of React](https://twitter.com/JoshWComeau/status/1617553220242661377 "Josh 发布 Joy of React 课程的推文") 的教程，这是一份面向初学者的 React 教程。在里面我能系统地学到一些 React 相关的知识，或者课程中会用到的 JavaScript 的基础等。

![Joy of React 课程宣传图，展示 React 标志性的原子模型图标（带笑脸），左侧写有 "Learn React. Build cool stuff. Have fun!"，由前端开发者 Josh W. Comeau 出品](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/13.png "The Joy of React 的课程封面")

这门课程对我的网站重构项目起了很大的帮助，学习后我弄清楚了一些 React 的运行规则，也确实做出了一些实用的组件，改进了页面的代码质量。之前一直卡住的、很具体的问题也在学习之后得到了解决。

这份 $50 左右的课程干货满满，作者也很用心地安排了每个章节会有互动性的练习，也会附带一些视频解说（有 CC 字幕和视频摘要）。后续我可能会出一篇文章来回顾下我在这个课程内学到的内容，记录下这段激情澎湃的青葱岁月。


### 请教专业人士

好吧，如果有些问题确实难搞，自己用尽所有的方法都没法解决，同时也在这个问题上耗费了很长时间的时候，我会咨询一些前端工程师朋友。

工作几年我一直和前端工程师的关系都也还行，所以有时候问到些问题他们也乐意帮我解答（还因为我的问题在他们眼里其实非常基础...）。

除了现实中认识的朋友以外，其实也可以咨询网络上的一些开发者。如果一个开发者将他的经验分享出来后，其他人使用过程中遇到问题，大部分都乐意帮忙解决的。

![Gmail 邮件截图：博主 Pudge 向博主 Danilo Leal 发邮件请教网站开发问题，Danilo 热情回复并分享了路由实现方案和 CMD+K 库推荐链接，展示了向网络开发者请教的实际案例。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2023-02-21/14.png)

---

## 未完待续...

这一篇文章记录了重构我的个人网站 [LRD.IM](https://lrd.im) 时的经历，计划后面会有多几篇文章来记录具体我做到某个模块时的一些过程，或者说重构之后一些我认为比较有意思的改良等等，算是给这次重构计划一个交代。