用 Codex 在 1 小时内恢复博客

博客很久没有认真维护了。

它不是完全坏掉,而是处在一种很熟悉的半失修状态:还能跑,但写文章别扭;依赖还能装,但 Hexo 已经从 6.3.0 走到了 8.1.2;主题 Icarus 又是自己 fork 后改过的版本,里面有暗色模式、静态友链、Giscus 和 highlight.js 主题切换。每一项单独看都不难,合在一起就很容易拖。

这次我干脆把 Codex 当成会读代码、会跑命令、会开浏览器验页面的工程搭档,让它把博客恢复到一个可以继续写、可以升级、也能放心发布的状态。

这类活很适合 Agent

这里难的不是想方案,而是有一堆小事必须做对:找配置、试依赖、修主题兼容、重新构建、开浏览器看真实页面。人负责判断方向和验收,Codex 负责把这些细碎步骤连续跑完。

先把写作结构救回来

原来的 Hexo 结构很标准:文章在 source/_posts/,图片集中放在 source/static/img/。生成网站没问题,但写文章时很别扭。Markdown 编辑器预览图片,经常因为路径是 /static/img/... 而看不到图。写博客本来应该低摩擦,结果每插一张图都要在“本地预览路径”和“站点生成路径”之间来回切。

讨论过 Post Asset Folder,也讨论过直接把文章放进 _posts/<slug>/index.md。最后我还是更想保留 Hexo 输出结构不变,另外维护一个面向写作的 raw/

1
2
raw/<slug>/index.md
raw/<slug>/assets/<image>

这样写文章时图片就跟文章放在一起,Markdown 里只要写:

1
![图片](assets/head.jpg)

预览器能直接看到,文章和素材也天然在同一个目录。发布前再由脚本同步到 Hexo 需要的结构:

1
2
source/_posts/<slug>.md
source/static/img/<slug>/<image>

这个同步脚本顺手做了两件很有用的事。第一,把 raw/ 里的本地图片路径改成站点路径 /static/img/<slug>/...;第二,把 JPEG、PNG、BMP、TIFF 转成 WebP,而且只有 WebP 更小时才采用。这样 raw/ 对写作友好,source/ 对 Hexo 友好,图片也会自动瘦身。

实际跑起来能看到类似这样的结果:

1
2
3
4
5
6
Normalized raw posts: 0
Synced raw posts: 15
Written source posts: 0
Published asset files: 105
Converted to WebP: 26
Estimated bytes saved: 3.51 MiB (32.7%)

这一步很典型:需求不复杂,但人工迁移文章、改路径、搬资源、避免漏文件,会非常烦。Codex 适合的地方就在这里。它可以先把现有 source/ 反向整理进 raw/,再写脚本,再运行同步验证结果。

升级 Hexo 8,麻烦在主题

博客原来用的是 Hexo 6.3.0,现在已经到 8.1.2。直接升级看起来只是一句:

1
npm install hexo@8.1.2

但真正麻烦的不是 Hexo 本体,而是主题。

我的 Icarus 不是普通上游版本。它 fork 后改过几轮,里面有静态 friendlinks、暗色模式、theme-switcher.js、highlight.js 浅色/深色主题联动、Giscus 暗色模式联动,还有一些文章页和样式细节。Codex 先检查升级风险:Hexo 8.1.2 要求 Node >=20.19.0,本机 Node 是 v25.8.1,这个没问题;再 fetch Icarus 上游,发现当前 fork 大概是 5 commits ahead46 commits behind

这时候最重要的判断是:不能直接合 upstream。

上游新版 Icarus 确实有兼容新 Hexo 的改动,但也改了主题结构,还加入了 PJAX 等新东西,而且没有我这边的 theme_switcher、静态友链和暗色样式。直接合上游,可能 Hexo 能跑了,但我自己改过的博客体验会被冲掉。

最后采用的是最小兼容补丁:保留当前 fork,只挑 Hexo 8 需要的改动。

hexo-log 的旧写法是:

1
const logger = require('hexo-log')();

新依赖下要改成:

1
2
const createLogger = require('hexo-log');
const logger = createLogger.default();

Hexo 内部 helper 路径也变了。旧路径:

1
require('hexo/lib/plugins/helper/date')

Hexo 8 下需要换成:

1
require('hexo/dist/plugins/helper/date')

还有主题自己的依赖检查。Icarus 会检查站点根目录安装的包版本,如果 themes/icarus/package.json 仍然写着 hexo: ^6.0.0hexo-pagination: ^2.0.0,升级后它自己会先拦住自己。所以依赖约束也要放宽到 Hexo 8 和新版 renderer/pagination。

这些补完之后再跑:

1
2
npm run clean
npm run build

构建通过,生成了 179 files

先在临时副本里踩坑

Codex 不是直接在主工作树里乱试。它先在临时副本里试装、跑构建、看失败信息,再把必要补丁应用回来。升级这种活,最怕边猜边改;能先隔离验证就舒服很多。

构建通过还不算完

静态站点构建成功,只能说明没有编译错误,不代表页面真的没坏。

这次我让 Codex 启动本地服务:

1
npx hexo server --port 4000

然后用 Chrome DevTools 打开真实页面检查。验收点不是随便看一眼首页,而是挑几个容易坏的地方:主页文章列表、WebP 图片、主题切换脚本;/links/ 的静态友链 DOM;from-typecho-to-hexo 里的 Giscus、highlight 主题链接和头图;还有 build-3dprinter-1{% message %}{% tabs %} 这类自定义 tag。

检查结果很具体:theme-switcher.js 在,#hljs-theme 在,本地图片正常加载且多数已经是 WebP,friendlinks DOM 在,Giscus script 在,没有 unknown tag,console 里也没有运行错误,只剩一些原本就有的 manifest warning。

这一步非常关键。很多升级看上去“构建成功”,实际上是页面脚本、异步组件或主题插件悄悄坏了。用浏览器看真实 DOM,比只看终端输出靠谱。

Giscus 暗色模式的小插曲

升级完成后,我又发现一个更隐蔽的问题:页面已经是 dark mode,但 Giscus 还是 light。

Chrome 里直接看 DOM,状态很清楚:

1
2
3
4
5
theme-mode=dark
highlight.js theme = atom-one-dark
body background = rgb(43, 43, 43)
giscus data-theme = light
giscus iframe theme = light

这不是 Hexo 8 直接造成的问题,而是原本暗色联动逻辑里有一个异步加载竞态。

主题脚本里有一个 changeGiscusTheme(),会向 Giscus iframe 发消息:

1
2
3
4
5
6
7
iframe.contentWindow.postMessage({
giscus: {
setConfig: {
theme
}
}
}, 'https://giscus.app');

问题是 Giscus 是 async script,它生成 iframe 的时间不固定。页面 DOMContentLoaded 时,iframe 可能还没出现;就算 iframe 已经出现,里面的 Giscus 脚本也可能还没 ready。这个时候发消息,消息会丢。

修复方式就是更耐心一点:Giscus 初始 data-theme 不再写死 light,改成 preferred_color_scheme;根据 cookie 或系统偏好计算当前主题;iframe 插入 DOM 后发一次主题消息,iframe load 后再发一次,前几秒短时间重试,确保 Giscus ready 后能收到;同时避免在 iframe 还处于初始 about:blank 时发消息,减少 console warning。

修完以后再用 Chrome 验证:冷刷新时 Giscus 自动进 dark;切到 light,Giscus 跟着变 light;再切回 dark,它也跟着变 dark;console 里不再出现 Giscus postMessage warning。

这种问题适合真实浏览器验证

这类 bug 光看代码很容易觉得“逻辑没问题”。真正的问题是时序:iframe 什么时候出现,内部脚本什么时候 ready,消息什么时候发出去。Codex 能反复刷新、看 DOM、看 iframe、改脚本、再生成验证,省掉了很多琐碎来回。

顺手把阅读体验修了一轮

博客能跑起来之后,还有一类更细碎的问题:页面能看,但读着不舒服。

一开始正文在暗色模式下对比度不统一:正文偏灰,标题又太亮;右侧 tags 卡片用了大块主题色,比正文还抢注意力;文章主栏只有一半宽,左右栏显得松;Read more 像普通浅色按钮,hover 后颜色也不搭;{% tabs %} 被正文全局链接样式误伤;{% message %} 在 dark mode 里还是偏浅的 Bulma 样式。

这些都不是大 bug,但每一个都要跑一轮小循环:找到真实页面元素,看 computed style,判断是 Bulma 默认样式、Icarus 主题样式,还是我自己的 patch 在覆盖;改 Stylus,重新生成 CSS,刷新页面,再在 light / dark 两种模式下看一遍。

最舒服的方式是直接告诉 Codex 我想要什么:正文不要糊,行高更松一点;暗色模式参考 GitHub/Giscus 的低疲劳色彩;主栏宽一点,左右栏窄一点;Tags 低饱和、按频率排序、数量多时折叠;Read moreGitHub Projects 保持一致;分类 hover 用主题红;tabs 不要吃正文链接样式;message 在暗色模式下也要有自己的颜色。

Codex 做这类活的好处是它不会改一行 CSS 就停下。它会打开页面,看 font-sizeline-heightborder-bottom-colorbackground-color,再截图确认视觉状态。比如正文一开始从 14px / 1.5 调到 16px / 1.7,看起来又有点大,最后改回 14px / 1.9。这个判断不是靠想象,而是在真实页面里看出来的。

最后阅读体验被整理到一个比较稳定的状态:暗色配色接近 GitHub dimmed / Giscus dark;正文使用 14px 字号和 1.9 行高;主栏在宽屏下扩大到 60%,左右栏收窄到 20%;Tags 卡片低饱和、按频率排序,并把多余标签折叠;Read moreGitHub Projects、Categories hover 使用同一套主题红交互;表格标题、archives 时间线、tabs、message 提示框都补上暗色模式细节。

为什么这次适合 Codex

这次其实不是让 AI 发明新方案。方向一直很明确:写作格式要舒服,source 输出还要兼容 Hexo,图片要自动 WebP,Hexo 要升到最新,fork 过的 Icarus 不能被上游覆盖,构建通过之后还要用浏览器看真实页面。

难点在于执行过程里有很多低价值但必须做对的小步骤。找文件、读配置、对照版本、查 breaking changes、做临时副本、跑 npm install、看报错、改几个小 require、跑 build、开 server、用 Chrome 看 DOM、看 console、查 iframe、反复刷新验证。人做起来会累,也容易因为嫌麻烦跳过验证。

Codex 的价值不是替我决定博客应该长什么样,而是把我已经想清楚的维护路径稳定地跑完。人只需要不断给判断:这个 fork 改过,不能直接合 upstream;这几个页面是验收重点;Giscus 暗色模式不对,需要追;raw/ 才是写作源,source/ 是生成结果。剩下大量机械但需要耐心的操作,就可以交给 agent。

人负责方向,Agent 负责闭环

这次最舒服的地方,是 Codex 不只会回答“应该怎么做”,它能在同一个工作区里读代码、改文件、跑命令、开浏览器、看结果,然后把反馈接回下一步。

最后

现在博客恢复到一个我比较满意的状态:写文章用 raw/<slug>/index.md,图片跟文章放在一起,Markdown 预览不再别扭;同步到 source/ 时会自动改路径,非 WebP 图片会按需转成 WebP;Hexo 升到了 8.1.2;Icarus fork 保留了原有暗色模式、友链和 Giscus 改动;本地构建通过,Chrome 真实页面也验证过;Giscus 暗色模式和阅读体验的细节也顺手修了一轮。

这种维护工作如果手动做,我大概率还会继续拖。因为它不难,但烦;每一步都要查、要跑、要看、要确认。

有了 Codex 之后,它变成了一件很顺滑的事:我负责给方向和验收标准,Codex 负责把一个个繁琐步骤串起来,直到页面真的恢复正常。

这大概就是我现在最喜欢用 AI agent 的场景:不是替我思考人生,而是替我把那些我明明知道怎么做、但一直懒得动手做的工程活,安安稳稳地做完。

Author

InariAimu

Posted on

2026-06-05

Updated on

2026-06-05

Licensed under