用 AI Agent 把 Hexo 博客从 5 秒白屏优化到 0.5 秒可见
前几天我重新打开自己的博客,第一眼不是慢,而是空。
页面框架已经出来了,左侧栏也在,但文章区域要等大约 5 秒才显示。对一个以文字为主的静态博客来说,这个体验很不合理。于是我让 AI Agent 参与了一次完整排查:测量线上表现、定位源码、修改配置、建立自动化验收、提交代码,再部署到腾讯云。
这篇文章记录实际过程。没有换主题,也没有为了跑分重写整站,主要目标只有一个:先让读者尽快看到文章。
先测量,不凭感觉改
博客使用 Hexo 和 NexT Gemini。线上站点看起来并不大,静态文件总量约 7.7 MB,磁盘空间不是主要矛盾。
真正的问题出现在冷启动:
| 指标 | 优化前 |
|---|---|
| DOMContentLoaded | 约 4.17 秒 |
| 首次内容绘制 FCP | 约 4.39 秒 |
| 第一篇文章真正可见 | 约 5.12 秒 |
anime.min.js 加载 | 约 4.06 秒 |
pace.min.js 加载 | 约 3.79 秒 |
如果只看 HTML 返回时间,很容易误以为站点没问题。浏览器自动化测量发现,NexT 的 Motion 会先隐藏 .post-block、.post-header 和 .post-body,等动画依赖准备好后再显示。
也就是说,文章早已在 HTML 里,却被样式主动藏了起来。外部 CDN 稍微慢一点,读者看到的就是一大片空白。
这个结论比“服务器带宽不够”靠谱得多,因为它能解释为什么页面外壳出现了,正文却迟迟不见。
第一刀:关闭不必要的首屏动画
博客的价值是内容,不是入场动画。我最终关闭了 Motion、Pace 和 PJAX,同时只保留一种图片缩放方案:1
2
3
4
5
6
7
8
9
10motion:
enable: false
pace:
enable: false
pjax: false
fancybox: false
mediumzoom: true
lazyload: true
关闭 Motion 后,文章不再默认隐藏。即使后面的 JavaScript 还在加载,正文也能先显示。
Pace 的进度条同样被移除。它并没有让页面更快,反而增加了一项外部依赖,还会让用户产生“页面还不能用”的心理暗示。
外部资源改为选择性自托管
站点原先从 jsDelivr、cdnjs 等公共 CDN 加载主题依赖。在国内网络环境下,这种依赖很容易成为短板。
我没有把整个 NexT 插件包全部复制到站点。第一次尝试全量本地化后,生成目录明显变大,很多从未启用的库也被一起带了进来。后来改成只托管实际使用的资源:
- Font Awesome
- anime.js
- Medium Zoom
- Lozad
- 本地搜索脚本
- Gitalk
- Creative Commons 图标
CSS 和 JavaScript 文件增加了内容哈希,例如:1
2/css/main-a5ed5ab8.css
/lib/medium-zoom/medium-zoom.min-42012313.js
服务器对静态资源使用了一年 immutable 缓存。如果文件名不变,发布新版本后,浏览器可能长期使用旧文件。内容哈希解决了这个问题:内容变化,URL 也会变化。
另一个容易忽略的问题是 CSS 内联。原配置把约 57 KB 的主题 CSS 重复塞进每个 HTML 页面,不仅让页面膨胀,也失去了浏览器共享缓存。调整后,首页从约 99 KB 降到 42 KB 左右,所有页面共用一份带哈希的样式文件。
卡片不是越宽松越好
桌面主内容区宽度本来没有问题,浪费主要来自纵向间距。
优化前,一张短文章卡片约 350 px 高;移动端约 305.5 px。标题区下方、阅读全文按钮上方和卡片内边距叠加后,首屏只能完整看到两篇短文章。
我把几个关键值收紧:1
2
3
4
5
6
7
8
9
10
11
12
13.posts-expand .post-header {
margin-bottom: 24px;
}
.posts-expand .post-button {
margin-top: 16px;
}
@media (min-width: 992px) {
.post-block {
padding: 28px;
}
}
调整后,桌面短卡片高度约 268 px,1440×1000 的首屏可以完整显示三篇文章。移动端卡片约 269.5 px,390×844 的首屏可以完整显示两篇,并露出第三篇。
移动端还隐藏了站点副标题,头部高度从约 111 px 缩到 75 px。标题、菜单和搜索按钮仍然完整,没有横向溢出。
图片里藏着比代码更大的浪费
线上 logo.svg 一度接近 1.95 MB。它虽然叫 SVG,内部实际嵌入了一张 1542×1542 的 PNG,并不是真正的矢量图。
源码仓库里已经有一份真正的矢量版本,只有约 68 KB,部署时直接使用即可。
头像问题更明显:源码头像有 5.5 MB,而页面显示区域很小。用线上已经压缩并验证过的 256×256 版本替换后,文件降到约 100 KB,视觉上没有变化。
这次没有盲目追求“所有图片都转 WebP”。图标、头像、文章截图的用途不同,处理方式也应该不同。先找体积最大的异常资源,收益通常更高。
源码构建还踩了两个坑
第一个坑是文章更新时间。
Hexo 原配置使用文件系统 mtime 作为更新时间。Git 不保存文件修改时间,所以在服务器重新克隆仓库后,所有文章都会显示成“今天更新”。最终将配置改为:1
updated_option: date
没有显式 updated 字段的文章回退到发布时间,不再因为重新拉取仓库而产生错误更新时间。
第二个坑是 PWA Manifest。图标放在 /images/,但 Manifest 使用了根目录路径,部署后会请求不存在的文件。修正为相对于 manifest.json 的路径后,浏览器才能正确找到图标:1
2
3{
"src": "./android-chrome-192x192.png"
}
AI Agent 在这次优化里做了什么
这次没有让 AI 直接“看一眼页面然后改 CSS”。实际流程更接近一次小型工程任务:
- 用浏览器自动化记录冷启动时间和元素可见时间;
- 读取线上生成文件,定位 Motion 和外部依赖;
- 找到真正的 Hexo 源码仓库,而不是直接修改
/var/www/hexo; - 先建立会失败的验收,再修改配置;
- 在桌面端和移动端测量卡片尺寸、视口宽度和溢出情况;
- 提交源码到 GitHub,再由腾讯云部署仓库更新线上静态文件;
- 发布前创建完整备份,发布后重新跑冷启动测试。
AI 适合做这种需要大量读取、测量和交叉检查的工作,但“能生成代码”不等于“可以直接上线”。第一次独立审查就发现,验收脚本在 public/ 不存在时会跳过生成结果检查,造成假通过。修复后,测试会先执行干净构建,并在生成目录缺失时明确失败。
后续复审还指出,验收脚本的路径边界和 HTML 解析可以更严格。这些问题没有影响当前线上页面,但说明测试代码也需要接受测试,不能因为它位于 test/ 目录就默认可信。
优化结果
最终线上冷启动复测结果如下:
| 指标 | 优化前 | 优化后 | 改善 |
|---|---|---|---|
| 第一篇文章可见 | 5124 ms | 476 ms | 约 90.7% |
| FCP | 4392 ms | 804 ms | 约 81.7% |
| DOMContentLoaded | 4172 ms | 810 ms | 约 80.6% |
| 完整加载 | 未单独记录 | 2116 ms | 不再阻塞正文显示 |
第一篇文章可见时间从 5 秒以上降到 0.5 秒以内,约快 10.8 倍。比数字更直观的变化是:现在打开页面时,正文直接出现,不需要盯着空白区域等动画。
线上复测还确认了这些结果:
- 桌面首屏完整显示三篇文章;
- 390 px 宽度下没有横向滚动;
- 站点 canonical 正确指向
https://snails.cafe/; - 已启用的主题资源全部由本站提供;
- Nginx 和所有哈希资源返回正常。
两份部署只保留一个源码源头
我的博客同时部署在 GitHub Pages 和腾讯云服务器。以前两边容易形成“看起来一样,实际配置不同”的状态。
这次调整后,GitHub 的 main 分支作为唯一源码源头:1
2
3本地电脑 ──push──> GitHub main
├──> GitHub Pages
└──> 腾讯云构建并部署
本地电脑只需要正常拉取源码:1
2
3git pull --rebase origin main
npm ci
npm run test:optimization
腾讯云也使用仓库级 Deploy Key 推送和拉取,不需要在服务器保存个人账号密码。
写在最后
这次优化没有换主题,也没有重做设计。真正有效的改动很朴素:别隐藏正文,减少不稳定的外部依赖,压缩异常资源,让缓存策略和文件版本保持一致。
AI Agent 节省了排查和验证时间,但最终结果仍然来自可复现的测量、自动化测试和上线后的真实复测。对个人博客来说,这套流程有点“重”,但它至少保证了一件事:下次改主题配置时,不需要再靠刷新页面和肉眼猜测有没有变快。