当前位置: 首页 > news >正文

【PaperFlow】前端部署到子路径之后,怎么解决路径问题

如果前端是直接部署在域名根路径下,很多配置确实容易“默认工作”。
但我们当时一把前端挂到子路径,问题就一下子多起来了。比如:

https://your-domain.example/paperflow/

问题就会突然变多:

  • 浏览器直接访问/时该跳去哪;
  • 打包后的静态资源到底从/assets/...取,还是/paperflow/assets/...取;
  • 刷新/paperflow/posts会不会变成 404;
  • React Router 到底要不要配basename
  • /api/...是让前端直连,还是让 Nginx 代理。

先说明一下,这篇里不会放真实公网域名、服务器 IP、管理后台地址、密钥、邮箱账号之类的信息。
像端口、上游地址这类内容,只保留“怎么接”的结构,不保留可以直接拿去扫机器的细节。

我们最后能把这套路径跑顺,不是因为哪一层特别高深,而是因为这几层终于开始说同一种路径语言了。

1. 我们先把前端入口统一成/paperflow/

前端的vite.config.ts里,最关键的其实就是base这一层。为了公开发帖不暴露不必要的部署细节,我这里只保留结构:

exportdefaultdefineConfig({base:"/paperflow/",plugins:[react()],server:{open:"/paperflow/",proxy:{"/api":{target:process.env.VITE_API_BASE??"<local-api-base>",changeOrigin:true}}}});

这里的base: "/paperflow/"决定了两件事:

  • 打包后的静态资源路径以/paperflow/为前缀;
  • 开发态打开页面时,也优先从/paperflow/进入。

我们当时就是先把这一步钉住,因为它相当于先声明:

这个前端应用不是部署在网站根路径,而是部署在/paperflow/下面。

只要这件事先说清楚,后面 Nginx 和路由层才有共同的参照物。

2. React Router 这层不能凭感觉,必须跟base一起走

apps/paperflow-web/src/main.tsx里,我们没有把路由前缀写死,而是直接从import.meta.env.BASE_URL派生:

const rawBaseUrl = import.meta.env.BASE_URL; const routerBasename = rawBaseUrl.endsWith("/") ? rawBaseUrl.slice(0, -1) : rawBaseUrl; const normalizedBasename = routerBasename && routerBasename !== "/" ? routerBasename : ""; const currentPath = window.location.pathname; const pathWithSlash = `${normalizedBasename}/`; if (normalizedBasename && currentPath !== normalizedBasename && !currentPath.startsWith(pathWithSlash)) { const nextPath = currentPath === "/" ? pathWithSlash : `${normalizedBasename}${currentPath}`; window.location.replace(`${nextPath}${window.location.search}${window.location.hash}`); } <BrowserRouter basename={routerBasename || "/"}> <App /> </BrowserRouter>

我们后来回头看,最省心的一点就在这儿:前端路由前缀不是手工写两份,而是直接复用Vite base的结果。

这能避免一个很常见的问题:

  • 打包配置是/paperflow/
  • BrowserRouter还在按/解释路由;
  • 最后跳转、刷新、资源加载全乱套。

另外,这段代码里还有一个我们自己觉得挺有用的小处理:
如果当前地址没有落在basename下,就主动重定向过去。

这意味着用户即使从根路径或者别的裸路径进入,也能被收回到统一入口。

3. Nginx 这边不只是放静态文件,它还在帮我们把入口收住

docker/nginx/paperflow.conf里,最关键的几个location大概是这个结构。这里同样省略了不必要的真实部署细节,只保留路径逻辑:

location = / { return 302 /paperflow/posts; } location /api/ { proxy_pass http://<gateway-upstream>; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /paperflow/assets/ { rewrite ^/paperflow/(.*)$ /$1 break; try_files $uri =404; } location /paperflow/ { rewrite ^/paperflow/(.*)$ /$1 break; try_files $uri $uri/ /index.html; }

我们那时候就是靠这几段,硬把几个入口关系拉直的。它们其实分别在解决三件不同的事。

第一件,根路径跳转。
用户访问/时,不是直接给一个空白首页,而是明确跳到/paperflow/posts

第二件,接口代理。
所有/api/请求都先经过 Nginx,再转给网关这一层。公开文章里不需要写出真实上游地址,知道“浏览器不直接碰后端服务”这件事就够了。

第三件,子路径静态资源和 SPA 刷新。
/paperflow/assets/解决打包后资源路径问题,/paperflow/下的try_files ... /index.html则保证前端路由刷新不会被 Nginx 当成真实文件查找失败。

这三层缺一层都不行。

4. 为什么/paperflow/assets/还要单独拎出来

我们第一次看到这段配置时其实也会疑惑:

  • 既然/paperflow/已经有try_files
  • 为什么/paperflow/assets/还要单独写一段。

后来真正踩到白屏问题之后,这个原因就很现实了:
静态资源和前端路由虽然都挂在/paperflow/下,但语义完全不同。

  • /paperflow/posts/paperflow/login这类路径,是 SPA 路由;
  • /paperflow/assets/index-xxxxx.js这类路径,是真实静态文件。

如果不把资源目录单独拿出来,最糟的情况就是:

  • 资源请求没命中真实文件;
  • 又被 fallback 到/index.html
  • 浏览器收到的是 HTML,却以为自己在加载 JS;
  • 页面直接白屏。

所以资源路径必须明确按真实文件处理,不能和前端页面路由混在一起。

5./api为什么不并到/paperflow/api下面

我们最后保留的是这种结构:

location /api/ { proxy_pass http://<gateway-upstream>; }

而不是:

/paperflow/api/

我们最后保留/api/...这条独立路径,是因为这样更容易把前端入口和后端入口拆开理解:

  • /paperflow/...属于前端页面和静态资源入口;
  • /api/...属于后端接口入口;
  • 两者都由同一个 Nginx 暴露给浏览器,但语义上不混在一起。

这也和前端开发态是一致的。
vite.config.ts里本地开发代理本质上也是这个结构:

proxy:{"/api":{target:process.env.VITE_API_BASE??"<local-api-base>",changeOrigin:true}}

也就是说,无论开发态还是生产态,前端认的都是同一个接口前缀:

/api/...

这对我们这种学生项目特别重要,因为开发态和部署态如果连接口前缀都不一样,后面排查的时候特别容易把自己绕晕。

6. 后端这边也得继续守住/api/v1这层边界

前端和 Nginx 路径收住了,后端也要继续保持一致。

user-servicecontent-serviceapplication.yml都定义了:

server:servlet:context-path:/api/v1

而网关这边又按/api/v1/...这套路径做路由分发:

-Path=/api/v1/auth/**-Path=/api/v1/users/**,/api/v1/public/users/**-Path=/api/v1/posts,/api/v1/posts/**-Path=/api/v1/comments,/api/v1/comments/**-Path=/api/v1/pathfinder/sessions,/api/v1/pathfinder/sessions/**

这说明整条链路的路径语义其实是一致的:

  • 浏览器页面入口走/paperflow/...
  • 浏览器接口入口走/api/...
  • 网关和业务服务内部继续统一到/api/v1/...

路径层级一旦这样固定下来,后面无论联调还是部署,脑子里至少不会同时打两三套路径。

7. 我们最后发现,最怕的不是配置多,而是只有一层记得自己在子路径下

我们最后发现,这类问题最容易出事故的地方,不是某个单独配置项写错,而是不同层对“自己到底是不是部署在子路径下”理解不一致。

典型错误一般有四种:

  • Vitebase配了/paperflow/,但 React Router 还按/解释;
  • React Router 配了basename,但 Nginx 没处理刷新 fallback;
  • Nginx 处理了/paperflow/,但静态资源还在按/assets/取;
  • 前端页面走子路径,接口也被错误地改成/paperflow/api/...

这些问题单看都不复杂,但叠在一起就特别像大学生项目里最常见的那种情况:
每一层都觉得自己差不多对了,最后整体就是跑不顺。
因为你会看到:

  • 首页能开;
  • 某些页面刷新就 404;
  • 某些资源偶尔又能加载;
  • 接口调用路径还不统一。

我们后来最有用的方法,不是继续乱试,而是老老实实把四层边界按顺序对一遍:

  • Vite base
  • BrowserRouter basename
  • Nginx location /paperflow/
  • Nginx location /api/

8. 回头看,这其实不只是前端细节,而是一次完整的部署排坑

一开始我们也把它当成前端小问题,后来才发现它其实是一次完整的全链路排坑。

因为它同时要求:

  • 前端构建工具理解部署位置;
  • 前端路由理解部署位置;
  • Nginx 理解页面和资源的区别;
  • 网关理解接口前缀边界。

只要这几层没有用同一套语义,系统就会看起来“好像差不多”,但总有一处在漏水。

PaperFlow 现在这套方案其实不算复杂,但它至少有一个很朴素的优点:
每一层都明确知道自己面对的是/paperflow/还是/api/

9. 最后

如果你也是类似的大学生项目,准备把 React 前端挂到一个子路径下,真的不要只改一处配置就觉得结束了。
至少把下面这几项一起核对掉:

  • vite.config.tsbase
  • BrowserRouterbasename
  • Nginx 的静态资源路径处理
  • Nginx 的 SPA fallback
  • /api是否继续保持独立入口
http://www.jsqmd.com/news/983027/

相关文章:

  • 一个问题变成 50 条 SQL:AI Agent 是怎么问数据库的?
  • NXP KMA321/A可编程角度传感器:AMR技术、SENT接口与ASIL功能安全详解
  • TQVaultAE终极指南:如何彻底解决《泰坦之旅》仓库空间不足问题
  • JumpServer4\.10\.16离线部署\+外部Nginx反向代理 解决30分钟空闲断开WebSocket超时(延长10天)
  • ARM Cortex-M4与K30微控制器:高性能低功耗嵌入式开发实战解析
  • 保姆级教程:用MMSegmentation+Swin-T+UperNet搞定停车场场景语义分割(附完整代码与数据集)
  • 【粉丝福利社】一本书讲透具身智能:技术、应用、商业与未来
  • 阿贝云免费服务器全面评测:永久免费的云服务值得冲吗?
  • 嘉哲AI智能财税平台深度测评:企业风险评测的智能化实践指南
  • 开源数据目录选型实战:元数据管理与数据血缘落地指南
  • HTTPS加密原理:图解安全传输全流程
  • 2024年Adobe Substance 3D Designer
  • 嵌入式音频系统低功耗设计:I2S/SAI接口时序参数深度解析与工程实践
  • Docker Swarm和K8S有什么区别?一图看懂复杂
  • 暗黑破坏神2存档编辑器完整指南:5分钟打造完美角色体验
  • 内核级硬件伪装技术实战指南:Windows驱动开发深度解析
  • Spring AI 实战指南(二):RAG、向量数据库、Tool Calling、Agent 企业级开发实战
  • 拷贝漫画第三方客户端终极指南:打造纯净高效的Android漫画阅读体验
  • LangChain4j 开发Java Agent智能体- 嵌入模型与向量数据库
  • i.MX 8ULP ADC/DAC/CMP电气特性深度解析与实战设计指南
  • QNAP 存算一体:理顺航空航天精密铸造车间 MES 报工与工艺参数闭环数据总线
  • 告别内存焦虑:用STM32H7的FMC+SDRAM给项目扩容,保姆级CubeMX配置避坑指南
  • 终极开源AI自瞄指南:5分钟完成YOLOv8智能瞄准部署
  • 时序数据库选型:吞吐、压缩与查询延迟的均衡之术
  • 别再为hiprint表格数据绑定头疼了!Vue项目里一个关键配置让你秒通
  • 嵌入式开发实战:深度解析MCU模拟与数字接口电气特性与设计
  • Claude归零层:语义保真度校验环的工程级移除与确定性重构
  • 9种字重完整字体库:Outfit字体解决品牌视觉统一难题的终极指南
  • context - mode:为AI编程减负,降成本98%、提记忆力至3小时,GitHub获超1.5万Star!
  • 嵌入式硬件设计基石:从MCU数据手册电气特性到可靠系统实现