全栈应用部署 #
架构概述 #
前后端分离架构将前端和后端分别部署,通过 API 进行通信。
架构图 #
text
┌─────────────────────────────────────────────────────┐
│ 全栈应用架构 │
├─────────────────────────────────────────────────────┤
│ │
│ 用户浏览器 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 前端应用 (React/Vue) │ │
│ │ myapp-frontend.herokuapp.com │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ │ API 请求 │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 后端 API (Node.js/Python) │ │
│ │ myapp-api.herokuapp.com │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ├──────────────────┬──────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │PostgreSQL│ │ Redis │ │ S3 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────┘
后端 API 部署 #
项目结构 #
text
api/
├── src/
│ ├── index.js
│ ├── routes/
│ ├── middleware/
│ └── models/
├── package.json
├── Procfile
└── app.json
CORS 配置 #
javascript
// src/index.js
const express = require('express');
const cors = require('cors');
const app = express();
const allowedOrigins = [
'https://myapp-frontend.herokuapp.com',
'https://myapp.com',
'http://localhost:3000'
];
app.use(cors({
origin: function (origin, callback) {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// API 路由
app.get('/api/health', (req, res) => {
res.json({ status: 'ok' });
});
module.exports = app;
JWT 认证 #
javascript
// src/middleware/auth.js
const jwt = require('jsonwebtoken');
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
module.exports = authMiddleware;
部署后端 #
bash
# 创建 API 应用
heroku create myapp-api
# 添加数据库
heroku addons:create heroku-postgresql:mini --app myapp-api
heroku addons:create heroku-redis:mini --app myapp-api
# 设置环境变量
heroku config:set JWT_SECRET=$(openssl rand -hex 32) --app myapp-api
heroku config:set CORS_ORIGIN=https://myapp-frontend.herokuapp.com --app myapp-api
# 部署
cd api
git push heroku main
前端部署 #
React 项目 #
bash
# 创建 React 项目
npx create-react-app frontend
cd frontend
环境变量配置 #
javascript
// src/config.js
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:3001';
export { API_URL };
API 调用 #
javascript
// src/api/index.js
import { API_URL } from '../config';
async function fetchAPI(endpoint, options = {}) {
const token = localStorage.getItem('token');
const headers = {
'Content-Type': 'application/json',
...options.headers,
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${API_URL}${endpoint}`, {
...options,
headers,
});
if (!response.ok) {
throw new Error('API request failed');
}
return response.json();
}
export const api = {
get: (endpoint) => fetchAPI(endpoint),
post: (endpoint, data) => fetchAPI(endpoint, {
method: 'POST',
body: JSON.stringify(data),
}),
put: (endpoint, data) => fetchAPI(endpoint, {
method: 'PUT',
body: JSON.stringify(data),
}),
delete: (endpoint) => fetchAPI(endpoint, {
method: 'DELETE',
}),
};
部署 React 应用 #
json
// package.json
{
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test"
},
"homepage": "."
}
bash
# 创建前端应用
heroku create myapp-frontend
# 设置环境变量
heroku config:set REACT_APP_API_URL=https://myapp-api.herokuapp.com --app myapp-frontend
# 部署
cd frontend
git push heroku main
使用静态站点部署 #
bash
# 使用 buildpack 部署静态站点
heroku buildpacks:set mars/create-react-app --app myapp-frontend
# 或使用 heroku-static-buildpack
# 创建 static.json
json
// static.json
{
"root": "build/",
"https_only": true,
"error_page": "error.html",
"routes": {
"/**": "index.html"
}
}
Vue 项目部署 #
项目配置 #
javascript
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/' : '/',
outputDir: 'dist',
devServer: {
proxy: {
'/api': {
target: process.env.VUE_APP_API_URL || 'http://localhost:3001',
changeOrigin: true
}
}
}
};
环境变量 #
text
# .env.production
VUE_APP_API_URL=https://myapp-api.herokuapp.com
部署 Vue 应用 #
bash
# 创建应用
heroku create myapp-frontend
# 设置 buildpack
heroku buildpacks:set heroku/nodejs --app myapp-frontend
# 部署
git push heroku main
统一域名部署 #
子域名配置 #
text
┌─────────────────────────────────────────────────────┐
│ 统一域名部署 │
├─────────────────────────────────────────────────────┤
│ │
│ myapp.com │
│ │ │
│ ├── www.myapp.com ──► 前端应用 │
│ │ │
│ └── api.myapp.com ──► 后端 API │
│ │
└─────────────────────────────────────────────────────┘
配置自定义域名 #
bash
# 前端域名
heroku domains:add www.myapp.com --app myapp-frontend
# API 域名
heroku domains:add api.myapp.com --app myapp-api
# DNS 配置
# www CNAME myapp-frontend.herokuapp.com
# api CNAME myapp-api.herokuapp.com
单应用部署(前后端同仓库) #
项目结构 #
text
myapp/
├── client/ # 前端代码
│ ├── src/
│ ├── public/
│ └── package.json
├── server/ # 后端代码
│ ├── src/
│ └── package.json
├── package.json
└── Procfile
根 package.json #
json
{
"name": "myapp",
"scripts": {
"install": "npm install --prefix server && npm install --prefix client",
"build": "npm run build --prefix client",
"start": "npm start --prefix server",
"heroku-postbuild": "npm run build"
}
}
后端服务静态文件 #
javascript
// server/src/index.js
const express = require('express');
const path = require('path');
const app = express();
// API 路由
app.use('/api', require('./routes'));
// 服务静态文件
app.use(express.static(path.join(__dirname, '../../client/build')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../../client/build/index.html'));
});
module.exports = app;
Procfile #
text
web: npm start
release: npm run db:migrate
认证流程 #
前端登录 #
javascript
// src/components/Login.js
import { useState } from 'react';
import { api } from '../api';
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const { token, user } = await api.post('/api/auth/login', {
email,
password
});
localStorage.setItem('token', token);
// 更新用户状态
} catch (error) {
console.error('Login failed:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Login</button>
</form>
);
}
后端认证 #
javascript
// server/src/routes/auth.js
const router = require('express').Router();
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
router.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);
if (!user || !(await bcrypt.compare(password, user.password_hash))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign(
{ id: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ token, user: { id: user.id, email: user.email } });
});
module.exports = router;
最佳实践 #
1. 环境变量管理 #
bash
# 前端环境变量
REACT_APP_API_URL=https://api.myapp.com
# 后端环境变量
DATABASE_URL=postgres://...
REDIS_URL=redis://...
JWT_SECRET=xxx
CORS_ORIGIN=https://myapp.com
2. 构建优化 #
json
// package.json
{
"scripts": {
"build": "NODE_ENV=production npm run build:client && npm run build:server",
"build:client": "cd client && npm run build",
"build:server": "cd server && npm run build"
}
}
3. 监控配置 #
bash
# 为前端和后端分别添加监控
heroku addons:create newrelic:wayne --app myapp-frontend
heroku addons:create newrelic:wayne --app myapp-api
heroku addons:create papertrail:choklad --app myapp-frontend
heroku addons:create papertrail:choklad --app myapp-api
下一步 #
全栈应用部署完成后,接下来学习 生产环境最佳实践 了解生产环境优化!
最后更新:2026-03-28