Heroku部署React+Node.js项目:前后端冲突问题求助
问题背景
- 本地环境:React前端+Node.js后端的联系表单功能完全正常——前端提交请求到后端,后端通过SMTP将消息发送到指定收件箱。
- Heroku部署后出现两类问题:
- 当Procfile配置
web: node server.js启用后端时,Node服务器会覆盖前端路由,导致React组件无法显示(根源是server.js中的app.get('/', res.send('Hello, World!')))。 - 注释Procfile配置后,前端正常显示,但后端失效,表单提交后无法发送邮件(控制台能捕获提交的JSON对象,但后端无响应)。
- 移除
app.get('/', res.send('Hello, World!'))后,Heroku无法正常启动项目。
- 当Procfile配置
关键代码片段
server.js 核心代码
const express = require('express'); const app = express(); const nodemailer = require('nodemailer'); const cors = require('cors'); app.use(cors()); app.use(express.json()); // 导致前端被覆盖的根路由 app.get('/', (req, res) => { res.send('Hello, World!'); }); // 表单提交接口 app.post('/send', (req, res) => { const transporter = nodemailer.createTransport({ // 硬编码的SMTP配置(建议改为环境变量) service: 'Gmail', auth: { user: 'your-email@example.com', pass: 'your-password' } }); const mailOptions = { from: req.body.email, to: 'target-email@example.com', subject: `Message from ${req.body.name}`, text: req.body.message }; transporter.sendMail(mailOptions, (error, info) => { if (error) { console.log(error); res.status(500).send('Error sending email'); } else { console.log('Email sent: ' + info.response); res.status(200).send('Email sent successfully'); } }); }); const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Contact.js 前端组件
import React, { useState } from 'react'; const Contact = () => { const [formData, setFormData] = useState({ name: '', email: '', message: '' }); const handleSubmit = async (e) => { e.preventDefault(); try { const response = await fetch('/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const data = await response.json(); console.log(data); } catch (error) { console.error('Error:', error); } }; const handleChange = (e) => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; return ( <form onSubmit={handleSubmit}> <input type="text" name="name" value={formData.name} onChange={handleChange} placeholder="Name" required /> <input type="email" name="email" value={formData.email} onChange={handleChange} placeholder="Email" required /> <textarea name="message" value={formData.message} onChange={handleChange} placeholder="Message" required /> <button type="submit">Send Message</button> </form> ); }; export default Contact;
Heroku 日志片段
202X-XX-XXTXX:XX:XX.XXX+00:00 app[web.1]: Server running on port 5000
202X-XX-XXTXX:XX:XX.XXX+00:00 heroku[web.1]: State changed from starting to up
202X-XX-XXTXX:XX:XX.XXX+00:00 app[web.1]: GET / 200 1.2ms - 13
202X-XX-XXTXX:XX:XX.XXX+00:00 app[web.1]: POST /send 404 5.7ms - 123
解决方案
1. 修改server.js,让Node托管React静态文件
移除原有的根路由app.get('/', res.send('Hello, World!')),替换为静态文件托管逻辑:
const path = require('path'); // 托管React构建后的静态资源(路径根据你的项目结构调整) app.use(express.static(path.join(__dirname, 'client/build'))); // 所有非API路由都返回React的index.html,保证前端路由正常工作 app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'client/build', 'index.html')); });
注:如果React项目和Node项目在同一根目录,调整client/build为你的React构建目录路径即可。
2. 配置根目录package.json的构建脚本
在根目录的package.json中添加heroku-postbuild脚本,让Heroku部署时自动构建React项目:
{ "scripts": { "start": "node server.js", "heroku-postbuild": "cd client && npm install && npm run build" } }
解释:Heroku会先安装根目录的Node依赖,再执行该脚本进入React子目录安装依赖并生成静态文件。
3. 保持Procfile配置不变
继续使用:
web: node server.js
此时Node服务器会同时托管前端静态文件和处理API请求,不会再覆盖前端。
4. 替换SMTP硬编码为环境变量
将server.js中的SMTP敏感信息改为读取Heroku环境变量:
const transporter = nodemailer.createTransport({ service: 'Gmail', auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS } });
然后在Heroku仪表盘→设置→Config Vars中添加SMTP_USER和SMTP_PASS两个环境变量,填入对应的邮箱账号和授权码。
5. 测试部署
提交所有修改到GitHub,重新部署到Heroku后验证:
- 访问根域名,确认React前端正常显示
- 提交联系表单,检查收件箱是否收到邮件,同时查看Heroku日志确认接口请求返回200状态码
内容的提问来源于stack exchange,提问作者EarningZekrom

