☰
React项目线上部署全攻略:从Nginx静态托管到Docker容器化
2026/10/2 4:18:11 网站建设 项目流程

任何把React项目从本地跑到线上的人,都绕不开一个灵魂拷问:npm run build明明成功了,怎么放到服务器上就是白屏、404、接口全挂?这篇文章就把部署这件事从头到尾捋一遍。我自己前后在不同机器上部署过不少React项目,从最早手传文件到后来用Docker固化流程,踩过的坑远比想象中多。这篇文章以一台全新Linux服务器为例,从环境准备、构建上传、Nginx配置到Docker部署,完整走一遍流程,每条命令、每段配置都是实测过能跑的。适合刚接触部署的前端新手,也适合在本地能跑通、一上服务器就懵的开发者。

1. 部署方式选型:先把思路理清楚

1.1 为什么React项目最后只是一堆静态文件

先说一个很多人没真正理解的点:React项目本身不是直接放在服务器上就能跑的。你写的是JSX、组件、ES Module,浏览器认不了这些。经过Vite或者Webpack打包之后,项目会变成一个纯静态资源集合——一个index.html、一堆压缩后的JS文件、CSS文件、图片字体等资源。

这就意味着两件事:第一,部署React项目的本质是“找一个能托管静态文件的Web服务器”,而不是像传统后端那样启动一个常驻进程;第二,因为最终产物是静态文件,部署过程天然就比Java、Go、Node后端服务要简单一个量级。你要做的核心工作,就是告诉服务器:“当有人访问某个路径时,把对应的静态文件返回给他。”

1.2 三种主流部署方式对比

我第一次部署前端项目时,面对“用什么方式上服务器”这个问题纠结了很久。市面上常见的有三种方案,我直接列个表对比一下:

部署方式核心思路优点缺点适用场景
Nginx静态托管+CORS/代理把dist目录放到Nginx指定目录,Nginx直接返回静态文件,接口走反向代理配置简单、性能极高、资源占用低需要自己处理服务器环境、后续扩展全栈时要再调绝大多数React项目(纯前端、前后端分离)
Docker容器化部署把Nginx配置、静态文件打成一个镜像,用容器运行环境隔离、升级回滚方便、与CI/CD天然契合多一层学习成本,国内服务器拉镜像偶尔慢中大型团队、多环境一致、微服务体系
SSR服务端渲染 / Node中间层用Next.js或自建Node服务读取静态文件并做路由分发SEO友好、首屏渲染可控部署链路多一个Node进程,复杂度明显上升需要SEO的营销页、重内容产品

对普通前端项目、个人网站、中小型后台管理系统,我通常首选Nginx静态托管。原因很实际:你能看到的React项目,绝大多数是内部系统、工具站、后台管理等,SEO根本不是刚需,Nginx方案运维成本最低,一台1核2G的云主机就能扛住不小的流量。

1.3 选型建议:别在最简单的地方叠加复杂度

很多人一上来就上Docker,结果卡在“镜像构建慢”“容器里Nginx配置写错”“docker run参数记不全”这些事上,项目反而半天上不了线。

我的建议是分步走:第一版部署先用最简单的Nginx静态托管,能让人通过域名访问到页面就算成功;跑通之后再考虑要不要Docker化。Docker解决的是“环境一致”和“快速分发”的问题,如果你只有一台服务器、固定部署一个项目,这些优势体会不明显,反而徒增操作负担。文章后面我会把两种方式都写清楚,你先跑通第一种,有余力再升级。

2. 部署前的环境准备:服务器、域名与基础组件

2.1 服务器选购与初始初始化

部署React项目对服务器性能要求不高,国内主流云厂商最便宜的入门款云主机就够了,一般2核2G内存、3M左右带宽,基本能扛住中小流量。操作系统我推荐选Ubuntu 22.04 LTS。原因不复杂:资料多、命令通用、Nginx和Node的安装源都很友好,出了任何报错几乎都能搜到答案。

拿到服务器IP和root密码后的第一件事,建议分三步做:

  1. 用SSH登录服务器,执行apt update && apt upgrade -y把系统软件包更新到最新。
  2. 创建一个日常用的普通用户,避免长期用root操作,命令是adduser deploy,然后按提示设置密码。
  3. 把用户加入sudo组,执行usermod -aG sudo deploy,这样遇到需要管理员权限的操作时不需要切来切去。

这几步看着啰嗦,但属于“前期省五分钟,后期省五小时”的事。如果你后面还要在这台服务器上安装Docker、配置多个站点,用普通用户登录会安全很多,也避免了一不小心在生产环境执行危险命令。

2.2 SSH免密登录配置实操

每次登录服务器都要输密码,次数多了会非常烦,而且容易在脚本化操作时卡住。配置SSH免密登录只要两步。

第一步,在本地电脑生成密钥对(如果已有~/.ssh/id_rsa可以跳过):

ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

一路回车,会在~/.ssh目录下生成id_rsa(私钥)和id_rsa.pub(公钥)两个文件。

第二步,把公钥追加到服务器的授权列表里:

ssh-copy-id deploy@你的服务器IP

这条命令会提示你输入服务器密码,输入完成后,公钥就被写入服务器上~/.ssh/authorized_keys。之后再执行ssh deploy@你的服务器IP,就不再需要密码了。配置好这个,后面反复上传文件、登录调试时会顺畅很多。

2.3 安装Node.js与Nginx

React项目构建必须在Node环境下执行,虽然可以把构建过程放在本地完成再上传,但很多团队习惯直接在服务器上拉代码、构建,所以先在服务器上装好Node。

Node的安装强烈建议用nvm(Node Version Manager),而不是直接用apt装。原因很实际:apt默认源里的Node版本往往偏旧,而且React项目迁移时经常要切换Node版本,nvm可以随时切换,避免“换台机器就编译报错”的问题。

# 以普通用户登录后执行 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc nvm install 18

选Node 18或20都可以,Vite 5要求Node 18+,React官方脚手架目前也完全兼容这两个大版本。

接着安装Nginx:

sudo apt install nginx -y

安装完先别急着改配置,确认一下服务状态:

sudo systemctl status nginx

看到active (running)就说明基础环境准备好了。此时直接用浏览器访问服务器IP,如果能看到Nginx的默认欢迎页,说明80端口已通、Nginx工作正常。

3. 核心实操流程:从本地构建到线上可用

3.1 本地构建与产物检查

在本地项目根目录执行:

npm install npm run build

如果一切正常,Vite会把构建产物输出到dist目录。这一步遇到问题最多的场景是什么?是npm install阶段因为网络原因卡死。如果你也遇到依赖下载缓慢或者超时,可以把npm源切换为国内镜像源:

npm config set registry https://registry.npmmirror.com

构建完成后,打开dist目录看一下结构,正常情况下会有:

  • index.html
  • assets/目录(里面是打包后的JS、CSS)
  • vite.svg、favicon.ico或public/下的其他静态文件

有一个细节非常关键:index.html里引用的JS、CSS路径。默认情况下,Vite生成的资源路径是绝对路径(以/开头),例如/assets/index-xxxx.js。如果你的项目部署在域名根路径,这没问题;但如果部署在某个子路径下,比如http://example.com/myapp/,就必须在vite.config.ts里配置base: './',否则页面会因找不到JS而白屏。这个坑我在帮别人排查问题遇到过好几次,后面会在问题章节再展开。

3.2 把dist目录上传到服务器的三种方式

本地构建出dist后,接下来的任务就是把它放到服务器的Web目录下。我常用的目录是/var/www/react-app,先创建它:

sudo mkdir -p /var/www/react-app

上传文件有三种常见姿势,按推荐程度排序说明。

第一种,直接scp(适合一次性覆盖):

# 在本地执行 scp -r dist/* deploy@服务器IP:/var/www/react-app/

第二种,rsync增量同步(适合频繁更新、文件很多):

rsync -avz --delete dist/ deploy@服务器IP:/var/www/react-app/

--delete参数会删除服务器目录里本地已不存在的文件,避免旧文件残留造成资源错乱。这个参数建议保留。

第三种,服务器上直接git pull进行构建(适合团队协作):

# 在服务器上克隆或拉取代码 cd /var/www git clone 你的仓库地址 react-app cd react-app npm install npm run build # 然后把 dist 内容作为站点根目录

这种方式的好处是保留了代码版本历史,每次发版前可以看commit记录,回滚也方便。小团队我比较推荐这种。

我个人的习惯是:项目不大、自己维护时用rsync,几分钟搞定;涉及多人协作或者有测试环境时需要快速迭代,就用git pull方案。

3.3 配置Nginx:最简可运行版本

文件传上去之后,配置Nginx让它知道你的网站根目录在哪里。Nginx的站点配置一般放在/etc/nginx/sites-available/目录,我创建一个针对该项目的配置文件:

sudo vim /etc/nginx/sites-available/react-app

写入以下内容:

server { listen 80; server_name your-domain.com; # 换成你的域名或服务器IP root /var/www/react-app; index index.html; location / { try_files $uri $uri/ /index.html; } }

这段配置的核心是try_files $uri $uri/ /index.html;。它的作用是:当用户访问某个路径时,Nginx先看有没有对应文件,如果没有,就把请求交给index.html处理。对React SPA(单页应用)来说,这是保证刷新不404的关键。具体原因我在第5章展开。

配置写完后,需要把它软链到sites-enabled目录并重载Nginx:

sudo ln -s /etc/nginx/sites-available/react-app /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx

nginx -t用来测试配置语法是否正确,有报错会直接提示,这一步建议每次都执行。

此时访问你的域名或服务器IP,应该能正常看到React应用首页。如果之前Nginx默认配置还在,先把你配置的站点设为唯一站点,或者删掉默认的欢迎页配置,避免多个server块端口冲突导致访问到错误页面。

3.4 接口代理配置:解决跨域和API地址问题

React项目部署后,大概率要面对接口请求问题。最常见的情况是:前端部署在http://your-domain.com,后端API跑在http://your-domain.com:8080或另一台服务器上,直接请求会产生跨域错误。

解决方案有两种。第一种是改后端CORS配置,这个不在本文范围内不展开。第二种更推荐的方案是让Nginx做一层反向代理,把前端发往/api/的请求转发给后端服务:

location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }

这里有个容易搞错的细节:proxy_pass结尾的/不能随意省略。如果写成proxy_pass http://127.0.0.1:8080;(不带末尾斜杠),Nginx转发时会保留原始的URI(如/api/user/info);如果写成http://127.0.0.1:8080/;(带斜杠),则会把/api/前缀替换掉。两种都有各自应用场景,但如果你期望后端接口是/user/info而不是/api/user/info,就用带斜杠的写法。

配置好后,前端项目里请求地址统一写成/api/xxx即可,不再需要关心服务器IP和端口。本地开发时用Vite的proxy,线上部署用Nginx proxy_pass,接口地址对前端代码透明,这种方式是React项目部署最标准的姿势。

3.5 配置域名与HTTPS:让网站像正规军

到前面这一步,你已经可以用IP访问了。但生产环境一般都会绑域名,顺便上HTTPS。过程就两步。

第一步,到域名服务商后台添加解析记录。通常加一条A记录,主机记录填@,记录值填你的服务器IP;如果要让www子域名也能访问,再添加一条主机记录为www的A记录。

第二步,用certbot免费申请Let's Encrypt证书,这一步能自动改写Nginx配置并开启HTTPS:

sudo apt install certbot python3-certbot-nginx -y sudo certbot --nginx -d your-domain.com -d www.your-domain.com

根据提示输入邮箱、同意条款后,certbot会自动验证域名所有权、下发证书,并自动修改Nginx配置加上443端口和证书路径。完成后重新访问你的域名,浏览器地址栏会多出一把小锁,HTTP访问也会自动跳转到HTTPS。

这里提醒一句:在申请证书之前,先确保域名的A记录已经解析到服务器IP,并且80端口能从外网访问,否则certbot的验证过程会失败。我碰到最多的情况就是“明明解析了,但忘了在云服务商控制台放行80端口”,导致证书申请一直报错。

4. Docker化部署:进阶方案,一次配置到处运行

4.1 为什么还需要Docker

Nginx静态托管已经能解决绝大部分问题了,但如果你有多个环境(开发、测试、生产)、多台服务器,或者经常要给同事演示项目,每次都在裸机上一遍遍安装Node、配置Nginx,人会疯掉。Docker的价值就在这时体现:把环境和应用一起打包,镜像到哪里跑,结果都一致。

我自己的经验是:等流程稳定后,抽个时间把部署过程写成一个Dockerfile,之后就再也没手动配过服务器环境。新机器只要装一个Docker,docker compose up -d一套组合拳,项目就起来了。

4.2 Dockerfile 与 Nginx 配置示例

用Docker部署React项目,戏最多的文件就两个:Dockerfile和nginx.conf。

整个思路是多阶段构建:第一阶段用一个装有Node的镜像去构建项目,第二阶段把构建产物拷贝进一个轻量的Nginx镜像里。这样最终镜像只包含静态文件和Nginx,体积小、启动快。

根目录新建Dockerfile,内容如下:

# 构建阶段 FROM node:18-alpine AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN npm install COPY . . RUN npm run build # 运行阶段 FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

同时,在项目根目录创建nginx.conf,内容和你裸机上的Nginx配置基本一致,只是去掉了一些全局配置,文件上下文只保留server块:

server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } }

如果你的项目还有接口代理需求,在location /api/里继续加proxy_pass配置即可。

4.3 构建镜像并启动容器

有了这两个文件,部署流程就简化成了三条命令。在项目根目录执行:

docker build -t react-app:latest . docker run -d -p 80:80 --name react-app react-app:latest

-d表示后台运行,-p 80:80把容器的80端口映射到宿主机80端口。启动后执行docker ps能看到容器在运行状态,浏览器访问服务器IP就能看到页面。

用Docker Compose管理会更方便日常维护。新建docker-compose.yml:

version: "3.8" services: react-app: image: react-app:latest container_name: react-app ports: - "80:80" restart: always

然后在项目根目录执行docker compose up -d,效果和docker run一样,好处是重启策略、端口映射、环境变量都写在文件里,换了新服务器也能原样拉起来。restart: always是生产环境必选,否则服务器重启后容器不会自动恢复。

4.4 镜像加速与体积优化经验

国内服务器构建镜像时,有一个很痛的体验:npm install和拉取基础镜像都慢。npm install慢的问题可以在Dockerfile里加上一行配置解决:

RUN npm config set registry https://registry.npmmirror.com && npm install

基础镜像慢的问题,建议在Docker的/etc/docker/daemon.json里配置镜像加速器,然后重启Docker服务。这块属于运维基本功,配置方式比较简单,就不赘述了。

体积优化方面,可以注意两点:

  • 使用node:18-alpine而非node:18,单是基础镜像就能小将近一半。
  • 在Dockerfile里执行npm install时,把package.json和package-lock.json单独COPY进去,利用Docker的层缓存机制,只要依赖没变,构建镜像时会直接命中缓存,速度会快非常多。

5. 常见问题与排查技巧实录

5.1 路由刷新后404:try_files的功劳

React用了react-router-dom后,最常见的坑就是:从首页点进子页面正常,但直接访问http://your-domain.com/about,或者在这个页面刷新,浏览器报404。

原因不复杂:路径/about对应的页面并不真实存在于服务器目录里,它是React在浏览器端根据URL动态渲染的。Nginx默认行为是“找这个路径对应的文件”,找不到就返回404,它并不知道要找index.html。

解决办法就是前面配置里的try_files $uri $uri/ /index.html;。它的执行逻辑是:先尝试按原路径找文件,再尝试按目录找,都失败就把请求重写到/index.html。这句是React SPA部署的核心配置,写错的话刷新必挂。

5.2 白屏、JS/CSS资源404:base路径的锅

页面能打开但一片空白,打开浏览器开发者工具的Console,如果看到类似Failed to load resource: the server responded with a status of 404,而且报错URL指向/assets/xxx.js,基本就是资源路径问题。

这个情况最常见的成因是:项目部署在子路径下,比如http://your-domain.com/react-app/,但Vite默认生成的资源引入路径是/assets/...,浏览器去服务器根目录找资源,自然找不到。

解决办法是在vite.config.ts里设置base: './',让Vite生成相对路径:

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

改完重新npm run build,再检查dist/index.html里的资源路径,会发现从/assets/变成了./assets/,刷新访问就不再404了。

5.3 接口404或502:先分清是代理问题还是后端问题

接口挂掉的排查步骤我总结成一套“三层确认法”:

第一层,确认后端服务是否正常。直接在后端服务器上执行curl http://127.0.0.1:8080/health(换成你的健康检查接口),如果后端本身挂了,再怎么调Nginx都白搭。

第二层,确认Nginx代理配置是否命中。执行curl http://your-domain.com/api/health,看返回结果。如果返回404,说明请求走到了/api对应的location,但后端没有对应的接口路径;如果返回502,说明Nginx连不上后端,重点检查proxy_pass里的IP和端口是否正确、后端进程是否在运行。

第三层,检查浏览器控制台报错。如果报的是CORS错误,而你又确认后端接口正常、Nginx配置正常,那多半是proxy_pass没有生效,请求直接打到了Nginx的静态资源处理上,跨域自然发生。此时检查Nginx配置里location /api/是否有缩进错误或语法错误。

5.4 页面更新后用户还是旧版本:缓存策略

部署新版本后,有些用户打开页面还是旧内容,浏览器缓存是主要原因。index.html因为是入口,通常要让浏览器每次都重新获取;而assets下的文件名都带hash,内容变化后文件名就变了,可以放心设置长缓存。

推荐的Nginx配置如下:

location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; } location /assets/ { add_header Cache-Control "public, max-age=31536000, immutable"; }

这样线上永远拉最新版入口,已经变了hash的静态资源又能利用浏览器缓存,加载速度不会受影响。

5.5 公网访问不通:防火墙与安全组

部署完发现外网无法访问、本地curl正常,90%是云服务商的安全组或服务器防火墙没放行端口。云服务商的“安全组”通常在控制台配置,比如阿里云、腾讯云,需要手动添加入方向规则,放行80(HTTP)、443(HTTPS)。服务器内部防火墙若启用了ufw,执行:

sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw reload

端口不通的排查不要瞎猜,一步步验证:先在本机curl http://127.0.0.1:80,通了再在另一台机器curl http://服务器IP:80,哪一步挂了就查哪一段。

5.6 常见问题速查表

我整理一张速查表,部署时遇到相同现象直接对照:

现象可能原因解决办法
访问IP显示Nginx默认页站点配置未生效或默认站点占用了80端口检查sites-enabled软链,删除默认配置后reload
刷新子路由404Nginx缺少try_files配置加上try_files $uri $uri/ /index.html;
首页白屏,JS 404Vitebase路径配置不对设置base: './'并重新构建
接口跨域请求没走代理配置location /api/反向代理
接口502后端未启动或IP端口错误先curl后端本机地址检查服务
新版本不生效入口文件被缓存给index.html设置no-cache
80端口无法访问安全组/防火墙未放行控制台和ufw分别检查放行

6. 个人实操中的几点体会

部署这件事,本质上不复杂,但小细节极多,任何一个环节出错都可能导致线上故障。我自己踩过最深的坑是:本地一切正常,结果因为base没配,部署到子路径后整站白屏,排查了快一个小时才想到去看index.html里的资源引用路径。后来学乖了,每次构建完先检查dist/index.html里的JS、CSS路径是不是预期的,一分钟能省下半小时排障。

另一个体会是:不管用哪种部署方式,一定要把部署流程文档化。所谓文档化,不是写一篇长篇大论,而是把关键命令、常用目录、Nginx配置备份留好。Docker部署的流行很大程度上就是因为这套流程可以固化下来,换服务器、加节点真的就是一条命令的事。

如果你正准备部署自己的React项目,建议先按第3章的Nginx方案完整跑通,再考虑Docker化。先掌握最简单的工具组合,等熟悉了再升级,踩坑成本会低很多。部署流程能稳定复现之后,你其实就有了一个可复用的发布系统——后面再部署Vue项目、纯静态站点,都是同一个套路。这也是我写这篇文章的初衷:把这些散落在各种文档里的细节集中起来,让你少走弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询