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" } }
解决方法
按以下顺序修改配置即可解决问题:
- 补全heroku-postbuild的打包步骤
当前的heroku-postbuild仅安装了客户端依赖,没有生成生产环境静态资源,修改服务端package.json的scripts字段:
"heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client"
部署时会自动在client目录生成存放打包后资源的build文件夹。
- 修正静态资源服务路径
当前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') ); });
调整路由顺序
确保所有API路由(/players、/teams等)的定义在静态资源服务配置之前,*通配路由放在所有路由的最后,避免访问非API路径时优先匹配到API返回JSON。删除static.json
该配置是给Heroku静态buildpack使用的,和当前Express托管静态资源的逻辑冲突,直接删除即可。修正服务端入口文件拼写错误
服务端package.json的main字段拼写错误,将"main": "sever.js"改为"main": "server.js",避免Heroku启动时找不到入口文件。
内容的提问来源于stack exchange,提问作者valensh1
相关产品推荐
相关产品推荐

