2546 字
13 分钟
卡了我半天的那些坑(顺便聊聊 vibe coding)

这是「搭建这个博客」系列的第 3 篇。前两篇是 1 从零搭一个博客 和 2 给静态博客接一个能登录的后台。

前两篇讲的是”怎么做对的”。这篇讲”怎么错的”——而且我保证,这一篇比前两篇有用。

顺便说一句:整个博客是我和 AI 协作做的(也就是现在说的 vibe coding)。下面这些坑,有一半是它帮我找到的,也有一半是它先带偏、后来才被我拽回来的。最后一节专门聊这件事。

坑 1:一个字符抄错,GitHub 直接给我 404#

配 OAuth 的时候,我从 GitHub 页面上把 Client ID 手动敲进了 Cloudflare 的环境变量。

然后点登录:打开 GitHub 授权页 → 404,一只沙漠里的猫告诉我”This is not the web page you are looking for”。

最要命的是这个错的表现形式,它几乎把所有常规排查方向都指向了错误的地方:

  • 如果我没登录 GitHub,打开那个授权地址,它会正常跳转到登录页——看起来”授权页是通的”!
  • 只有登录之后,GitHub 才真正处理这个授权请求,这时才发现找不到这个应用 → 404

于是接下来的一个多小时,我依次怀疑并排查了:网络被墙、浏览器缓存、油猴脚本、代理软件、DNS、hosts……一个都不是。

真正的原因是:我把第 5 位的小写字母 l 敲成了数字 1,还漏掉了一个字符——19 位,而正确的是 20 位。

教训:

  • Client ID / Client Secret 这类东西,永远用复制按钮,不要手打。它们的字符集里全是 l / 1 / I、O / 0 这种长得一模一样的东西。
  • 自检方法:这类 client ID 通常是 20 位。长度不对,基本就是抄错了。
  • 排查顺序:先怀疑自己刚输入的东西,再怀疑环境。我是反着来的,所以白花了一个多小时。

坑 2:无刷新跳转,把页面卡在了半截#

Fuwari 用 Swup 做无刷新页面切换——点站内链接时不整页刷新,只把 <main> 里的内容换掉。体验确实好,但有个前提:目标页面得有 <main> 容器。

而后台页面(CMS)是独立的单页应用,压根没有 <main>。

于是点导航里指向后台的链接时,浏览器控制台里出现:

[swup] Container missing in incoming document: main
[swup] Container mismatch, aborting

Swup 一看容器对不上 → 把整个跳转中止了 → 页面就停在”切了一半”的状态,导航栏上打开的浮层也跟着挂在那儿不动。

这也是我一开始判断错的地方:我以为是”面板关闭逻辑写错了”,改了好几轮都没用。因为问题根本不在关闭逻辑,而在”跳转失败了”。

解决:给指向后台的链接加一个 data-no-swup 属性(这是 Swup 的忽略标记),让它老老实实整页加载。后台本来就是独立应用,整页加载才是对的。

坑 3:bfcache 让”已经关掉”的面板又活了#

我做了个导航栏浮层面板,逻辑是”打开时关掉别人,离开页面时关掉自己”。听起来没毛病。

但浏览器前进后退的时候出问题了:

你点了链接离开 → pagehide 触发 → 代码把状态设成「关闭」
↓ 但框架是异步更新 DOM 的
浏览器立刻把页面冻结进 bfcache
(DOM 上还是「打开」的样子)
↓ 你按了后退,页面从缓存里被恢复
状态已经是「关闭」→ 框架认为没变化 → 永远不再渲染
↓
面板就永久卡在打开的样子,只有刷新能救

关键点在于:bfcache 冻结页面时,JS 状态和 DOM 会一起被原样保存,而且 pagehide 之后留给你的时间非常短。你改了状态,但框架还没把 DOM 更新出来,页面就已经被冻住了。

解决:不要依赖框架重渲染——直接操作 DOM 类名(classList.add(...)),并且在 pageshow 时再对齐一次。

普遍启示:任何”离开页面时改状态”的逻辑,都要考虑 bfcache。这不是框架的 bug,是浏览器的正常行为。

坑 4:改了环境变量,必须重新部署#

这个纯属不知道就会踩:Cloudflare 不会把新填的环境变量自动应用到已经跑着的 Functions 上。

现象是:变量明明填对了,接口却一直报”未配置”。去部署页面点一次”重新部署”就好了。

坑 5:“代码没生效”十有八九是浏览器在跑旧代码#

调试期间反复出现”我明明改了,怎么还是老样子”。后来发现是两类原因:

  • 开发服务器的热更新(HMR)能更新模板和状态,但不重新绑定已经挂载的组件的事件逻辑。你改了事件处理,页面不整页刷新就还是旧的。
  • 浏览器前进后退会恢复缓存页面,看起来就像”我刚改的东西消失了”。

解决:我在组件里加了一行版本标记——window.__adminMenuV = "v6"。不确定当前页面跑的是哪一版,控制台敲一下就知道。就这一行,省了后面大量的”到底生效了没”的扯皮。

坑 6:我自己的测试,把我自己骗了#

这条最值得说,因为它跟代码无关,跟”怎么确认问题真的解决了”有关。

修完 bfcache 那个 bug 之后,我写了个自动化测试来验证。测试通过了。但线上问题还在。

原因是我用脚本合成的 pagehide / pageshow 事件去模拟”前进后退”。而合成事件根本不会触发真正的页面冻结——于是 DOM 更新有充足的时间跑完,那个 bug 自然复现不出来。测试给了我一个绿色的假象。

后来我把测试改成两条硬要求:

  1. 用真实的导航:location.href 真的跳走,再 history.back() 真的回来
  2. 断言”这次确实走了 bfcache”:在 pageshow 里记录 event.persisted,如果它没变成 true,这次测试就不算数

改完之后测试才真正有价值——它才真的在测那条会出问题的路径。

教训:测试通过 ≠ 问题解决,尤其是当测试自己构造了运行环境的时候。多问一句:“我这么测,有没有可能绕过了真实路径?“

关于 vibe coding:它改变了什么,又要求什么#

这个博客是我和 AI 协作做的。整个过程里我几乎没手写代码,但每一步都在做判断。

它确实很能打#

  • 一个下午搞定整个站点的汉化、配置、若干交互组件
  • 几十行写出并部署了 OAuth 代理,还顺手做了 CSRF、白名单、最小权限
  • 排查问题时能快速给出可验证的假设(例如”直连返回 302、走代理返回 404,所以问题在本机代理”——这个假设完全正确,一下省了我半小时)

放在以前,这些事情的时间成本大概是这里的几倍。

但它有硬性要求#

1. 验证权必须留在人手里。 它会非常自信地给结论,包括错的结论。这一轮里它先后把问题归因于”缓存""浏览器扩展""本机代理”,全都不是。关键结论自己复现一遍,这是底线。

2. 凭据永远不进仓库、不进聊天。 Client Secret、访问令牌这类东西只能待在部署平台的环境变量里。我中途把 secret 截图发出去过,事后只能作废重发。能不发就不发。

3. 小步提交,保持可回滚。 每次改动独立成一次提交,写清”改了什么、为什么”。出问题时能一眼定位到是哪一步引入的——这一条救过我不止一次。

4. 让它承认”我不确定”。 比”答错了”更危险的是”答得很顺但你没法验证”。所以我会强制它给依据:日志、状态码、测试结果。没有证据的结论,一律当假设。

5. 关键代码你得看得懂。 不用逐行懂,但安全相关的必须懂:谁能拿到 token、白名单在哪校验、secret 存在哪。看不懂的地方,就不该让它上线。

6. 文档要写”为什么”。 我把踩过的坑和排查方法都写进仓库的 docs/ 了。三个月后的我,或者下一个接手的人,能少走一遍。

7. 接受”它会自信地犯错”,并据此设计流程。 所以要有版本标记、要有可复现的测试、要有能回滚的提交历史。这些不是仪式感,是保险。

我的结论#

vibe coding 没有让”懂技术”变得不重要。恰恰相反,它把人的角色从”实现者”推到了”审查者”——而审查需要的判断力一点不比写代码少:

  • 看得出方案哪里不对
  • 读得懂报错在说什么
  • 问得出”这个测试有没有绕过真实路径”
  • 守得住”凭据不进仓库”这种底线

它能极大压缩”实现”的时间,但压缩不了”判断”的责任。

最后#

这三篇写下来,其实我想说的就一句:搭博客这件事,现在真的不难了。难的是那些”看起来好了其实没好”的时刻——而对付它们,靠的不是更聪明的工具,是更笨的验证。

如果你也在搭,希望这几篇能帮你少花一个多小时。


系列导航:1 从零搭一个博客 · 2 给静态博客接一个能登录的后台 · 3 卡了我半天的那些坑

卡了我半天的那些坑(顺便聊聊 vibe coding)
https://56076077.xyz/posts/blog-pitfalls/
作者
Nskdfh
发布于
2026-09-19
许可协议
CC BY-NC-SA 4.0