React工具链

React 本身是一个 UI 库,负责组件、状态和渲染。真正开发一个可运行、可调试、可构建、可部署的前端项目时,还需要一整套工具链,例如:

  1. 包管理器:npm、pnpm、Yarn。
  2. 构建工具:Vite、Webpack、Rsbuild、Parcel。
  3. 编译转换:Babel、SWC、TypeScript。
  4. 开发服务器:本地热更新、代理后端接口。
  5. 代码质量:ESLint、Prettier、测试工具。
  6. 部署产物: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 创建新项目
后端模板页中只想嵌入局部交互 在已有构建流程中安装 reactreact-dom,按挂载点逐步接入 不需要一次性重写整个项目
没有任何现代前端构建流程的老项目 先引入 Vite,或只在少量页面用 CDN 体验 React 正式开发仍建议使用 Node.js 和模块化构建

基础环境

React 开发通常需要安装 Node.js。Node.js 会附带 npm,也可以额外安装 pnpm 或 Yarn。

node -v
npm -v

建议:

  1. 多个项目之间 Node 版本不一致时,使用 nvm、nvm-windows、Volta 或 fnm 管理版本。
  2. 团队项目中把 Node 版本写进 .nvmrc.node-versionpackage.jsonengines 字段,避免“我这里能跑”的问题。
  3. 国内网络下载依赖慢时,可以配置 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>
);

现在需要注意:

  1. React 官方已在 2025-02-14 宣布 CRA 对新应用 deprecated。
  2. CRA 仍可在维护模式下工作,并支持 React 19。
  3. 新项目更建议使用 React 框架或 Vite、Parcel、Rsbuild 等构建工具。
  4. 已有 CRA 项目如果没有明显问题,可以继续维护;如果依赖升级困难、启动慢、构建慢、Webpack 配置受限,可以考虑迁移。
  5. 学习旧项目、维护公司历史项目时仍然需要理解 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

如何选择:

  1. 需要 SEO、SSR、SSG、全栈路由、API 路由时,可以考虑 Next.js。
  2. 偏标准 Web API、路由数据加载、从传统 SPA 过渡时,可以考虑 React Router Framework。
  3. 只是学习组件、状态、Hooks,Vite + React 更轻。

在已有项目中引入 React

已有项目接入 React 不一定要重写整个系统,可以渐进式引入。

方式一:把某个子路由交给 React

适合场景:

  1. 后端项目已有主站,例如 Rails、Django、Laravel、Spring MVC。
  2. 只想把 /admin/dashboard/app 这类新模块做成 React 应用。

做法:

  1. 用 Vite 或 React 框架创建一个独立 React 子应用。
  2. 配置构建基础路径,例如 /admin/
  3. 后端或反向代理把 /admin/* 请求交给 React 应用的静态资源和入口 HTML。
  4. 如果是 SPA 路由,服务器要把子路径 fallback 到入口 index.html

Vite 中可以配置 base

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  base: '/admin/',
  plugins: [react()]
});

方式二:在已有页面中挂载局部 React 组件

适合场景:

  1. 老系统中某些区域需要复杂交互,例如搜索框、弹窗、表格、图表。
  2. 页面大部分仍由后端模板或旧前端框架渲染。

安装依赖:

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 />);
}

注意:

  1. 不要一上来清空 document.body,否则会破坏原页面。
  2. React 组件只接管自己的挂载节点。
  3. 老项目如果没有模块化构建能力,需要先配置 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 理解成两层:

  1. create-react-app:脚手架命令,只负责创建项目、生成目录、安装依赖。
  2. 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 startnpm run build 这类统一命令。

版本安装建议

CRA 生态中常见的稳定版本是 react-scripts@5.0.1

npm install react-scripts@5.0.1

如果项目是历史 CRA 项目,升级前建议先查看当前版本:

npm list react-scripts

升级时注意:

  1. 不要只升级 react,也要检查 react-domreact-scripts 的兼容情况。
  2. 老项目从 react-scripts@4 升到 5 时,可能遇到 Webpack 5、Node polyfill、ESLint、Jest 相关变化。
  3. 如果项目已经 eject,就不能再像普通 CRA 项目一样只升级 react-scripts 来获得配置更新。
  4. 新项目不建议为了使用 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

作用:

  1. 启动本地开发服务器,默认端口通常是 3000
  2. 启用开发模式构建,代码不会按生产环境压缩。
  3. 开启热更新或自动刷新。
  4. 在浏览器和终端显示编译错误、ESLint 错误。
  5. 读取 .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

作用:

  1. 使用生产模式构建项目。
  2. 默认输出到 build 目录。
  3. 压缩 JavaScript、CSS 和静态资源。
  4. 给输出文件名添加 hash,方便浏览器长期缓存。
  5. 默认生成 sourcemap,便于线上错误排查。
  6. 根据 homepagePUBLIC_URL 处理静态资源路径。

构建产物示例:

build
├── asset-manifest.json
├── index.html
└── static
    ├── css
    ├── js
    └── media

常见生产构建变量:

BUILD_PATH=dist
GENERATE_SOURCEMAP=false
PUBLIC_URL=/admin/
INLINE_RUNTIME_CHUNK=false

说明:

  1. BUILD_PATH 可以修改输出目录。
  2. GENERATE_SOURCEMAP=false 可以关闭生产 sourcemap,减少产物体积和源码暴露。
  3. PUBLIC_URLpackage.json 中的 homepage 会影响静态资源引用路径。
  4. INLINE_RUNTIME_CHUNK=false 常用于需要更严格 CSP 的场景。

react-scripts test

npm test

等价于:

react-scripts test

作用:

  1. 启动 Jest 测试运行器。
  2. 默认进入交互式 watch 模式。
  3. 支持测试文件命名如 *.test.js*.spec.js
  4. 默认适配 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 配置包,而是由项目自己维护这些配置。

注意:

  1. eject 是单向操作,执行后不能通过 CRA 命令恢复。
  2. eject 后可以深度修改 Webpack、Babel、Jest,但维护成本会明显提高。
  3. eject 后升级 React 工具链会更麻烦,需要自己处理配置兼容。
  4. 大多数项目不建议为了小配置就 eject。

更常见的替代方案:

  1. 简单变量配置:优先使用 .envhomepageproxybrowserslist
  2. 需要改 Webpack 但不想 eject:可以考虑 CRACO、react-app-rewired,但它们本质上是在绕过 CRA 的封装。
  3. 长期维护项目:如果配置需求越来越多,更建议迁移到 Vite、Rsbuild 或 React 框架。

环境变量规则

CRA 中有两类环境变量:

  1. 自定义业务变量:必须以 REACT_APP_ 开头,才会被注入浏览器代码。
  2. CRA 内置变量:例如 PORTHOSTHTTPSPUBLIC_URLBUILD_PATHCI,不需要 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 适合“不想关心构建配置”的项目,但它的边界也很清楚:

  1. 可以改:环境变量、代理、浏览器兼容范围、public 静态资源、CSS Modules、Sass、TypeScript、测试文件。
  2. 不方便改:Webpack loader 顺序、Babel 插件细节、复杂代码分割策略、构建缓存策略、微前端特殊配置。
  3. 强行改:通常需要 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 收到请求后会按内部中间件顺序处理,大致可以理解为:

  1. 如果是页面请求,例如 /,返回 index.html
  2. 如果是源码模块请求,例如 /src/main.jsx,转换后返回给浏览器。
  3. 如果是 HMR 请求,交给热更新逻辑。
  4. 如果路径匹配 server.proxy,例如 /api/users 匹配 /api,交给代理中间件。
  5. 如果都不匹配,再按静态资源或 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
  -> 浏览器

关键点:

  1. 浏览器只看到自己在请求 localhost:5173,所以从浏览器视角看是同源请求。
  2. 真正跨端口访问后端的是 Vite dev server,而服务端到服务端请求不受浏览器同源策略限制。
  3. 代理只在本地开发服务器中生效,npm run build 后的静态产物不会自带代理能力。
  4. 生产环境需要由 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

常见误区:

  1. 不要在前端代码里写死 http://localhost:8080/api/users,否则请求不会经过 Vite 代理。
  2. Vite 代理不能解决生产环境跨域问题,它只是开发阶段的便利工具。
  3. 如果接口路径没有匹配 /api,代理不会生效。
  4. 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.jsbuild.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,适合发布前检查,不建议作为生产服务器。

常见检查:

  1. 直接访问首页是否正常。
  2. 刷新二级路由是否 404。
  3. 静态资源路径是否正确。
  4. 接口地址是否仍然指向开发环境。
  5. 浏览器控制台是否有资源加载失败。

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 的作用:

  1. 文件内容变化时文件名变化,浏览器会重新下载。
  2. 文件内容不变时文件名不变,可以长期缓存。
  3. 配合 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 等平台。

部署流程通常是:

  1. 安装依赖:npm cinpm install
  2. 执行构建:npm run build
  3. 找到产物目录:Vite 默认是 dist,CRA 默认是 build
  4. 把产物目录上传到服务器、CDN、对象存储或部署平台。
  5. 配置静态资源服务、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;
  }
}

关键点:

  1. root 指向构建产物目录,而不是项目源码目录。
  2. try_files $uri $uri/ /index.html 用于支持 React Router 这类前端路由。
  3. 静态资源可以长期缓存,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 出现路径问题,也可以用单独的 roottry_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 常见做法:

  1. 执行 npm run build
  2. 把产物复制到 src/main/resources/static 或构建脚本指定的静态资源目录。
  3. 配置未知前端路由 fallback 到 index.html
  4. 后端 API 继续走 /api,前端页面走 / 或指定子路径。

部署到对象存储和 CDN

常见组合:

  1. AWS S3 + CloudFront。
  2. 阿里云 OSS + CDN。
  3. 腾讯云 COS + CDN。
  4. 七牛云 Kodo + CDN。

流程:

  1. 构建项目,得到 distbuild
  2. 上传目录内所有文件到 bucket。
  3. 设置默认首页为 index.html
  4. 如果支持 SPA,配置错误页或回源规则,让未知路径返回 index.html
  5. 配置 CDN 缓存策略:HTML 短缓存,带 hash 静态资源长缓存。
  6. 发布新版本后,如有必要刷新 CDN 的 index.html

部署到 Vercel、Netlify、Cloudflare Pages

这类平台通常从 Git 仓库自动构建和部署。

通用配置:

项目类型 Build Command Output Directory
Vite npm run build dist
CRA npm run build build

部署步骤:

  1. 把代码推送到 GitHub、GitLab 或 Bitbucket。
  2. 在平台中导入仓库。
  3. 设置构建命令和产物目录。
  4. 设置生产环境变量。
  5. 部署后绑定自定义域名。

如果使用前端路由,需要配置 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。解决方式:

  1. 使用 hash 路由,例如 /#/users/1
  2. 添加 404.html fallback 方案。
  3. 换用支持 rewrites 的平台,例如 Vercel、Netlify、Cloudflare Pages。

部署前检查清单

  1. 产物目录是否正确:Vite 是 dist,CRA 是 build
  2. index.html 是否能正常访问。
  3. JS、CSS、图片等资源是否 200。
  4. 刷新前端路由是否正常。
  5. API 地址是否是生产地址。
  6. 环境变量是否在构建前注入。
  7. 子路径部署时,basehomepagebasename 是否一致。
  8. 缓存策略是否避免 index.html 长期缓存。
  9. HTTPS、域名、跨域、CSP 是否按生产要求配置。
  10. 如果关闭 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

迁移思路:

  1. 新建 Vite React 项目,或在原项目中安装 Vite。
  2. src 下的业务代码迁移过去。
  3. 把入口从 CRA 的 src/index.js 调整为 Vite 常见的 src/main.jsxsrc/main.tsx
  4. public/index.html 中的模板变量改成 Vite 支持的写法。
  5. 把环境变量从 REACT_APP_ 改成 VITE_
  6. process.env.REACT_APP_XXX 改成 import.meta.env.VITE_XXX
  7. react-scripts 命令替换为 Vite 命令。
  8. 检查代理、路径别名、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。应使用静态服务器或部署平台预览。

参考资料

  1. React 官方:Creating a React App - https://react.dev/learn/creating-a-react-app
  2. React 官方:Build a React app from Scratch - https://react.dev/learn/build-a-react-app-from-scratch
  3. React 官方:Add React to an Existing Project - https://react.dev/learn/add-react-to-an-existing-project
  4. React 官方博客:Sunsetting Create React App - https://react.dev/blog/2025/02/14/sunsetting-create-react-app
  5. Vite 官方:Getting Started - https://vite.dev/guide/
  6. CRA 文档:Getting Started - https://create-react-app.dev/docs/getting-started/
posted @ 2026-02-16 09:24  vonlinee  阅读(18)  评论(0)    收藏  举报