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

MERN栈Create React App部署Heroku后React前端无法正常显示求助

MERN栈Create React App项目部署Heroku后前端无法渲染问题

我在将基于MERN栈开发的Create React App项目部署到Heroku时遇到了React内容无法正常显示的问题:项目在本地运行完全正常,但在Heroku上访问任意路由时,仅返回后端提供的JSON数据,JSON包含完整数据库信息,说明后端与MongoDB连接运行正常,但无任何React组件渲染。我已尝试添加static.json文件、调整heroku-postbuild脚本、参考相关技术帖子修改server.js中静态index.html文件的服务代码,均未成功,希望得到解决方法。

项目基础信息

  • Github代码地址:github
  • 目录结构:目录结构

客户端package.json

{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "proxy": "https://youth-sports.herokuapp.com",
  "homepage": "https://youth-sports.herokuapp.com",
  "dependencies": {
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/react": "^11.2.7",
    "@testing-library/user-event": "^12.8.3",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^5.3.0",
    "react-scripts": "4.0.3",
    "web-vitals": "^1.1.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "node-sass": "^6.0.1"
  }
}

根目录/服务端package.json

{
  "name": "youth-sports",
  "version": "1.0.0",
  "description": "",
  "main": "sever.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js",
    "dev": "nodemon server.js",
    "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client"
  },
  "author": "Shaun Valentine",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^10.0.0",
    "express": "^4.17.1",
    "mongoose": "^6.0.12"
  }
}

server.js文件

require('dotenv').config();
const Players = require('./Model/playersModel.js'); //!Importing players model into backend for now to serve front-end React
const express = require('express');
const app = express();
const path = require('path');
const PORT = process.env.PORT || 5000;

const mongoose = require('mongoose');
const MONGODB_URI = process.env.MONGODB_URI;
const db = mongoose.connection;

mongoose.connect(MONGODB_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});
db.on('open', () => {
  console.log('Mongo is Connected');
});

//? MIDDLEWARE
app.use(express.json());
if (process.env.NODE_ENV !== 'development') {
  app.use(express.static('public'));
  // app.use('/static', express.static(path.join(__dirname, 'build')));
}

// app.use(express.json());
// if (process.env.NODE_ENV === 'production') {
//   // app.use(express.static('public'));
//   app.use(express.static(path.join(__dirname, 'build')));
// }

const cors = require('cors');
app.use(cors());
app.use(express.urlencoded({ extended: true })); // Middleware - so we can use req.body otherwise Express can't read what's in the body of our POST request. This Middleware parses data from forms in the x-www-form-urlencoded format
app.use(express.json()); // Middleware - for parsing data coming in the body of the POST request as json format

//? GET REQUEST - INDEX ROUTE - SHOW ALL PLAYERS UPON HITTING THIS ROUTE
app.get('/players', async (req, res) => {
  try {
    const roster = await Players.find({});
    res.json(roster);
  } catch (error) {
    res.status(400).json(error);
  }
});

//? GET REQUEST - SHOW PAGE (INDIVIDUAL PLAYER)
app.get('/players/:id', async (req, res) => {
  try {
    const playerProfile = await Players.findById(req.params.id);
    res.status(200).json(playerProfile);
    console.log(playerProfile);
  } catch (error) {
    res.status(400).send(error);
  }
});

//? POST REQUEST - CREATE ROUTE - POST NEW PLAYER DATA TO MONGODB FROM NEW PLAYER FORM ON NewPlayer.js file
app.post('/players', async (req, res) => {
  try {
    console.log(req.body);
    await Players.create(req.body);
    res.redirect('/players');
  } catch (error) {
    console.log(error);
  }
});

app.get('/teams', async (req, res) => {
  try {
    await res.json({ message: 'Hello from server!!!' });
  } catch (error) {
    console.log(error);
  }
});

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

app.listen(PORT, () => {
  console.log(`Listening on PORT ${PORT} yo`);
});

客户端App.js文件

import './Sass/App.scss';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; //! MUST IMPORT BrowserRouter (changing name to just Router is optional) and Route and Switch
import NewPlayer from './Components/NewPlayer.js';
import Rosters from './Views/Rosters.js';
import PlayerProfile from './Views/PlayerProfile.js';
import Home from './Views/Home.js';
import './Sass/App.scss';

function App() {
  return (
    <div className="App">
      <Router>
        <Switch>
          <Route exact path="/players">
            <Rosters />
          </Route>

          <Route exact path="/players/new">
            <NewPlayer />
          </Route>

          <Route exact path="/players/:id">
            <PlayerProfile />
          </Route>

          <Route exact path="/">
            <Home />
          </Route>
        </Switch>
      </Router>
    </div>
  );
}

export default App;

static.json

{
  "root": "build/",
  "clean_urls": false,
  "routes": {
    "/**": "index.html"
  }
}

解决方法

按以下顺序修改配置即可解决问题:

  1. 补全heroku-postbuild的打包步骤
    当前的heroku-postbuild仅安装了客户端依赖,没有生成生产环境静态资源,修改服务端package.json的scripts字段:
"heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client"

部署时会自动在client目录生成存放打包后资源的build文件夹。

  1. 修正静态资源服务路径
    当前server.js中静态资源路径和index.html路径配置错误,替换server.js中对应部分:
// 生产环境静态资源配置
if (process.env.NODE_ENV === 'production') {
  app.use(express.static(path.join(__dirname, 'client', 'build')));
}

// 通配路由配置,放在所有API路由之后
app.get('*', (req, res) => {
  res.sendFile(
    path.resolve(__dirname, 'client', 'build', 'index.html')
  );
});
  1. 调整路由顺序
    确保所有API路由(/players、/teams等)的定义在静态资源服务配置之前,*通配路由放在所有路由的最后,避免访问非API路径时优先匹配到API返回JSON。

  2. 删除static.json
    该配置是给Heroku静态buildpack使用的,和当前Express托管静态资源的逻辑冲突,直接删除即可。

  3. 修正服务端入口文件拼写错误
    服务端package.json的main字段拼写错误,将"main": "sever.js"改为"main": "server.js",避免Heroku启动时找不到入口文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 14:06:04