把一个 SPA 博客补成可预渲染、可同步、可持续部署
这次不是做“新功能”,而是把站点补成能长期运行的状态
这个博客原本已经能用,但存在几个典型问题:
- 它是一个 SPA,页面内容依赖前端运行后才出现。
WebFetch抓不到渲染后的正文,SEO 和分享预览都不稳。/news是每天持续增长的数据,但本地仓库和线上站点并不总是一致。- 服务器可以生成日报,却没有稳定地把新内容自动写回仓库。
结果就是,页面表面上都能打开,但一到构建、预渲染、同步和部署这些真实流程里,就会开始出现“本地没问题,线上不一致”“今天能发,明天可能覆盖”的问题。
这次的目标很明确:把博客补成一个能持续运行的工程,不只是一个能展示的页面。
第一件事:把首页、归档页、文章页接进预渲染链路
原站点是纯 CSR。浏览器打开当然没问题,但对于搜索引擎、抓取器或者分享机器人来说,它们拿到的往往只是一个空壳。
这次做法是:
- 先正常执行
vite build。 - 启一个只服务
dist的本地静态服务。 - 用 Playwright 逐个打开
/、/archive、/news和/post/:slug。 - 等页面主体真正渲染完成后,把最终 HTML 写回对应路由目录。
这个过程中真正踩到的坑有两个。
1. 不能用 networkidle 当成“页面好了”
在实际页面里,第三方请求、懒加载和异步模块都有可能让 networkidle 等不到,或者等得太久。
更稳的做法是:
- 先等
domcontentloaded - 再等页面主体元素出现
- 同时避开还在显示的骨架屏
这比单纯看网络状态更贴近“用户已经能看到内容”。
2. /news 这种无扩展名路由,必须统一回到壳文件
之前本地预渲染 /news 超时,不是因为 News 页面本身坏了,而是静态服务把无扩展名路径错误地解析到了已有输出目录,导致拿到的不是正确的 SPA 壳文件。
修复后,像 /news、/archive、/post/... 这样的前端路由,都会先回到主壳,再让前端自己渲染路由内容。这样预渲染才有正确的起点。
第二件事:把 /news 从“看起来在线”补成“仓库和线上一致”
线上 https://techloop.online/news 当时已经有 7 篇 news,但本地仓库只有 4 篇。
这意味着本地一旦部署,很可能把线上已经存在的日报重新覆盖掉。
问题本质不是页面错了,而是内容源断开了同步。
后来确认缺的是这 3 篇:
ai-dev-daily-2026-05-22ai-dev-daily-2026-05-26ai-dev-daily-2026-05-27
处理方式分两步:
1. 先把缺失日报补回本地仓库
这一步做完之后,本地 /news 和线上终于对齐,构建出来的 news 列表也恢复一致。
2. 再把服务器的“生成日报”改成“生成后自动回写仓库”
这一步补了几个关键配置:
AUTO_PUSH_DAILY_NEWS=true- Git 远端与分支
- Git 作者信息
- 服务器工作目录改成真实 Git worktree
这样以后服务器生成了新的日报,本地只要 git pull 就能拿到,不会再出现“线上有,本地没有,一部署就冲掉”的状态。
第三件事:把部署链路里两个真正会卡死发布的问题补掉
问题一:服务器环境变量里带空格的作者名没有加引号
当时服务器的配置里写的是:
GIT_AUTHOR_NAME=TechLoop Bot
Shell 在读取时会把 Bot 当成一个命令去执行,于是直接报:
Bot: command not found
正确写法应该是:
GIT_AUTHOR_NAME="TechLoop Bot"
这是一个很小,但会直接让发布失败的部署问题。
问题二:服务器没有 Chrome/Chromium,预渲染会整段失败
本地之所以能预渲染,是因为本机本来就有可用浏览器。
但服务器没有安装 Chrome/Chromium,prerender.mjs 在服务器上执行时会直接退出。
这次我做了两层处理:
- 补了 Linux 常见浏览器路径探测
- 如果服务器没有浏览器,就优雅跳过预渲染,不让整个发布失败
这意味着当前线上部署已经能稳定完成。
如果后面你想让服务器也真正产出静态化的 HTML,再补装 Chromium 即可。
顺手做掉的三个收口项
除了主线问题,这次还顺手把几个容易拖慢体验的小问题一起收了。
1. 补了无障碍基线
skip to content- 更清晰的
focus-visible - 导航当前状态标识
- 菜单支持
Esc关闭
这些改动不显眼,但能显著改善键盘导航和基础可访问性。
2. 整理了移动端主题入口
原来移动端切换主题的入口藏得太深。现在 Header 上有独立按钮,不用先展开菜单再操作。
3. 补齐了 3 篇日报的本地封面图
这 3 篇补回来的 news 原本没有 coverImage,列表页会出现大片空白占位。
这次直接补成了本地 SVG 封面,避免继续依赖空白占位或外链资源。
这次最大的收获,不是“修好一个 bug”
这次更像是把博客从“页面能展示”推进到“工程能长期维护”。
我更在意的是下面这几个变化:
- 构建结果开始对搜索抓取更友好。
news不再是线上和本地两套状态。- 服务器新增内容后,会自动回写仓库。
- 部署链路不会再因为一个空格或一个缺失浏览器直接中断。
这些工作不会像改首页那样立刻带来视觉冲击,但它们决定了这个博客以后是不是能稳定迭代。
下一步还值得继续做什么
如果继续往下走,我会建议优先做三件事:
- 在服务器安装 Chromium,让线上也真正产出预渲染页面。
- 给文章页补阅读进度、TOC 激活联动和返回顶部。
- 继续把图片资源整理成更系统的生成规范,比如统一封面尺寸、命名和压缩流程。
如果一个博客准备长期写下去,内容、构建、同步和部署最好在同一个工程闭环里。
这次做的,就是把这个闭环先搭起来。
See also
- 一次完整的 WSL2 安装排障:从 BIOS、TPM 判断到 Windows 11 修复安装 2026-07-07
- 把宿舍 Windows 主机改成可远程训练的 WSL 工作站 2026-05-27
- 把每日科技日报改成服务器自运行 2026-05-26
- Chrome 一打开就跳到 360 导航页?按这份手册一步步修复 2026-05-20
- 博客改版记录:首页、栏目页与轻量 3D 2026-05-16