如果你在uniapp项目里同时开发小程序、App和H5,大概率会碰到这个经典场景:代码在小程序端跑得好好的,接口数据正常返回,一切岁月静好;一切到H5,在浏览器里一打开,接口直接给你一个红色的404。第一次遇到这个问题的人,十个里有八个会先去问后端是不是接口挂了,结果后端一脸无辜地说我本地测得好好的。说实话,这个问题我在多端项目里前前后后踩过好几次坑,根因通常不是后端,也不是接口本身,而是本地H5开发服务器的请求转发没配好。这篇文章就把这个“本地h5运行提示接口404”的问题彻底讲透,从根因到排查再到解决方案,一次给你说清楚。
1. 先搞清楚这个404是谁返回的
1.1 多端项目的运行环境差异
uniapp最强的卖点是一套代码编译到多端,但代价就是每一端的运行环境都不一样。小程序端跑在微信的WebView里,请求从微信的容器发出,只要开发工具里勾选了“不校验合法域名”,请求就能直接打到后端;App端跑在手机的网络栈里,没有浏览器的同源策略限制,请求也是直连后端的。唯独H5端,它跑在浏览器里,开发阶段页面由本地开发服务器(webpack-dev-server或vite)提供。
这就有意思了。你在代码里通过uni.request请求一个接口,比如/api/user/info,浏览器收到的页面地址是http://localhost:8080。按照HTTP请求的基本规则,相对路径的请求会被自动拼成http://localhost:8080/api/user/info——请求发出去以后,到达的是你本地的开发服务器,而不是后端服务器。
本地开发服务器本质上是个静态资源服务器,它只负责给你返回页面、JS、CSS这些文件。遇到/api/user/info这种路径,它会先看一下自己的配置:有没有配代理?没配的话,就尝试在项目目录里找这个文件,找不到就直接返回404。所以这个404其实是开发服务器返回的,跟后端一点关系都没有。
1.2 404只是一个表象,关键是请求根本没到后端
很多人看到404第一反应是检查后端,但如果你在Network面板里仔细看,会发现这个404的响应体是一段HTML,而不是JSON。道理很简单:开发服务器找不到对应资源,返回的是自己的默认404页面。
有一种更隐蔽的情况是:请求确实发到了后端,但后端也返回了404。比如前端请求的是/api/user/info,后端实际路由是/user/info,中间差了一个/api前缀,后端网关找不到这个路径,一样返回404。这两种404虽然状态码一样,但处理方式完全不同。第一种需要在开发服务器配代理,第二种需要统一接口路径。
所以排查的第一步永远是打开浏览器开发者工具的Network面板,看请求的完整URL、状态码、响应体和响应头。这一步能直接决定你后面往哪个方向走。
2. 根因排查:几步定位到底哪里404
2.1 先看Network面板,确认请求URL
遇到404,我从来不会先去看代码,先看浏览器控制台加Network面板。点开那条红色的请求,看这几个关键信息:
- Request URL:请求最终发到哪里
- Status Code:404还是别的
- Response Headers:返回的Content-Type是
text/html还是application/json - Response Body:返回的是HTML页面还是JSON错误
如果Request URL是http://localhost:8080/api/xxx,而且响应体是HTML,基本可以断定这是开发服务器返回的404。如果Request URL已经是你后端的完整地址http://192.168.1.100:3000/api/xxx,响应体是JSON,那这404就是后端返回的,问题出在路径或后端路由上。
2.2 用curl直接验证后端接口
确认后端是否正常,最稳的办法不是用小程序的模拟器,也不是用浏览器,而是用终端直接访问。比如后端接口是http://192.168.1.100:3000/api/user/info,在终端执行:
curl http://192.168.1.100:3000/api/user/info如果返回了正常JSON,说明后端和接口本身没问题,问题一定在前端环境或代理配置。如果curl也返回404,那才需要去找后端核对路由。这个方法在排查接口问题时非常高效,因为curl绕过了所有前端开发服务器的干扰,直接跟后端对话。
2.3 区分接口404、跨域和前端路由404
还有一个容易混淆的地方:404和跨域是两码事。跨域在浏览器里会单独特有的报错,英文提示类似“Access to XMLHttpRequest at ... has been blocked by CORS policy”,而且Network面板里这条请求的状态码往往是200,只是数据被浏览器拦截了。404则是服务器明确告诉你路径不存在,两者处理思路完全不同。
另外要注意,H5端如果前端路由用了history模式,比如http://localhost:8080/user/detail,刷新页面时可能也会出现404。但这是页面404,不是接口404,报错的是整个页面空白或者显示“Not Found”,跟请求的404是两回事。别把这两个搞混,不然会白折腾一大圈。
3. 解决方案:给本地开发服务器配代理
3.1 HBuilderX项目:在manifest.json配置devServer
如果你是HBuilderX创建的项目,解决方案是修改manifest.json的h5节点,加上devServer.proxy配置。下面是一个最常用的写法:
{ "h5": { "devServer": { "port": 8080, "proxy": { "/api": { "target": "http://192.168.1.100:3000", "changeOrigin": true, "pathRewrite": { "^/api": "" } } } } } }这里有几个参数要重点说明。"/api"是匹配规则,只要请求路径以/api开头,就会被这个代理拦下来,转发给target指定的目标地址。changeOrigin设置为true,作用是把请求头里的Host字段改成目标服务器的域名,很多后端会校验这个字段,不改有时候会出问题。pathRewrite是路径重写,"^/api": ""表示把开头/api去掉,因为很多后端接口本身没有/api这个前缀。
修改完manifest.json后,一定要重新运行项目或者至少重启一下开发服务器,这个配置是启动时读取的,不会热更新。
3.2 CLI项目:在vite.config.js配置proxy
如果你用的是cli方式创建的项目,或者项目是基于vue3+vite的,那配置位置在vite.config.js文件里:
import { defineConfig } from 'vite' export default defineConfig({ server: { port: 8080, proxy: { '/api': { target: 'http://192.168.1.100:3000', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } } })这里rewrite的作用和pathRewrite一样,都是把路径开头的/api移除。如果你后端接口本身就带/api前缀,那rewrite这行可以直接删掉,不用重写。改完vite.config.js后要重启开发服务器。
3.3 pathRewrite到底要不要写
很多新手在这个配置上栽跟头,主要是不清楚pathRewrite的意义。用生活类比解释一下:代理就像小区门口的快递中转站。你写的收件地址是“XX小区转交302室”,中转站收到后,要把“小区转交”这几个字去掉,替换成真正的门牌号,再把包裹送过去。pathRewrite干的就是这个“去掉/替换”的事。
具体要不要写,取决于后端接口的实际路由。假设你前端请求写的是/api/user/info:
- 后端真实接口是
/user/info:需要pathRewrite,去掉/api前缀,最终转发为/user/info。 - 后端真实接口就是
/api/user/info:不需要pathRewrite,原样转发就行。
配置代理有个小坑要提醒:target地址结尾不要加斜杠。写成http://192.168.1.100:3000没问题,但写成http://192.168.1.100:3000/,在某些版本的代理中间件里会导致路径拼接多出一个斜杠,后端如果路由匹配严格,就可能出现奇怪的404。
3.4 代理配置参数速查
为了方便大家配置时查阅,我把常见的参数和说明整理成一个表格:
| 参数 | 作用 | 注意事项 |
|---|---|---|
| target | 代理目标地址,即后端服务器地址 | 不要带结尾斜杠 |
| changeOrigin | 修改请求头Host为目标地址 | 后端校验Host时建议设为true |
| pathRewrite | 重写请求路径 | 根据后端实际路由决定是否需要 |
| secure | 目标地址是https时,是否验证证书 | 本地自签名证书可设为false |
| bypass | 定义哪些请求不走代理 | 一般用不到,复杂场景才需要 |
4. 不是代理的问题?还有这些隐蔽坑
4.1 接口地址和baseURL拼接错误
代理配置好了还是404,这时候就要回头审查代码了。最常见的坑是接口地址拼接问题。比如你的baseURL写的是/api,请求方法里又写了url: '/user/info',最终请求就是/api/user/info,这是对的。但有人会写成baseURL: '/api',然后又写了url: '/api/user/info',最终变成/api/api/user/info,后端自然找不到这个路由。
这种情况的排查方法也很简单:只要看Network面板里最终的Request URL,一眼就能看出是不是路径重复了。这类报错往往是后端网关返回的404,响应体是JSON而不是HTML,注意区分。
4.2 接口地址写死导致的环境切换问题
还有一个我经常遇到的情况:本地H5跑通了,代码里写死的是http://192.168.1.100:3000,提交代码给同事,同事一运行就404了。因为同事电脑上后端服务端口可能不一样,或者后端的IP变了。跨团队协作时,接口地址千万别写死。
推荐的做法是使用环境变量。CLI项目可以直接创建.env.development和.env.production文件:
# .env.development VITE_API_BASE_URL=http://192.168.1.100:3000/api# .env.production VITE_API_BASE_URL=https://api.example.com/api代码里通过import.meta.env.VITE_API_BASE_URL读取。如果是HBuilderX项目,可以在代码里判断process.env.NODE_ENV来选择不同环境下的接口地址。
4.3 后端路由本身存在,但就是404
这种情况也不能排除。比如后端接口是/user/info,但前端请求是/v1/user/info,或者后端接口必须带版本号,前端没带。这种问题在代理配置正确之后才会暴露出来,因为之前根本没转发到后端。
排查方式还是用curl,直接访问后端最终要接收的路径。curl通了,说明后端没问题;curl不通,把后端日志打开,看看请求到底有没有进来。后端返回404不一定就是路由不对,也可能是因为请求方法不对(比如后端只接收POST,前端发的是GET)。
4.4 小程序端没问题,H5端有问题是正常的
很多人的疑惑点是“小程序端明明好好的,为什么H5就404了”。这其实一点都不奇怪。小程序端没有浏览器同源策略,也没经过本地开发服务器中转,请求直接发向后端。但H5端开发时,页面和接口都依赖开发服务器,不发代理请求就会被本地服务器拦截。
这也解释了另一个现象:很多项目上线到生产环境后,接口用的完整域名,不依赖代理,所以生产环境下H5反而没有404问题。只有本地开发时需要代理来绕开开发服务器的限制。理解了这个原理,以后看到这个场景就不会慌了。
5. 实战补充:多端项目接口层的标准化做法
5.1 统一封装request请求
多端项目建议在项目初期就封装一个统一的请求方法,后续排查问题也会省很多事。下面是一段比较实用的封装示例:
// utils/request.js const BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api' export function request(options) { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: options.header || {}, success: (res) => { if (res.statusCode === 200) { resolve(res.data) } else if (res.statusCode === 404) { console.error('接口404,检查路径:', BASE_URL + options.url) reject(res) } else { reject(res) } }, fail: (err) => { reject(err) } }) }) }这个封装的好处是:只要改BASE_URL这一个变量,所有接口的请求地址都会跟着变。出404时,控制台会直接打印完整的请求URL,排查时一目了然。
5.2 开发环境用代理,生产环境用域名
本地开发建议统一走代理,也就是请求路径用/api开头。这样开发时不暴露后端真实地址,也绕开了跨域问题。生产环境则配置完整域名,不依赖代理。具体配置可以在环境变量里区分:
- 开发环境:
VITE_API_BASE_URL=/api - 生产环境:
VITE_API_BASE_URL=https://api.example.com
上线前还要检查一件事:如果项目部署在Nginx的某个子目录下,比如https://example.com/app/,请求路径不能以/开头,否则会跑到根目录去,导致404。这种情况需要在baseURL里加上前缀,或者部署时统一配置Nginx的proxy_pass。
5.3 常见问题速查表
我把这些年踩过的坑整理成一个速查表,遇到404的时候对照排查就行:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 本地H5请求接口404,响应体是HTML | 开发服务器没配代理 | 在manifest.json或vite.config.js配置proxy |
| 请求URL变成了/api/api/xxx | baseURL和url重复带前缀 | 统一接口拼接,只保留一个前缀 |
| 路径正确但后端返回404 JSON | 接口路径带版本号或前缀不对 | 用curl验证后端真实路由 |
| 代理配了还是不生效 | 没重启开发服务器 | 修改配置后必须重启 |
| 小程序正常,H5才404 | 小程序没有同源策略,H5有 | 给H5开发环境配代理 |
| 请求路径对,但出现405 | 请求方法不对,GET/POST不一致 | 核对后端接口method |
我在实际项目中还养成了一个习惯:把这份速查表直接放到项目根目录的README里。团队新人进来后,遇到类似问题自己对着表格排查一遍,基本不用麻烦别人。每次排查都按“Network看URL -> curl验证后端 -> 检查devServer配置 -> 核对pathRewrite”这个顺序走,两分钟内就能定位到根因。最后再分享一个小技巧:如果你用的HBuilderX运行内置浏览器,Network面板有时候不如Chrome的开发者工具直观,遇到排查问题别犹豫,直接把项目运行到外部浏览器里,调试体验会好很多。