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

MERN应用部署Heroku后服务器请求被拒绝问题求助

MERN应用部署Heroku后前端接口调用CONNECTION REFUSED问题排查与解决

问题现象

MERN应用已部署至Heroku,Postman可正常访问后端接口并获取正确返回,但生产环境下前端调用接口时提示CONNECTION REFUSED,请求无法完成。

相关代码片段

前端Services文件(目标接口带/api前缀)

import http from "../utils/http-common";

class CountriesService {
  getAll() {
    return http.get("/api/country");
  }

  getAllCountries() {
    return http.get("/country/getAll");
  }

  getScroll(skip) {
    return http.get(`/country?skip=${skip}`);
  }

  get(id) {
    return http.get(`/country/${id}`);
  }

  create(countryForm) {
    return http.post("/country/new", countryForm);
  }

  edit(id, values) {
    return http.put(`/country/${id}`, values);
  }

  delete(id) {
    return http.delete(`/country/${id}`);
  }
  
}
export const getPostsPage = async (pageParam = 1, options = {}) => {
  const response = await http.get(`/api/country?_page=${pageParam}`, options)
  return response.data
}
export default new CountriesService();

前端http-common配置

import axios from "axios";

var url;

process.env.REACT_APP_NODE_ENV === "production"
  ? (url = "")
  : (url = "http://localhost:5000/");

export default axios.create({
    baseURL: `${url}`,
    headers: {
        'Content-Type': 'application/json',
    },
    withCredentials: true,
});

后端Server配置

const path = require('path');

require("dotenv").config();
const express = require("express");
const app = express();
const mongoose = require("mongoose");
const cors = require("cors");
var morgan = require("morgan");
const MongoDBStore = require('connect-mongo');
const mongoSanitize = require('express-mongo-sanitize');
const helmet = require('helmet');
// Models
const User = require("./models/user.js");
// Passport Config
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const flash = require("connect-flash");
const session = require("express-session");
const cookieParser = require("cookie-parser");

// 省略中间件和路由导入...

const db_url = process.env.DB_URL;
const PORT = process.env.PORT || 5000;
const secret = process.env.SESSION_SECRET;

const corsOptions = {
  origin: [
    "http://localhost:3000",
    "http://localhost:5000",
    "https://geopilot.herokuapp.com",],
  credentials: true,
  optionSuccessStatus:200,
};
app.use(cors(corsOptions));

// 省略数据库连接、session配置、passport配置...

// Routes
app.use("/api/country/:countryId/reviews", reviewRoute);
app.use("/api/country", countryRoute);
// 省略其他路由...

// ----------- Deployment -----------

__dirname = path.resolve();
if (process.env.NODE_ENV === "production") {
  app.use(express.static(path.join(__dirname, "../client/build")));

  app.get("*", (req, res) => {
    res.sendFile(path.resolve(__dirname, "../client", "build", "index.html"));
  });
}

// ----------- Deployment -----------

app.get("*", () => {
  res.send("PAGE NOT FOUND");
});

已尝试但无效的方案

  • Postman测试接口正常返回
  • 修改package.json代理配置及服务器启动脚本
  • 切换端点并调整CORS策略

排查与解决步骤

1. 统一前端接口路径前缀

注意到Services中部分接口未带/api前缀(如/country/getAll),但后端所有API路由均挂载在/api下,这类请求在生产环境会被静态文件路由拦截,返回index.html导致失败。需统一所有接口路径:

// 修改Services中的接口路径
getAllCountries() {
  return http.get("/api/country/getAll");
}

getScroll(skip) {
  return http.get(`/api/country?skip=${skip}`);
}

get(id) {
  return http.get(`/api/country/${id}`);
}

create(countryForm) {
  return http.post("/api/country/new", countryForm);
}

edit(id, values) {
  return http.put(`/api/country/${id}`, values);
}

delete(id) {
  return http.delete(`/api/country/${id}`);
}

2. 修正Session Cookie生产环境配置

Heroku使用HTTPS,需调整Cookie的secure和sameSite参数,同时开启反向代理信任:

// 在session配置前添加
app.set('trust proxy', 1);

// 修改sessionOptions中的cookie配置
const sessionOptions = {
  name: "geopilot_session",
  secret: secret,
  store: store,
  resave: false,
  saveUninitialized: false,
  cookie: {
    samesite: process.env.NODE_ENV === "production" ? 'none' : false,
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    expires: Date.now() + 1000 * 60 * 60 * 24 * 365,
    maxAge: 1000 * 60 * 60 * 24 * 365,
  },
};

3. 确认前端环境变量设置

在Heroku控制台的应用Settings -> Config Vars中添加REACT_APP_NODE_ENV=production,确保前端axios的生产环境baseURL逻辑生效。

4. 优化后端路由顺序

删除后端最后冗余的app.get("*")路由,生产环境下该路由会被前面的静态文件*路由拦截,无实际作用,避免逻辑混淆。

5. 验证CORS配置准确性

确认Heroku应用的实际域名与corsOptions中的https://geopilot.herokuapp.com完全一致,若使用自定义域名需同步更新origin列表:

const corsOptions = {
  origin: process.env.NODE_ENV === "production" 
    ? ["https://geopilot.herokuapp.com"] 
    : ["http://localhost:3000", "http://localhost:5000"],
  credentials: true,
  optionSuccessStatus:200,
};

总结

最核心的问题是前端部分接口缺少/api前缀导致请求被静态文件路由拦截,其次是Session Cookie的生产环境配置不符合HTTPS要求。按照上述步骤修复后,重新部署Heroku应用即可解决连接拒绝问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 23:01:05