从VitePress到NextJS-构建AI阅读笔记站的技术演进
1. 项目背景
AI 阅读(ai-reading)是一个个人知识管理项目,用于整理和分享 AI 相关书籍的阅读笔记。项目始于简单的 Markdown 文件收集,随着内容的积累,逐渐演变成一个功能完善的静态站点。

图:书籍详情页展示 - 左侧文件树,中间正文,右侧 TOC
项目规模:
- 📚 书籍笔记:93 篇 Markdown 文档
- 💻 代码量:约 1,800 行 TypeScript/TSX
- 🏗️ 分类体系:多级目录结构(商业管理、个人成长、思维方式等)
- 📝 Git 提交:146 次,最近重构涉及 93 次提交
- ⏱️ 构建产物:97 个静态页面,构建耗时 4.3 秒
核心功能:
- 📁 文件树浏览(多级分类,支持展开/折叠)
- 🔍 全文搜索(书名、作者、标签、内容)
- 🏷️ 标签系统
- 📖 文章大纲(TOC)
- 🎨 Markdown 渲染(代码高亮、表格、引用等)
- 📱 响应式设计(桌面端 + 移动端)
2. 技术演进:三次迁移的故事
2.1 第一代:VitePress
选择理由:开箱即用的文档站,Markdown 渲染优秀,配置简单。
遇到的问题:
- 定制化受限,更像是”文档站模板”而非”框架”
- 默认布局难以调整,不适合书籍库的展示需求
- 侧边栏配置需要手动维护,无法自动从文件系统生成
结论:VitePress 适合传统技术文档,但不适合需要高度定制的书籍库。
2.2 第二代:Astro
选择理由:
- “内容优先”的设计理念
- Islands Architecture,零 JS by default,性能极佳
- 高度可定制,可以使用 React/Vue/Svelte
实施效果:
- ✅ 成功实现自定义布局和样式
- ✅ 实现基于文件系统的自动分类
- ✅ 添加标签系统和搜索功能
致命问题:侧边栏状态无法保持
这是一个看似简单但难以解决的问题:
- 用户在侧边栏展开某个分类,点击一本书
- 阅读完毕后返回,想继续浏览同一分类下的其他书籍
- 但页面刷新后,侧边栏又回到了初始折叠状态
尝试的解决方案:
- localStorage 持久化:页面刷新时有明显闪烁,用户体验差
- View Transitions API:只能保持 DOM 的视觉连续性,无法保持 JavaScript 状态
- Persistent Islands:Astro 的页面导航本质是浏览器原生导航(MPA),每次都是全新页面加载
根本原因:Astro 的设计理念是真正的 MPA(多页面应用),每次导航都是完整的页面刷新,JavaScript 状态无法跨页面保持。
2.3 第三代:Next.js
选择理由:
- 支持静态导出(
output: 'export'),可以部署到 GitHub Pages - 本质上是一个 React SPA,客户端路由可以保持组件状态
- App Router 的 Layout 机制,完美支持常驻侧边栏
实施效果:
- ✅ 侧边栏状态完美保持
- ✅ 页面导航流畅,类似 SPA 的体验
- ✅ 所有功能都能实现
- ✅ 静态部署,无需服务器
结论:Next.js 是目前最适合我们需求的技术方案。
3. 为什么 Next.js 能做到而 Astro 不行?
这是整个技术选型中最关键的问题。表面上看,两者都支持静态导出,都能生成多个 HTML 页面,但底层机制完全不同。
3.1 构建产物对比
Next.js 的构建产物:
1 | out/ |
关键特征:
- 所有页面共享相同的 JavaScript chunks
- HTML 只包含内容,交互逻辑在 JS 中
__next.*.txt文件用于客户端路由的数据获取
Astro 的构建产物:
1 | dist/ |
关键特征:
- 每个 HTML 是完整独立的页面
- JavaScript 按需加载,每个页面可能加载不同的 JS
- 没有客户端路由
3.2 运行时行为对比
Next.js:SPA with SSG
1 | 用户访问 /books/book1 |
关键点:
- Hydration 后,页面变成一个 React SPA
- 后续导航完全由 React Router 控制
- Layout 组件(包括 Sidebar)在应用级别挂载,永不卸载
Astro:传统 MPA
1 | 用户访问 /books/book1.html |
关键点:
- 每次导航都是浏览器原生导航
- 页面刷新 = 所有 JavaScript 状态丢失
- 即使使用 View Transitions API,也只是视觉平滑,无法保持内存状态
3.3 为什么 View Transitions 无法解决问题?
Astro 支持 View Transitions API,可以实现页面切换的平滑动画。
View Transitions 做了什么:
- 拦截链接点击
- 通过 fetch 获取新页面的 HTML
- 对比新旧 DOM,找到变化的部分
- 使用 CSS 动画平滑过渡
- 替换 DOM
问题在于:
- View Transitions 只处理 DOM,不处理 JavaScript 状态
- Sidebar 的展开状态存在 JavaScript 的内存中(如
useState) - DOM 替换后,JavaScript 重新执行,状态重置
类比:View Transitions 就像是拍了一张照片,然后平滑地切换到另一张照片。但照片里的人不记得上一张照片里发生了什么。
3.4 Next.js App Router 的 Layout 机制
Next.js 的 App Router 引入了 Layout 的概念,这是实现常驻侧边栏的关键:
1 | // app/layout.tsx |
运行时行为:
1 | 组件树结构: |
当用户从 book1 导航到 book2:
RootLayout保持挂载 ✅Sidebar组件不卸载 ✅- 只有
children部分更新(BookPage 组件) - Sidebar 的 React state 保持不变 ✅
3.5 技术架构本质差异
| 特性 | Next.js (Static Export) | Astro |
|---|---|---|
| 架构模式 | SPA with SSG | MPA |
| 运行时 | React 应用 | 原生 HTML + 可选 JS |
| 导航方式 | 客户端路由 | 浏览器原生导航 |
| 状态管理 | React state(跨页面) | 页面级别(无法跨页面) |
| 代码共享 | 所有页面共享 chunks | 每个页面独立 |
| Hydration | 完整 React 应用 | Islands(局部) |
| 初始加载 | 较大(React runtime) | 较小(零 JS) |
| 后续导航 | 极快(无刷新) | 较慢(页面刷新) |
| 状态保持 | ✅ 完美支持 | ❌ 需要 hack |
总结:
- Next.js:虽然生成静态文件,但本质是一个 React SPA
- Astro:真正的 MPA,追求最小 JS,不保持客户端状态
这就是为什么我们最终选择了 Next.js。
4. AI 协助开发的实践
在整个项目开发过程中,Claude Code 扮演了重要角色。这里分享一些实践经验。
4.1 AI 协助的场景
场景 1:技术选型咨询
问题:Astro 无法保持侧边栏状态,是否有解决方案?
AI 的帮助:
- 分析了 Astro 的 Islands Architecture 原理
- 解释了 View Transitions API 的局限性
- 对比了 Next.js 的 Layout 机制
- 给出了明确的技术选型建议
价值:避免了在错误的方向上浪费时间。
场景 2:需求实现
任务:实现文章大纲(TOC)功能。
协作流程:
需求讨论:
- 我:想要一个像 VitePress 那样的 TOC
- AI:建议桌面端固定右侧,移动端用抽屉
- 我:认可,并提到 Obsidian 的移动端体验不错
设计决策:
- AI:提议使用 shadcn/ui 的组件
- 我:不想引入新依赖,用原生实现
- AI:同意,使用 Tailwind CSS 实现
迭代优化:
- 第一版:TOC 按钮放在顶部导航
- 我:不太合适,之前看到过浮动按钮的方案
- AI:改为右下角浮动按钮
- 我:很好,就这样
Bug 修复:
- 我:点击”返回顶部”没反应
- AI:检查后发现滚动容器是
main元素而非window - 修复:添加了容器检测逻辑
场景 3:代码重构
任务:将分类系统从 frontmatter 驱动改为文件系统驱动。
背景:
- 原先每个 Markdown 文件都有
category: 商业管理/市场营销字段 - 但文件本身就在
books/商业管理/市场营销/目录下 - 存在”双重真相源”:文件路径和 frontmatter 可能不一致
- 移动文件时需要同步更新 frontmatter,容易出错
AI 的帮助:
理解需求后,制定了重构计划:
- 修改
lib/books.ts的逻辑,从文件路径提取分类 - 编写批量处理脚本移除 frontmatter 中的 category 字段
- 验证分类正确性
- 修改
编写了两个自动化脚本:
remove-category-field.js:移除 93 个文件的 category 字段fix-frontmatter-spacing.js:清理多余空行
执行并验证:
- 处理了 93 个文件
- 确保 Git 正确识别为内容修改(而非删除+新建)
- 测试分类系统正常工作
价值:
- 自动化了繁琐的批量操作
- 避免了手动编辑可能出现的错误
- 实现了”单一真相源”:文件系统即分类系统
4.2 AI 协助的优势
知识广度:
- 熟悉多种框架(VitePress、Astro、Next.js)
- 了解最佳实践和常见陷阱
- 能够对比不同方案的优劣
快速迭代:
- 立即生成可运行的代码
- 根据反馈快速调整
- 无需查阅大量文档
自动化能力:
- 编写批量处理脚本
- 自动化测试和验证
- 减少重复劳动
问题诊断:
- 分析 Bug 的根本原因
- 提供多种解决方案
- 解释背后的技术原理
4.3 AI 协助的局限
需要明确的需求:
- AI 不能替你做决策(如技术选型)
- 需要你明确表达期望的效果
- 对模糊的需求可能给出不合适的方案
需要验证和测试:
- AI 生成的代码不一定完美
- 需要实际运行和测试
- 可能需要多次迭代
依赖你的反馈:
- AI 看不到实际效果(除非你截图)
- 需要你描述问题和期望
- 协作质量取决于沟通质量
4.4 高效协作的技巧
清晰的需求描述:
- ✅ “实现一个像 VitePress 那样的 TOC,桌面端固定右侧,移动端用抽屉”
- ❌ “加个目录功能”
及时的反馈:
- 发现问题立即指出:”点击按钮没反应”
- 说明期望的行为:”应该滚动到页面顶部”
允许迭代:
- 不要期望一次就完美
- 通过多轮对话逐步优化
- 每次改进一个问题
技术决策由你主导:
- AI 可以提供建议,但最终决策权在你
- 你更了解项目的长期目标和约束
- AI 是助手,不是替代品
4.5 协作统计
在这个项目中:
- 💬 对话轮数:约 100+ 轮
- 📝 生成代码:约 2,000 行(包括迭代和调整)
- 🐛 修复 Bug:10+ 个
- 📚 编写脚本:2 个自动化脚本
- ⏱️ 节省时间:估计节省了 20+ 小时的开发时间
最有价值的协助:
- 技术选型建议(避免了在 Astro 上继续浪费时间)
- 分类系统重构(自动化处理 93 个文件)
- TOC 功能实现(从零到完成只用了 1 小时)
5. 经验总结与思考
5.1 技术选型的思考
教训 1:不要被”性能”迷惑
Astro 的性能确实很好(零 JS by default),但:
- 对于这个项目,性能不是瓶颈
- 用户体验(状态保持)更重要
- 800KB 的 JS 在现代网络下可以接受
教训 2:理解框架的设计理念
- VitePress:文档站,不是通用框架
- Astro:内容站,MPA 架构
- Next.js:全栈框架,支持 SPA 和 SSG
选择框架时,要理解其设计理念是否匹配你的需求。
教训 3:不要过早优化
最初担心 Next.js 的包体积,想用 Astro 来”优化”。但实际上:
- 用户体验 > 包体积
- 开发效率 > 极致性能
- 可维护性 > 技术炫技
5.2 AI 协助开发的思考
AI 的价值:
- 不是替代开发者,而是提升效率
- 不是写代码的机器,而是协作伙伴
- 不是万能的,但在特定场景下非常有用
何时使用 AI:
- ✅ 实现标准功能(文件树、搜索、TOC)
- ✅ 代码重构和批量操作
- ✅ Bug 诊断和修复
- ✅ 技术方案对比
- ❌ 核心业务逻辑设计
- ❌ 架构设计决策
- ❌ 产品需求定义
AI 协助的最佳实践:
- 保持清晰的沟通
- 及时反馈和迭代
- 验证 AI 生成的代码
- 主导技术决策
- 把 AI 当作有经验的同事,而不是工具
5.3 开发过程的反思
什么是真正重要的:
- 不是代码写得多漂亮
- 不是技术栈多先进
- 而是解决了实际问题
- 而是提升了用户体验
AI 时代的开发者:
- 不需要记住所有 API
- 不需要从零手写所有代码
- 但需要:
- 清晰的需求理解能力
- 技术方案的判断能力
- 与 AI 协作的沟通能力
- 验证和测试的严谨态度
6. 结语
从 VitePress 到 Astro,再到 Next.js,这个项目经历了三次技术栈迁移。每一次迁移都是对需求的重新理解,对技术的深入探索。
核心收获:
- 技术选型没有银弹:适合的才是最好的
- 用户体验优先:技术指标要服务于用户体验
- 理解底层原理:才能做出正确的决策
- AI 是强大的助手:但决策权始终在开发者手中
希望这篇文章能帮助你:
- 理解 Next.js、Astro 等框架的本质差异
- 了解如何与 AI 高效协作
- 在类似项目中做出更好的技术选型
技术栈:
- Next.js 16.1.6 (App Router + Static Export)
- React 19.2.4
- Tailwind CSS v4
- Pagefind (全文搜索)
- GitHub Pages (部署)
开发环境:
- Claude Code (AI 协助)
统计数据(截至 2026 年 2 月):
- 📚 书籍笔记:93 篇
- 💻 代码行数:1,800 行
- 🏗️ 静态页面:97 个
- 📝 Git 提交:146 次
- ⏱️ 构建时间:4.3 秒