别被开发环境骗了
本地 pnpm dev 跑得飞起,一切岁月静好。真正把站点塞进宝塔 + nginx + PM2 这套组合后,才发现开发环境自动帮你绕开了一堆坑。今晚一次性撞了五个,顺手记一下。
1. 后台所有删除操作一律 403 — Astro 的隐形”防火墙”
症状:本地 admin 随便删,生产上点哪个都是 Delete failed。nginx 日志里是 DELETE ... 403,但我自己 nginx 没写任何 method 限制。
直接 curl http://127.0.0.1:4321/api/admin/xxx -X DELETE,Node 端返回:
Cross-site DELETE form submissions are forbidden这是 Astro 5 的新默认值 security.checkOrigin: true。它把所有 state-changing 请求(PUT / DELETE / POST form)都当可能的 CSRF,要求 Origin 和 Host 匹配。问题是反代之后 Node 收到的 Host 是 127.0.0.1:4321,Origin 是 https://mydomain.com,两者永远对不上,直接拒。
dev 模式下请求都走本地,看不出来。
修法:
security: { checkOrigin: false,},我这边 session cookie 已经是 httpOnly + sameSite=lax + secure,等效的 CSRF 防护已经有了,关掉这层不会裸奔。
2. 国外 API 在国内”活得不太好” — 足迹里填香港就崩
两个典型场景都是同一个病:
- countriesnow.space 拿到 “Hong Kong” 后直接
error: true或非 2xx 返回 → 前端提示加载行政区失败 - Nominatim(OpenStreetMap 搜索)用 “省=Hong Kong + 国=Hong Kong” 搜地址搜不到 →
保存失败
开发时我填的都是”广东省” “东京都”这种标准二级行政区,全绿。实际用户会填港澳台、梵蒂冈、摩纳哥这种”省=国”的地方,两个 API 都不配合。
修法:每一步外部调用都包 try/catch,配合逐级退化:
country + state查不到 → 用state + country拼成 q 再查- 还查不到 → 退成只查国家中心点
- API 完全挂 → 用国家名本身作为唯一可选政区
另外 API 层的 POST / PUT 原本没 try/catch,错误直接抛出就变成 Astro 的 HTML 错误页,前端 response.json() 解析崩,只能给用户看个泛化的”保存失败”。现在把它们都包起来、返回 JSON error,至少能看到真实原因。
3. 后台删了的文章,前台又活过来了 — git 的一次精准背刺
表现很诡异:后台只剩 1 篇,重建后前台显示 5 篇。多出来的恰好就是我之前删掉的那 4 篇。
时间线:
- 后台删文章 →
.md文件删掉、DB 记录删掉 ✅ - 推了点代码要上服务器更新
- 服务器上
git stash && git pull && git stash drop - 重建,前台 5 篇 ❌
git stash 确实把”删了 4 个 .md”这个改动存进了 stash,git pull 拉完以后 git stash drop 直接把 stash 丢了,那 4 个已经被删的 .md 就以 git HEAD 的形态被放回工作区。同时 sync-db-posts.mjs 只做”从 DB 往文件系统写”,不做”清理 DB 里没有的孤儿文件”,于是多出来的就留下了。
修法有两层:
- 治标:
sync-db-posts.mjs同步完加 prune 逻辑,DB 没有的.md/.mdx一律删掉,强制让 DB 成为唯一真相源。 - 治本:
src/content/posts/**/*.md(x)加进.gitignore,4 个遗留文件git rm --cached,以后git pull再也不会把后台删的文章搬回来。
教训:任何由后台管理的数据都不该被 git 跟踪。public/uploads/、data/site.db 我一开始就 ignore 了,但 src/content/posts/ 这种历史遗留的”种子文章”混在一起,就漏了。
4. “距离上次编辑:16 小时” — 时区经典坑
刚改的文章,底部显示”16 小时前编辑”。
DB 里 updated 存的是完整 ISO 时间戳(2026-04-20T10:00:00.000Z),但 sync-db-posts.mjs 往 frontmatter 写的时候用的是:
yamlDate(value).toISOString().slice(0, 10) // "2026-04-20"直接把时分秒砍了。Astro 读 2026-04-20 时把它当成 UTC 午夜,换算成 UTC+8 就是当天凌晨 8 点——于是不管几点改,显示时间都锚定在当天 08:00,跟”现在”一减最多能差 16 小时。
顺便看了一眼运行时 content-sync/posts.ts,那边一直用的是 yamlDateTime(完整时间戳),两条路径不一致。
修法:prebuild 脚本里 updated 改用 yamlDateTime 对齐运行时。
5. 首页切到文章页会闪一下白色 — CSS 加载竞速
Swup 做 SPA 切页的时候,新页面的外链 variables.css 偶尔比 DOM 替换慢一拍。那一瞬间 CSS 变量全都读不到默认值,--page-bg 退化成白色,整个深色主题闪一下浅色再恢复。
本地基本看不到,因为 Vite 缓存热得一塌糊涂,CSS 永远先到。
修法:把最关键的 :root / :root.dark / body.wallpaper-transparent 变量直接写进 <style is:inline>,每页的 HTML 都自带一份,无论外链什么时候到,关键变量一定在。
总结
Dev 环境跟线上差的不只是”慢一点”:
| 类别 | dev 不会暴露 |
|---|---|
| 安全策略 | CSRF、CSP、SameSite cookie 在本地 127.0.0.1 全部无感 |
| 网络环境 | 国外 API 在国内的超时 / rate limit / 直接返错 |
| 部署流程 | git pull / stash / pm2 reload 任何一步都可能搅乱运行时状态 |
| 资源加载顺序 | 冷缓存 + 弱网络 = CSS 比 HTML 晚到 |
| 时区 | 服务器 UTC、浏览器本地、数据库 ISO 三者对不齐 |
以后上线前至少过一遍:
- 反代后起一个真实环境(哪怕本地跑 nginx)
- 用慢网络 profile 切几次页
- admin 的”增改删”各做一次完整回放,重建 + 刷新都看一眼
- 把所有会被运行时写入的路径都 ignore 掉,别留在 git 里
代码写完跑通只是开始,真正的 debug 是从它跑在别人电脑上那一刻开始的。
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时






