React工具链
React 本身是一个 UI 库,负责组件、状态和渲染。真正开发一个可运行、可调试、可构建、可部署的前端项目时,还需要一整套工具链,例如:
- 包管理器:npm、pnpm、Yarn。
- 构建工具:Vite、Webpack、Rsbuild、Parcel。
- 编译转换:Babel、SWC、TypeScript。
- 开发服务器:本地热更新、代理后端接口。
- 代码质量:ESLint、Prettier、测试工具。
- 部署产物:HTML、CSS、JavaScript、图片等静态资源。
重要更新:React 官方已在 2025-02-14 宣布对新应用弃用 Create React App(CRA)。CRA 仍可在维护模式下工作,但新项目不建议再使用 CRA。新项目优先考虑 React 框架;如果只是学习或构建纯客户端应用,可以使用 Vite、Parcel、Rsbuild 等构建工具。
选择建议
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 生产级 Web 应用,需要路由、数据加载、SSR/SSG | Next.js、React Router Framework、TanStack Start 等 React 框架 | 框架会把路由、数据获取、代码分割、部署方式整合好 |
| 学习 React、内部管理页、纯客户端 SPA | Vite + React | 启动快,配置少,是当前常见的轻量方案 |
| 老项目已经是 CRA | 继续维护或逐步迁移到 Vite/框架 | 不建议用 CRA 创建新项目 |
| 后端模板页中只想嵌入局部交互 | 在已有构建流程中安装 react、react-dom,按挂载点逐步接入 |
不需要一次性重写整个项目 |
| 没有任何现代前端构建流程的老项目 | 先引入 Vite,或只在少量页面用 CDN 体验 React | 正式开发仍建议使用 Node.js 和模块化构建 |
基础环境
React 开发通常需要安装 Node.js。Node.js 会附带 npm,也可以额外安装 pnpm 或 Yarn。
node -v
npm -v
建议:
- 多个项目之间 Node 版本不一致时,使用 nvm、nvm-windows、Volta 或 fnm 管理版本。
- 团队项目中把 Node 版本写进
.nvmrc、.node-version、package.json的engines字段,避免“我这里能跑”的问题。 - 国内网络下载依赖慢时,可以配置 npm 镜像或公司内部私有源。
创建 React 项目
使用 CRA 创建项目(已 deprecated)
CRA 即 Create React App,曾经是官方推荐的零配置脚手架。它的设计目标是:开发者不用自己配置 Webpack、Babel、ESLint、Jest,就能直接开始写 React 应用。CRA 的这些工具配置主要由 react-scripts 统一管理。
npx create-react-app my-app
cd my-app
npm start
TypeScript 模板:
npx create-react-app my-app --template typescript
生成后的常见目录:
my-app
├── README.md
├── package.json
├── public
│ ├── index.html
│ └── favicon.ico
└── src
├── App.js
├── index.js
└── index.css
入口文件通常是 src/index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
现在需要注意:
- React 官方已在 2025-02-14 宣布 CRA 对新应用 deprecated。
- CRA 仍可在维护模式下工作,并支持 React 19。
- 新项目更建议使用 React 框架或 Vite、Parcel、Rsbuild 等构建工具。
- 已有 CRA 项目如果没有明显问题,可以继续维护;如果依赖升级困难、启动慢、构建慢、Webpack 配置受限,可以考虑迁移。
- 学习旧项目、维护公司历史项目时仍然需要理解 CRA 和
react-scripts,因为很多存量项目还在使用它。
使用 Vite 创建项目
Vite 是现代前端项目中很常见的构建工具,提供开发服务器、热更新、生产构建、插件系统等能力。
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev
TypeScript 模板:
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev
常见脚本:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
常见目录:
my-app
├── index.html
├── package.json
├── vite.config.js
└── src
├── App.jsx
├── main.jsx
└── assets
入口文件通常类似:
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import './index.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>
);
使用框架创建项目
React 官方现在更倾向于推荐框架,因为实际生产应用通常会需要路由、数据加载、代码分割、服务端渲染、静态生成、错误边界等能力。
Next.js:
npx create-next-app@latest
React Router Framework:
npx create-react-router@latest
如何选择:
- 需要 SEO、SSR、SSG、全栈路由、API 路由时,可以考虑 Next.js。
- 偏标准 Web API、路由数据加载、从传统 SPA 过渡时,可以考虑 React Router Framework。
- 只是学习组件、状态、Hooks,Vite + React 更轻。
在已有项目中引入 React
已有项目接入 React 不一定要重写整个系统,可以渐进式引入。
方式一:把某个子路由交给 React
适合场景:
- 后端项目已有主站,例如 Rails、Django、Laravel、Spring MVC。
- 只想把
/admin、/dashboard、/app这类新模块做成 React 应用。
做法:
- 用 Vite 或 React 框架创建一个独立 React 子应用。
- 配置构建基础路径,例如
/admin/。 - 后端或反向代理把
/admin/*请求交给 React 应用的静态资源和入口 HTML。 - 如果是 SPA 路由,服务器要把子路径 fallback 到入口
index.html。
Vite 中可以配置 base:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
base: '/admin/',
plugins: [react()]
});
方式二:在已有页面中挂载局部 React 组件
适合场景:
- 老系统中某些区域需要复杂交互,例如搜索框、弹窗、表格、图表。
- 页面大部分仍由后端模板或旧前端框架渲染。
安装依赖:
npm install react react-dom
在 HTML 或后端模板中预留挂载点:
<div id="user-card-root"></div>
在入口 JS 中渲染组件:
import { createRoot } from 'react-dom/client';
import UserCard from './UserCard.jsx';
const container = document.getElementById('user-card-root');
if (container) {
createRoot(container).render(<UserCard />);
}
如果一个页面有多个挂载点,可以按需创建多个 root:
import { createRoot } from 'react-dom/client';
import SearchBox from './SearchBox.jsx';
import NoticePanel from './NoticePanel.jsx';
const searchRoot = document.getElementById('search-root');
const noticeRoot = document.getElementById('notice-root');
if (searchRoot) {
createRoot(searchRoot).render(<SearchBox />);
}
if (noticeRoot) {
createRoot(noticeRoot).render(<NoticePanel />);
}
注意:
- 不要一上来清空
document.body,否则会破坏原页面。 - React 组件只接管自己的挂载节点。
- 老项目如果没有模块化构建能力,需要先配置 Vite、Webpack、Rollup、Parcel 等工具来处理 JSX 和 npm 依赖。
方式三:无构建工具的临时接入
只适合 Demo、教学、小实验,不适合正式项目。
<div id="root"></div>
<script type="module">
import React from 'https://esm.sh/react';
import { createRoot } from 'https://esm.sh/react-dom/client';
function App() {
return React.createElement('h1', null, 'Hello React');
}
createRoot(document.getElementById('root')).render(React.createElement(App));
</script>
正式项目仍建议使用 Node.js、npm、构建工具和锁文件,保证依赖版本可控。
react-scripts
react-scripts 是 CRA 项目的核心依赖。创建 CRA 项目后,package.json 通常不会直接暴露 Webpack、Babel、Jest、ESLint 的大量配置,而是只依赖一个 react-scripts 包。这个包把常用前端工程配置封装起来,并对外提供固定命令。
{
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-scripts": "5.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
}
可以把 CRA 理解成两层:
create-react-app:脚手架命令,只负责创建项目、生成目录、安装依赖。react-scripts:运行期工具包,负责开发服务器、生产构建、测试、eject。
react-scripts 安装及使用
在 CRA 项目中自动安装
正常使用 CRA 创建项目时,不需要手动安装 react-scripts。脚手架会自动把它写入 package.json:
npx create-react-app my-app
创建完成后进入项目:
cd my-app
npm start
此时 npm start 实际执行的是 react-scripts start。
在已有项目中手动安装
如果已有项目想补齐 CRA 风格的脚本,可以手动安装:
npm install react react-dom react-scripts
然后在 package.json 中添加脚本:
{
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
}
项目还需要满足 CRA 的基本目录约定:
my-app
├── package.json
├── public
│ └── index.html
└── src
└── index.js
public/index.html 中需要有挂载节点:
<div id="root"></div>
src/index.js 中渲染 React 应用:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
然后就可以运行:
npm start
npm run build
npm test
通过 npx 直接调用
如果没有在全局安装 react-scripts,也不需要全局安装。项目本地安装后,可以通过 npm scripts 调用,也可以直接使用:
npx react-scripts start
npx react-scripts build
npx react-scripts test
更推荐通过 package.json 的 scripts 调用,因为团队成员只需要记住 npm start、npm run build 这类统一命令。
版本安装建议
CRA 生态中常见的稳定版本是 react-scripts@5.0.1:
npm install react-scripts@5.0.1
如果项目是历史 CRA 项目,升级前建议先查看当前版本:
npm list react-scripts
升级时注意:
- 不要只升级
react,也要检查react-dom和react-scripts的兼容情况。 - 老项目从
react-scripts@4升到5时,可能遇到 Webpack 5、Node polyfill、ESLint、Jest 相关变化。 - 如果项目已经 eject,就不能再像普通 CRA 项目一样只升级
react-scripts来获得配置更新。 - 新项目不建议为了使用
react-scripts而手动搭建 CRA 风格项目,优先选择 Vite 或 React 框架。
react-scripts 封装了什么
react-scripts 主要封装了这些能力:
| 能力 | 说明 |
|---|---|
| Webpack 配置 | 处理 JS、JSX、CSS、图片、字体、代码分割、生产压缩等 |
| Babel 配置 | 转换 JSX 和现代 JavaScript 语法 |
| ESLint 配置 | 开发时显示语法和部分代码质量问题 |
| Jest 配置 | 提供单元测试和组件测试的默认配置 |
| webpack-dev-server | 提供本地开发服务器、自动刷新、错误覆盖层 |
| PostCSS | 处理 CSS 兼容性和部分 CSS 转换 |
| 环境变量规则 | 支持 .env 文件、REACT_APP_ 前缀变量和部分内置变量 |
| 生产构建优化 | 生成 hash 文件名、压缩资源、拆分 bundle、生成 sourcemap |
CRA 的“零配置”本质上就是把这些配置藏在 react-scripts 中。好处是上手快、升级时理论上只升级 react-scripts;缺点是深度定制会比较受限。
react-scripts start
npm start
等价于:
react-scripts start
作用:
- 启动本地开发服务器,默认端口通常是
3000。 - 启用开发模式构建,代码不会按生产环境压缩。
- 开启热更新或自动刷新。
- 在浏览器和终端显示编译错误、ESLint 错误。
- 读取
.env.development、.env.local等环境变量文件。
常见配置:
PORT=3001
HOST=0.0.0.0
HTTPS=true
BROWSER=none
这些变量可以放在 .env.development 中,也可以在命令行中设置。Windows PowerShell 中临时设置端口的写法:
$env:PORT=3001
npm start
react-scripts build
npm run build
等价于:
react-scripts build
作用:
- 使用生产模式构建项目。
- 默认输出到
build目录。 - 压缩 JavaScript、CSS 和静态资源。
- 给输出文件名添加 hash,方便浏览器长期缓存。
- 默认生成 sourcemap,便于线上错误排查。
- 根据
homepage或PUBLIC_URL处理静态资源路径。
构建产物示例:
build
├── asset-manifest.json
├── index.html
└── static
├── css
├── js
└── media
常见生产构建变量:
BUILD_PATH=dist
GENERATE_SOURCEMAP=false
PUBLIC_URL=/admin/
INLINE_RUNTIME_CHUNK=false
说明:
BUILD_PATH可以修改输出目录。GENERATE_SOURCEMAP=false可以关闭生产 sourcemap,减少产物体积和源码暴露。PUBLIC_URL或package.json中的homepage会影响静态资源引用路径。INLINE_RUNTIME_CHUNK=false常用于需要更严格 CSP 的场景。
react-scripts test
npm test
等价于:
react-scripts test
作用:
- 启动 Jest 测试运行器。
- 默认进入交互式 watch 模式。
- 支持测试文件命名如
*.test.js、*.spec.js。 - 默认适配 React Testing Library。
在 CI 中运行:
npm test -- --watchAll=false
或者设置:
CI=true
CI=true 时,CRA 会把构建中的 warning 当成失败处理,测试也不会默认进入交互 watch 模式。
react-scripts eject
npm run eject
等价于:
react-scripts eject
eject 会把隐藏在 react-scripts 里的配置释放到项目中,包括 Webpack、Babel、ESLint、Jest 等配置文件和依赖。执行后,项目不再只依赖一个封装好的 react-scripts 配置包,而是由项目自己维护这些配置。
注意:
eject是单向操作,执行后不能通过 CRA 命令恢复。- eject 后可以深度修改 Webpack、Babel、Jest,但维护成本会明显提高。
- eject 后升级 React 工具链会更麻烦,需要自己处理配置兼容。
- 大多数项目不建议为了小配置就 eject。
更常见的替代方案:
- 简单变量配置:优先使用
.env、homepage、proxy、browserslist。 - 需要改 Webpack 但不想 eject:可以考虑 CRACO、react-app-rewired,但它们本质上是在绕过 CRA 的封装。
- 长期维护项目:如果配置需求越来越多,更建议迁移到 Vite、Rsbuild 或 React 框架。
环境变量规则
CRA 中有两类环境变量:
- 自定义业务变量:必须以
REACT_APP_开头,才会被注入浏览器代码。 - CRA 内置变量:例如
PORT、HOST、HTTPS、PUBLIC_URL、BUILD_PATH、CI,不需要REACT_APP_前缀。
示例:
REACT_APP_API_BASE_URL=http://localhost:8080
PORT=3001
GENERATE_SOURCEMAP=false
代码中读取:
const apiBaseUrl = process.env.REACT_APP_API_BASE_URL;
注意:所有注入前端代码的变量最终都会出现在浏览器产物里,不能放数据库密码、服务端密钥、私有 token。
配置边界
CRA 适合“不想关心构建配置”的项目,但它的边界也很清楚:
- 可以改:环境变量、代理、浏览器兼容范围、public 静态资源、CSS Modules、Sass、TypeScript、测试文件。
- 不方便改:Webpack loader 顺序、Babel 插件细节、复杂代码分割策略、构建缓存策略、微前端特殊配置。
- 强行改:通常需要 eject、CRACO、react-app-rewired 或迁移工具链。
这也是 CRA 被弃用后,很多项目迁移到 Vite 或框架的原因:现代工具链通常更快,也更容易显式配置。
本地开发代理
前端开发时经常需要把 /api 请求代理到后端,避免浏览器跨域问题。
Vite 代理
vite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
});
前端请求:
fetch('/api/users');
开发时请求会被 Vite 转发到 http://localhost:8080/api/users。
请求被代理的实现原理
没有代理时,如果前端页面运行在 http://localhost:5173,后端接口运行在 http://localhost:8080,浏览器直接请求:
fetch('http://localhost:8080/api/users');
这就是跨域请求。是否允许访问取决于后端是否正确返回 CORS 响应头。
使用 Vite 代理后,前端代码请求的是同源地址:
fetch('/api/users');
这里的关键是:/api/users 不是完整 URL,而是一个以 / 开头的绝对路径 URL。浏览器会用当前页面的 origin 补全它。
假设当前页面地址是:
http://localhost:5173/
那么当前页面的 origin 是:
http://localhost:5173
浏览器解析 fetch('/api/users') 时,会把它补全为:
new URL('/api/users', window.location.origin)
也就是:
http://localhost:5173/api/users
如果写的是相对路径 api/users,浏览器会基于当前页面路径解析;如果写的是 /api/users,浏览器会基于当前站点根路径解析。它们都不会自动请求 localhost:8080,除非代码里显式写出 http://localhost:8080。
浏览器实际请求的是:
http://localhost:5173/api/users
为什么这个请求会先到 Vite dev server?因为开发时页面本身就是由 Vite dev server 提供的。执行 npm run dev 后,Vite 会启动一个 Node.js HTTP 服务,默认监听 localhost:5173。浏览器访问的 index.html、/src/main.jsx、HMR WebSocket、静态资源,以及 /api/users 这类同源请求,都会先发到这个 5173 端口。
Vite dev server 收到请求后会按内部中间件顺序处理,大致可以理解为:
- 如果是页面请求,例如
/,返回index.html。 - 如果是源码模块请求,例如
/src/main.jsx,转换后返回给浏览器。 - 如果是 HMR 请求,交给热更新逻辑。
- 如果路径匹配
server.proxy,例如/api/users匹配/api,交给代理中间件。 - 如果都不匹配,再按静态资源或 fallback 规则处理。
所以,fetch('/api/users') 先到达 Vite dev server,不是 fetch 的特殊能力,而是浏览器 URL 解析规则和当前页面来源共同决定的:页面来自 localhost:5173,以 / 开头的请求自然也发往 localhost:5173。
Vite dev server 发现路径匹配 server.proxy 中配置的 /api,于是由 Vite 在 Node.js 进程里把请求转发给后端:
浏览器
-> http://localhost:5173/api/users
-> Vite dev server
-> http://localhost:8080/api/users
-> 后端服务
后端响应也会沿着相反方向返回:
后端服务
-> Vite dev server
-> 浏览器
关键点:
- 浏览器只看到自己在请求
localhost:5173,所以从浏览器视角看是同源请求。 - 真正跨端口访问后端的是 Vite dev server,而服务端到服务端请求不受浏览器同源策略限制。
- 代理只在本地开发服务器中生效,
npm run build后的静态产物不会自带代理能力。 - 生产环境需要由 Nginx、网关、后端服务、部署平台 rewrite 或真实 API 域名来处理接口请求。
changeOrigin: true 会把代理请求的 Host 请求头改成目标服务的 host,例如从 localhost:5173 改成 localhost:8080。很多后端服务、网关或虚拟主机会根据 Host 判断请求来源,因此这个配置很常用。
如果后端接口没有 /api 前缀,可以使用 rewrite 去掉前缀:
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
此时:
/api/users -> http://localhost:8080/users
常见误区:
- 不要在前端代码里写死
http://localhost:8080/api/users,否则请求不会经过 Vite 代理。 - Vite 代理不能解决生产环境跨域问题,它只是开发阶段的便利工具。
- 如果接口路径没有匹配
/api,代理不会生效。 - WebSocket 代理需要额外配置
ws: true。
CRA 简单代理
package.json:
{
"proxy": "http://localhost:8080"
}
适合简单场景。如果需要更复杂的路径重写或多个后端,可以使用 http-proxy-middleware。
CRA 自定义代理
安装:
npm install http-proxy-middleware --save
创建 src/setupProxy.js:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8080',
changeOrigin: true
})
);
};
构建与部署
React 客户端应用构建后,通常会变成一组静态文件:index.html、JavaScript、CSS、图片、字体等。部署时不是部署 src 源码,而是部署构建命令生成的产物目录。
Vite 构建
npm run build
默认输出目录是 dist。可以通过 vite.config.js 的 build.outDir 修改:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist'
}
});
典型产物目录:
dist
├── index.html
├── favicon.ico
└── assets
├── index-a1b2c3d4.js
├── index-e5f6g7h8.css
├── logo-i9j0k1l2.svg
└── vendor-m3n4o5p6.js
产物说明:
| 文件或目录 | 作用 |
|---|---|
dist/index.html |
应用入口 HTML,浏览器首先加载它 |
dist/assets/*.js |
打包后的业务代码、第三方依赖、按需加载 chunk |
dist/assets/*.css |
提取后的样式文件 |
dist/assets/*.{png,jpg,svg,webp} |
被构建工具处理并带 hash 的静态资源 |
dist/favicon.ico |
从 public 或项目根路径复制来的公共资源 |
*.map |
sourcemap 文件,开启时用于线上调试和错误定位 |
本地预览生产构建:
npm run preview
vite preview 会启动一个本地静态服务器预览 dist,适合发布前检查,不建议作为生产服务器。
常见检查:
- 直接访问首页是否正常。
- 刷新二级路由是否 404。
- 静态资源路径是否正确。
- 接口地址是否仍然指向开发环境。
- 浏览器控制台是否有资源加载失败。
CRA 构建
npm run build
默认输出目录是 build。也可以通过环境变量 BUILD_PATH 修改输出目录:
BUILD_PATH=dist
典型产物目录:
build
├── asset-manifest.json
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── robots.txt
└── static
├── css
│ ├── main.a1b2c3d4.css
│ └── main.a1b2c3d4.css.map
├── js
│ ├── main.e5f6g7h8.js
│ ├── main.e5f6g7h8.js.map
│ └── 453.i9j0k1l2.chunk.js
└── media
└── logo.m3n4o5p6.svg
产物说明:
| 文件或目录 | 作用 |
|---|---|
build/index.html |
应用入口 HTML,会引用 /static/js、/static/css 中的资源 |
build/static/js |
打包后的 JavaScript,包括主包和异步 chunk |
build/static/css |
打包后的 CSS |
build/static/media |
图片、字体、SVG 等资源 |
build/asset-manifest.json |
资源映射表,服务端集成或调试时可能用到 |
build/manifest.json |
PWA 或浏览器安装信息 |
build/robots.txt |
搜索引擎爬虫规则 |
*.map |
sourcemap 文件,默认可能生成,可用 GENERATE_SOURCEMAP=false 关闭 |
CRA 官方部署文档强调:npm run build 生成 build 目录后,需要让 HTTP 服务器返回 index.html,同时正确服务 /static/js/main.<hash>.js 这类静态资源。
产物中 hash 的作用
构建产物经常带 hash,例如:
main.e5f6g7h8.js
index-a1b2c3d4.css
hash 的作用:
- 文件内容变化时文件名变化,浏览器会重新下载。
- 文件内容不变时文件名不变,可以长期缓存。
- 配合 CDN 可以提升加载速度并减少回源。
常见缓存策略:
index.html Cache-Control: no-cache
assets/*.js, static/js/*.js Cache-Control: public, max-age=31536000, immutable
assets/*.css Cache-Control: public, max-age=31536000, immutable
原因:index.html 是资源入口,需要尽快拿到最新版本;带 hash 的 JS/CSS 文件名变化后可以安全长期缓存。
部署静态站点
React SPA 构建后通常是静态资源,可以部署到 Nginx、Apache、对象存储、CDN、Vercel、Netlify、GitHub Pages 等平台。
部署流程通常是:
- 安装依赖:
npm ci或npm install。 - 执行构建:
npm run build。 - 找到产物目录:Vite 默认是
dist,CRA 默认是build。 - 把产物目录上传到服务器、CDN、对象存储或部署平台。
- 配置静态资源服务、SPA fallback、缓存策略和 HTTPS。
使用 serve 本地验证产物
Vite:
npm run build
npx serve -s dist -l 4000
CRA:
npm run build
npx serve -s build -l 4000
-s 表示以 SPA 模式服务静态目录,未知路径会回退到 index.html。
使用 Nginx 部署
假设把产物上传到:
/var/www/my-app
如果是 Vite,通常把 dist 目录中的文件放进去;如果是 CRA,通常把 build 目录中的文件放进去。
Nginx 配置示例:
server {
listen 80;
server_name example.com;
root /var/www/my-app;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|svg|webp|ico|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
}
关键点:
root指向构建产物目录,而不是项目源码目录。try_files $uri $uri/ /index.html用于支持 React Router 这类前端路由。- 静态资源可以长期缓存,
index.html不建议长期缓存。
部署到子路径
如果站点不是部署在域名根路径,而是部署在 /admin/、/console/ 这类子路径,需要同时配置构建工具、路由和服务器。
Vite 配置:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
base: '/admin/',
plugins: [react()]
});
CRA 配置:
{
"homepage": "https://example.com/admin"
}
React Router 配置:
import { BrowserRouter } from 'react-router-dom';
export default function Root() {
return (
<BrowserRouter basename="/admin">
<App />
</BrowserRouter>
);
}
Nginx 子路径示例:
location /admin/ {
alias /var/www/admin/;
try_files $uri $uri/ /admin/index.html;
}
如果 alias 配合 SPA fallback 出现路径问题,也可以用单独的 root 和 try_files 方案。核心原则是:真实静态资源要按路径返回,前端路由刷新时要回退到该子应用的 index.html。
部署到已有后端服务
很多项目会把 React 产物放到后端服务中一起发布。
Express 示例:
const express = require('express');
const path = require('path');
const app = express();
const root = path.join(__dirname, 'build');
app.use(express.static(root));
app.get('*', (req, res) => {
res.sendFile(path.join(root, 'index.html'));
});
app.listen(9000);
Spring Boot 常见做法:
- 执行
npm run build。 - 把产物复制到
src/main/resources/static或构建脚本指定的静态资源目录。 - 配置未知前端路由 fallback 到
index.html。 - 后端 API 继续走
/api,前端页面走/或指定子路径。
部署到对象存储和 CDN
常见组合:
- AWS S3 + CloudFront。
- 阿里云 OSS + CDN。
- 腾讯云 COS + CDN。
- 七牛云 Kodo + CDN。
流程:
- 构建项目,得到
dist或build。 - 上传目录内所有文件到 bucket。
- 设置默认首页为
index.html。 - 如果支持 SPA,配置错误页或回源规则,让未知路径返回
index.html。 - 配置 CDN 缓存策略:HTML 短缓存,带 hash 静态资源长缓存。
- 发布新版本后,如有必要刷新 CDN 的
index.html。
部署到 Vercel、Netlify、Cloudflare Pages
这类平台通常从 Git 仓库自动构建和部署。
通用配置:
| 项目类型 | Build Command | Output Directory |
|---|---|---|
| Vite | npm run build |
dist |
| CRA | npm run build |
build |
部署步骤:
- 把代码推送到 GitHub、GitLab 或 Bitbucket。
- 在平台中导入仓库。
- 设置构建命令和产物目录。
- 设置生产环境变量。
- 部署后绑定自定义域名。
如果使用前端路由,需要配置 rewrites。例如 Netlify 可添加 public/_redirects:
/* /index.html 200
Vercel 可添加 vercel.json:
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
部署到 GitHub Pages
Vite 项目页部署时,仓库名通常会成为子路径,例如:
https://username.github.io/my-app/
需要设置:
export default defineConfig({
base: '/my-app/',
plugins: [react()]
});
CRA 需要设置 homepage:
{
"homepage": "https://username.github.io/my-app"
}
使用 gh-pages 发布 CRA:
npm install --save-dev gh-pages
{
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
}
}
发布:
npm run deploy
如果是 Vite,则发布目录通常改成 dist:
{
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d dist"
}
}
GitHub Pages 对 HTML5 history 路由支持有限,刷新二级路由可能 404。解决方式:
- 使用 hash 路由,例如
/#/users/1。 - 添加
404.htmlfallback 方案。 - 换用支持 rewrites 的平台,例如 Vercel、Netlify、Cloudflare Pages。
部署前检查清单
- 产物目录是否正确:Vite 是
dist,CRA 是build。 index.html是否能正常访问。- JS、CSS、图片等资源是否 200。
- 刷新前端路由是否正常。
- API 地址是否是生产地址。
- 环境变量是否在构建前注入。
- 子路径部署时,
base、homepage、basename是否一致。 - 缓存策略是否避免
index.html长期缓存。 - HTTPS、域名、跨域、CSP 是否按生产要求配置。
- 如果关闭 sourcemap,线上错误监控是否仍能定位版本和 chunk。
环境变量
Vite
Vite 暴露给浏览器的变量必须以 VITE_ 开头。
.env.development:
VITE_API_BASE_URL=http://localhost:8080
使用:
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
CRA
CRA 暴露给浏览器的变量必须以 REACT_APP_ 开头。
.env.development:
REACT_APP_API_BASE_URL=http://localhost:8080
使用:
const apiBaseUrl = process.env.REACT_APP_API_BASE_URL;
注意:前端环境变量会被打包进浏览器代码,不能放数据库密码、服务端密钥、私有 token。
从 CRA 迁移到 Vite
迁移思路:
- 新建 Vite React 项目,或在原项目中安装 Vite。
- 把
src下的业务代码迁移过去。 - 把入口从 CRA 的
src/index.js调整为 Vite 常见的src/main.jsx或src/main.tsx。 - 把
public/index.html中的模板变量改成 Vite 支持的写法。 - 把环境变量从
REACT_APP_改成VITE_。 - 把
process.env.REACT_APP_XXX改成import.meta.env.VITE_XXX。 - 把
react-scripts命令替换为 Vite 命令。 - 检查代理、路径别名、SVG、CSS Modules、测试配置。
脚本迁移示例:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
依赖示例:
npm uninstall react-scripts
npm install -D vite @vitejs/plugin-react
如果项目依赖 CRA 特有能力,例如 Jest 配置、PUBLIC_URL、Webpack loader、自定义代理,需要逐项替换。
常见工具
| 工具 | 作用 |
|---|---|
| Vite | 开发服务器、构建、插件系统 |
| Webpack | 老项目中常见的打包器,CRA 底层使用它 |
| Babel | 转换 JSX 和新语法 |
| SWC | Rust 写的快速编译器,常被框架或插件使用 |
| TypeScript | 类型检查和类型标注 |
| ESLint | 静态代码检查 |
| Prettier | 代码格式化 |
| Vitest | Vite 生态常用测试框架 |
| Jest | CRA 生态常用测试框架 |
| React Testing Library | 从用户行为角度测试 React 组件 |
常见问题
JSX 为什么需要构建工具?
浏览器不能直接识别 JSX。构建工具会通过 Babel、SWC、esbuild 等工具把 JSX 转换成浏览器能执行的 JavaScript。
Vite 和 CRA 的主要区别是什么?
CRA 是一个封装好的 Webpack 脚手架,通过 react-scripts 隐藏配置。Vite 使用原生 ESM 提供更快的开发启动和热更新,并通过插件支持 React、Vue 等框架。更重要的是,CRA 已经 deprecated,新项目不建议继续选择 CRA。
React 项目一定需要路由库吗?
不一定。单页面小组件不需要路由。但只要应用有多个页面状态、需要 URL 可分享、需要权限控制和布局嵌套,建议使用 React Router 或直接选择带路由能力的框架。
为什么不把所有数据请求都写在 useEffect 里?
简单页面可以这样写。但复杂应用中,组件渲染后再请求数据容易产生加载瀑布流、重复请求、缓存困难、错误处理分散等问题。生产项目通常会结合框架 loader、TanStack Query、SWR、Apollo、Relay 等方案。
构建后的文件能直接双击 HTML 打开吗?
不建议。现代前端项目通常依赖模块路径、资源路径、浏览器 history 路由和服务器 fallback。应使用静态服务器或部署平台预览。
参考资料
- React 官方:Creating a React App - https://react.dev/learn/creating-a-react-app
- React 官方:Build a React app from Scratch - https://react.dev/learn/build-a-react-app-from-scratch
- React 官方:Add React to an Existing Project - https://react.dev/learn/add-react-to-an-existing-project
- React 官方博客:Sunsetting Create React App - https://react.dev/blog/2025/02/14/sunsetting-create-react-app
- Vite 官方:Getting Started - https://vite.dev/guide/
- CRA 文档:Getting Started - https://create-react-app.dev/docs/getting-started/

浙公网安备 33010602011771号