You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MERN应用部署Heroku后前端正常但API返回404问题排查

核心根因定位

结合你描述的「本地全链路功能正常、Heroku平台静态资源可正常加载、仅Axios发起的API请求返回404、Network面板可见请求路径/参数/请求头均无异常」的特征,触发问题的原因集中在以下几类:

  • 路由注册优先级错误:生产环境下将前端SPA的通配符兜底路由(用于适配history模式的前端路由跳转)放在了API路由注册逻辑之前,所有进入Express服务的请求会优先被静态资源逻辑匹配拦截,API路由根本没有机会处理对应请求
  • 环境分支逻辑错配:在Express入口文件中,将API路由的注册逻辑写在了development专属的判断分支内——本地开发时NODE_ENV为development,API路由可正常加载运行;但Heroku部署时默认将NODE_ENV设置为production,这部分API注册逻辑完全不会被执行,所有API路径无匹配的处理逻辑
  • 静态资源模块拦截范围过宽:Express静态资源托管配置未排除/api路径,请求进入服务后会优先在client/build静态目录下查找对应路径的文件,找不到时直接触发404响应,跳过后续的API路由匹配
  • 兜底路由文件路径拼接错误:SPA兜底逻辑中sendFile指向的index.html路径,因为__dirname层级判断偏差在生产环境下找不到目标文件,触发Express默认的404响应,哪怕请求路径是合法的API接口路径
对应修复方案

按照优先级逐项排查调整即可:

1. 严格规范Express入口的路由注册顺序

必须遵循「通用中间件挂载 → API路由注册 → 生产环境静态资源托管 → SPA兜底路由」的固定执行顺序,参考正确的结构:

// 1. 挂载全局通用中间件
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(passport.initialize());
app.use(fileUpload());

// 2. 全局注册所有API路由,必须放在静态资源配置之前,且不受环境分支限制
app.use('/api/user', userRouter);
// 其他业务API路由以此类推

// 3. 生产环境下配置静态资源托管
if (process.env.NODE_ENV === 'production') {
  app.use(express.static(path.join(__dirname, 'client', 'build')));

  // 4. 最后注册SPA前端路由兜底逻辑,通过正则排除所有/api开头的请求
  app.get(/^\/(?!api).*/, (req, res) => {
    res.sendFile(path.join(__dirname, 'client', 'build', 'index.html'));
  });
}

注意规避典型错误写法:不要将API路由的注册逻辑放在仅开发环境执行的else分支中——这是MERN项目部署到Heroku最常见的踩坑点,错误示例参考:

// 错误写法:生产环境完全不会加载API路由
if (process.env.NODE_ENV === 'production') {
  app.use(express.static('client/build'));
  app.get('*', (req, res) => res.sendFile(...));
} else {
  app.use('/api/user', userRouter);
}

2. 排查Axios生产环境配置

确认前端Axios的baseURL在生产环境下没有硬编码本地开发地址(比如http://localhost:5000/api),生产环境应使用相对路径(比如/api),让请求自动匹配当前部署的域名。如果你的Network面板中已经看到请求路径为Heroku域名下的合法路径,这项可直接跳过。

3. 生产环境路由加载状态校验

可以在API路由注册逻辑中加入简短日志(比如注册完用户图片接口后打印已加载/api/user/myimages路由),重新部署后查看Heroku控制台日志是否有对应输出。如果日志缺失,说明你的API路由逻辑因为环境分支判断被跳过,直接将路由注册逻辑移到所有环境判断分支的外层即可。

4. 静态资源模块补全排除规则

在Express静态资源托管配置中加入对/api路径的排除,避免静态资源模块优先拦截API请求:

app.use((req, res, next) => {
  // 仅非API路径的请求传递给静态资源模块处理
  if (!req.path.startsWith('/api')) {
    return express.static(path.join(__dirname, 'client', 'build'))(req, res, next);
  }
  next();
});

内容的提问来源于stack exchange,提问作者F Alem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.25 04:15:03