我把"时间胶囊"部署上腾讯云宝塔,跨域从根源消失了

0 阅读6分钟

我把"时间胶囊"部署上腾讯云宝塔,跨域从根源消失了

我做了个叫 FUTURE_CAPSULE(时间胶囊) 的全栈小应用:可以给未来的自己留一段话、设一个解锁时间,倒计时结束前它被锁着显示 [ LOCKED ],解锁后才长出文字。前端是 React 的瀑布流页面,后端是 Node + express + MySQL。

本地开发一切正常。但我有一套前端 localhost:5173,一套后端 localhost:3001,于是逃不掉那个经典报错:

from origin 'http://localhost:5173'
has been blocked by CORS policy

又是跨域。我一度以为"前后端分离 = 必须和 CORS 搏斗"。直到我把它部署到一台腾讯云轻量服务器上用宝塔面板跑起来,发现:

部署完之后,跨域问题消失了。我一行 CORS 代码都没改,甚至没写。 只是换了个入口。

这篇文章就用这个真实的"时间胶囊"项目,讲清楚那个反直觉的真相:Nginx 反向代理把前后端收进同一个源,跨域从根本前提上就不成立了。


为什么本地会跨域:两个源

跨域不是后端的问题,是浏览器的同源策略拦下来的——协议、域名、端口三者必须完全一致才算"同源"。

本地开发时我们有两个源:

前端(浏览器访问)   http://localhost:5173   ← vite 开发服务器
后端(数据接口)     http://localhost:3001   ← node 接口
                  ↑ 仅端口不同 = 不同源

端口不同就是不同源,浏览器直接拦截。所以本地代码里我不得不开 CORS 兜底:

// server/src/app.ts —— 本地开发我开了 CORS
app.use(cors());

但注意,这个 cors() 是针对"两个源"续命用的。部署之后,根本不该再靠它——这正是很多人绕不过去的误区。


部署后:Nginx 成了唯一入口

npm run build 把前端 React 打成了 dist/ 静态文件;后端 node 跑着接口。但生产环境没有 vite 这层代理了。谁管请求该去哪?

Nginx。 它一个端口,统管前后端:

用户 → http://1.13.20.252             (只这一个入口,只开 80)
                │
        ┌───────┴────────┐
        │      Nginx       │
        │                 │
   ┌────┴─────┐     ┌─────┴────┐
   │ 静态请求    │     │ 动态请求    │
   │ / → dist/ │     │ /api → node│
   │ (前端页面) │     │ (3001)     │
   └──────────┘     └──────────┘

关键:用户从头到尾只打 http://1.13.20.252 这一个源——

前端页面 html      http://1.13.20.252/
请求数据 /api      http://1.13.20.252/api/capsules
                  ↑ 同一个协议 + 同一个域名 + 同一个端口

端口相同 = 同源。跨域的前提没了,于是 cors() 变成了死代码。

这就是最新颖的地方:我这个项目的客户端 .env 里直接写 VITE_API_URL=http://1.13.20.252(同源),根本不用填后端子域名;服务器里的 app.use(cors()) 在生产环境纯属冗余——nginx 已经把源统一了。


Nginx 配置:其实就两段 location

腾讯云宝塔里,我为这个站点做的 nginx 配置,核心就两个 location:

server {
    listen 80;
    server_name 1.13.20.252;

    # 1. 静态请求:直接把前端打包产物吐给浏览器
    location / {
        root /www/wwwroot/future_capsule/dist;
        index index.html;
        try_files $uri $uri/ /index.html;   # 🔑 前端路由回退,别漏
    }

    # 2. 动态请求:/api 反代给 node 后端
    location /api/ {
        proxy_pass http://127.0.0.1:3001/;  # 🔑 末尾斜杠,剥掉 /api 前缀
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

拆开看 Nginx 的"三件事":

Nginx 干的活对应配置时间胶囊里的作用
接受请求listen 80对外唯一入口
发静态文件location / → root .../dist把前端瀑布流页面返回给浏览器
转发给后端location /api/ → proxy_pass把 /api/capsules 转给 node 3001

一句话记忆:静态的走 Nginx 本地文件,动态的走 Nginx 转发给 node。Nginx 一分为二,前端后端各取所需,且全程同源。

反代时最容易踩的坑:/api 前缀剥不剥

宝塔默认给的 proxy_pass 很可能不带末尾斜杠。它的行为差异,正是新手翻车点:

# ❌ 不带斜杠:转发时保留 /api 前缀
proxy_pass http://127.0.0.1:3001;
# → node 收到 /api/capsules(后端路由是 /capsules,404)

# ✅ 带斜杠:剥掉 /api 前缀
proxy_pass http://127.0.0.1:3001/;
# → node 收到 /capsules,正好命中

我的后端路由是 /api/capsules 下挂着 /(见 routes/capsule.ts):浏览器打 /api/capsules,nginx 剥掉 /api 后转给 node 的 /capsules,配得正正好。

⚠️ 坑:proxy_pass 结尾有没有 /,决定 nginx 是否剥掉 location 匹配到的前缀。前后端部署时先想清楚你后端路由带不带 /api,再决定斜杠要不要。


Nginx 在整条访问链里站在哪

放回"用户敲网址到拿到数据"的完整链路,Nginx 只负责其中一环,但它是关键的一环:

浏览器输入 http://1.13.20.252
      │ 1. DNS:把域名解析成公网 IP
      ▼
腾讯云安全组 → 只放行 80 / 443        ← 云厂商网络层"大门保安"
      │
      ▼
宝塔/系统防火墙 → 同样只放行必要端口   ← 服务器内部"楼栋门禁"
      │
      ▼
Nginx(80 端口)                        ← "前台接待",真正的入口
      ├─ 静态请求 → dist/ 文件
      └─ /api 请求 → 反代 → node 3001
                              ↓
                        node → MySQL 「重型以太」
环节位置类比
DNS全球分布式查电话簿找地址
安全组腾讯云网络层小区大门保安
防火墙服务器系统内部楼栋门禁
Nginx服务器 80 端口前台接待,分流找人

记住这条链路的价值:出问题时你知道去查哪一环。 /api 接口 502,先看 node 逻辑对不对、再看 nginx 反代地址;页面 404,先看 dist/ 有没有传上去、路由回退写没写。分清了层次,排查不再瞎试。


用腾讯云宝塔,部署变成"点按钮"

命令行部署 Linux 对新手劝退(装 nginx、配置、传文件、改权限、看日志,每步都可能卡)。我用的腾讯云轻量服务器 + 宝塔面板(BT Panel),把命令行操作变成了可视化点击:

纯命令行玩法:ssh 登录 → 各种命令行 → 很累
宝塔玩法:网页后台点几下 → 完成

我在宝塔网页后台做的事:

目的宝塔里的动作
装 Node.js、nvm 多版本「软件商店」装 Node.js 版本管理器,指针切版本
装 Nginx「软件商店」装 nginx
建数据库「数据库」新建 future_capsule 库(我分了 dev/production 两套,开发线上互不影响)
放代码/www/wwwroot/future_capsule 上传 dist + server
配反代、配 HTTPS可视化编辑站点 nginx 配置、一键部署 SSL 证书

而后端要接 MySQL,就在 .env 里配置宝塔建好的连接信息:

# server/.env
DB_HOST=你的宝塔MySQL主机IP
DB_PORT=3306
DB_USER=future_capsule
DB_PASSWORD=你的密码
DB_NAME=future_capsule
PORT=3001

建议:自己玩 / 中小项目,宝塔面板上手最快,能把精力放在"部署逻辑"而不是 Linux 命令。规模大了再回命令行也不迟。


结尾

那句让我愣住的观察,现在你明白了:

跨域不是我"解决"的,是我让它"不发生"的。 本地用 vite 代理或 CORS,是因为有 5173 和 3001 两个源;生产用 nginx 反代,是把前后端统一进同一个源。

一句话收尾:

本地开发用 proxy / CORS,是因为有两个源;生产部署用 Nginx 反向代理,是把前后端收进同一个源——源统一了,跨域就根本不存在。

下次再有人说"前后端分离必然跨域",你可以接一句:那是你没在它们前面放一个 Nginx。

留个开放问题:我这套 proxy_pass http://127.0.0.1:3001/ 是把 /api 剥掉前缀转发。如果后端同时有 user、order 多个服务在不同端口,nginx 该怎么配合 location 做按路径路由?评论区聊聊你的方案。