Nginx 子路径部署 + 生产环境 git 冲突处理
把 8gu_killer 挂在 www.ploverjoe.xyz/8gu 下,Nginx 子路径配置的三处同步修改,以及服务器上 git pull 时保留本地数据库解决二进制文件冲突。
背景
今天做了一件一直在计划的事:把 8gu_killer 答题平台挂到主域名 www.ploverjoe.xyz 的子路径下,和博客共存。
域名根路径 / 跑博客(Next.js, 端口 3000),新加 /8gu 子路径跑答题平台(Vite + React 前端, Express 后端 3002)。
Nginx 子路径配置
问题:子路径的资源全都 404
一开始只加了一个 location /8gu,结果 HTML 渲染出来了,但 /@vite/client、@react-refresh、/src/main.jsx 全部 404。原因是 Vite 生成的资源路径是根绝对路径(/...),浏览器请求 /assets/index.js 时命中 location / → 被转发到博客的 3000,而博客没有这些路由。
三处同步修改
子路径部署有三个点必须同步改,少一个就崩:
① Vite base 配置 — 所有构建资源的路径前缀
// vite.config.js
export default defineConfig({
base: '/8gu/', // 让产物 HTML 中的资源路径变为 /8gu/assets/...
// ...
})
② React Router basename — 前端路由的根路径
// src/main.jsx
<BrowserRouter basename="/8gu">
<App />
</BrowserRouter>
③ Nginx location 块 — 反向代理分类
# 规范 301:/8gu → /8gu/
location = /8gu {
return 301 /8gu/;
}
# 前端静态资源代理到 Vite 开发服务器
location /8gu/ {
proxy_pass http://localhost:3001;
# ... WebSocket 支持、请求头
}
# API 独立 location,避免和博客的 /api 冲突
location /8gu/api/ {
proxy_pass http://localhost:3002/api/;
# ... 同上
}
这里有个关键细节:proxy_pass 末尾带路径(/api/)会替换匹配的前缀——所以 /8gu/api/captcha → http://localhost:3002/api/captcha。如果不带路径(http://localhost:3002),则保留完整 URI(/8gu/api/captcha 原样发过去),后端就会 404。
API 路径冲突
最初前端的 axios baseURL 是 /api,请求 /api/captcha → 被 Nginx 的 location / 转发到博客的 3000 端口。博客自己有 GET /api/contact 路由,不能把整个 /api 让给 8gu。
解决方案:前端 API 基路径改为 /8gu/api,Nginx 加独立 location 处理,同时 Vite 的 dev proxy 也要同步改:
// vite.config.js
server: {
proxy: {
'/8gu/api': {
target: 'http://localhost:3002',
rewrite: path => path.replace(/^\/8gu\/api/, '/api'),
},
},
},
// src/lib/api.js
const api = axios.create({ baseURL: '/8gu/api' });
服务器同步:git pull 冲突
Nginx 配置改完、代码也提交推送了,上服务器拉取最新代码时遇到 git 冲突:
root@VM-0-12-ubuntu:/home/ubuntu/8gu_killer# git pull
Updating 382fd2b..4449ee8
error: Your local changes to the following files would be overwritten by merge:
dist/index.html
server/data.sqlite
Please commit your changes or stash them before you merge.
Aborting
两个冲突文件:dist/index.html(前端构建产物)和 server/data.sqlite(数据库)。服务器上有用户在后台加的练习题,数据库不能丢。
stash → stash pop 冲突
先 stash 再 pull:
git stash
git pull
git stash pop
git pull 顺利 Fast-forward 合并到 4449ee8,但 git stash pop 报二进制文件冲突:
warning: Cannot merge binary files: server/data.sqlite
Auto-merging server/data.sqlite
CONFLICT (content): Merge conflict in server/data.sqlite
Auto-merging dist/index.html
CONFLICT (content): Merge conflict in dist/index.html
The stash entry is kept in case you need it again.
为什么二进制文件不能自动合并
git 的 merge 算法基于 diff3——对比三个版本(共同祖先 + 两侧改动)。这对文本文件按行 diff/merge 有效,但 SQLite 数据库文件是二进制格式,git 没有"行"的概念,只能标记 "两边不一样,你选一个"。
就算本地的 data.sqlite 只是新增了 10 道题,远程改的是其他表,两边完全不冲突——git 也不知道。它只知道两个二进制文件 SHA-1 不同。
解决方案:--ours / --theirs
核心诉求:保留服务器数据库,其他同步远程。
需要理解冲突状态下 --ours 和 --theirs 的语义:
| 选项 | 从谁拿 | 当前场景 |
|---|---|---|
--ours | HEAD(当前分支) | 远程的 4449ee8 |
--theirs | stash(暂存内容) | 本地的改动 |
# 前端构建产物 → 用远程版本(重新构建就行)
git checkout --ours -- dist/index.html
# 数据库 → 保留服务器本地的数据
git checkout --theirs -- server/data.sqlite
# 标记冲突已解决
git add dist/index.html server/data.sqlite
# 清理 stash
git stash drop
最终状态:
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
modified: package-lock.json
modified: server/data.sqlite
数据没丢,代码也同步了。最后 reload Nginx 生效。
关键收获
1. 子路径部署的三处同步修改
Vite base → React Router basename → Nginx proxy_pass,三处有一处不统一就崩。特别是 Nginx 的 proxy_pass 末尾是否带路径决定了 URI 替换行为,最容易踩坑。
2. 多应用共用域名的 API 隔离
博客有 /api/contact,8gu 有 /api/captcha——根路径下 /api 冲突。子路径化 API(/8gu/api)是最干净的解决方案。
3. 生产服务器数据库是"有状态"的
和开发环境不同,生产数据库有真实用户数据,不能被 git 覆盖。长期来看 data.sqlite 应该进 .gitignore,通过迁移脚本管理 schema 变更。