mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1443 字
4 分钟
个人博客在服务器部署遇到的问题
2026-04-20
2026-04-21

别被开发环境骗了#

本地 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 模式下请求都走本地,看不出来。

修法:

astro.config.mjs
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,配合逐级退化:

  1. country + state 查不到 → 用 state + country 拼成 q 再查
  2. 还查不到 → 退成只查国家中心点
  3. API 完全挂 → 用国家名本身作为唯一可选政区

另外 API 层的 POST / PUT 原本没 try/catch,错误直接抛出就变成 Astro 的 HTML 错误页,前端 response.json() 解析崩,只能给用户看个泛化的”保存失败”。现在把它们都包起来、返回 JSON error,至少能看到真实原因。

3. 后台删了的文章,前台又活过来了 — git 的一次精准背刺#

表现很诡异:后台只剩 1 篇,重建后前台显示 5 篇。多出来的恰好就是我之前删掉的那 4 篇。

时间线:

  1. 后台删文章 → .md 文件删掉、DB 记录删掉 ✅
  2. 推了点代码要上服务器更新
  3. 服务器上 git stash && git pull && git stash drop
  4. 重建,前台 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 是从它跑在别人电脑上那一刻开始的。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

部分信息可能已经过时

目录