☰
React项目部署到服务器全指南:从Nginx配置到白屏排查
2026/10/2 19:16:02 网站建设 项目流程

1. 部署前必须想明白的几件事:为什么你的React项目本地好好的,一上服务器就白屏

做React开发的朋友大多有过这种经历:本地npm run dev跑得飞起,页面丝滑流畅,结果构建完丢到服务器上,浏览器一打开——白屏、404、资源路径全错、接口调不通……然后开始怀疑人生。这套流程我前前后后部署过不下几十个项目,从最简单的create-react-app到重型的Next.js SSR应用,踩过的坑攒了一箩筐。这篇就把React项目部署到服务器的完整流程和避坑点一次性写清楚,照着做基本能少走一半弯路。

先说清楚部署到服务器到底是在干什么。React项目本身是纯前端应用,开发模式下你访问的http://localhost:3000是Webpack/Vite启动的开发服务在实时编译代码。但服务器上不可能给你跑一套开发环境,生产环境里的React项目,本质上是把源码通过构建工具打包成静态HTML、CSS、JS文件,然后由一个Web服务器(Nginx、Apache等)把这些静态文件当作"网站"对外提供服务。

这句话里藏着三个最常见的坑:

  1. 路由问题:React做的是SPA(单页应用),路由是前端JS控制的。你要是不懂Nginx需要配置try_files回退,一刷新浏览器就404了。
  2. 资源路径问题:构建产物里的JS/CSS资源默认是用绝对路径/static/js/main.js引用的,如果你的项目部署在子目录(比如http://ip/react-app/)下,不调整publicPath,照样一片白屏。
  3. 环境变量问题:env.development和.env.production不分开,API地址硬编码,部署后接口全部调不通。

所以说,部署不是"把文件夹扔上去就完事",而是"构建、环境、服务器配置、域名与HTTPS"这一套组合拳。下面我按一个完整的部署流程,从零到一展开讲。

2. 本地构建与产物检查:打包这一步决定了服务器上80%的坑

2.1 构建命令与产物结构

不同脚手架构建命令略有差异,但核心思路一致。常见几种:

脚手架/框架构建命令产物目录
create-react-appnpm run buildbuild/
Vitenpm run builddist/
Next.js(纯静态导出)next build && next exportout/
UmiJSnpm run builddist/

我在部署主机的第一步永远是在本机或CI环境执行构建,而不是在服务器上执行。为什么?因为服务器环境往往和本地不一致,Node版本不同、npm依赖没装全、系统架构不同,都可能让构建莫名其妙失败。更关键的是,构建过程会消耗服务器CPU和内存,小内存服务器在构建时直接被OOM(内存溢出)杀死也见过好几次。

执行完构建后,马上检查几个关键点:

# 以Vite项目为例,构建并查看产物 npm run build ls -la dist/ cat dist/index.html

打开index.html,看里面的资源引用路径:

<script type="module" src="/assets/index-abc123.js"></script>

注意这个/assets/...,最前面有一个斜杠,这是根路径。如果你的站点部署在域名根路径(比如https://example.com/)下,没问题;但如果你打算部署到子路径(比如https://example.com/react-app/),就需要在vite.config.ts里设置base: '/react-app/',在create-react-app里设置package.json中的"homepage": "/react-app"。

这个base路径配置是部署后白屏的最高频原因之一。很多人本地预览好好的——因为本地开发服务器也是部署在根路径下,但一旦放到服务器子目录,资源全部404。检查产物中JS/CSS是否404,用浏览器F12打开Network面板一眼就能看出来。

2.2 环境变量拆分:让开发和生产的API地址不再混淆

第二个要提前处理的是环境变量。React项目里,.env文件支持在不同环境下加载不同配置,这个机制很多人忽略了。

默认情况下,React构建时会加载这几类环境文件:

  • .env:所有情况下都会加载
  • .env.development:仅npm start时加载
  • .env.production:仅npm run build时加载

你可以在项目根目录创建两个文件:

// .env.development VITE_API_BASE_URL = http://localhost:8080/api // .env.production VITE_API_BASE_URL = https://api.example.com/api

这样代码里统一用import.meta.env.VITE_API_BASE_URL(Vite)或process.env.REACT_APP_API_BASE_URL(create-react-app)来拼接接口地址,构建时自动选对应的值。省的每次上线前手动改接口地址,改完忘记改回来,下次开发接口全崩。

这里有个血的教训:所有在.env中声明的自定义环境变量,必须带特定前缀才能被React暴露给前端代码。Vite要求是VITE_前缀,create-react-app要求是REACT_APP_前缀。我遇到过同事把变量写成API_URL,怎么访问都是undefined,找了半天才意识到是前缀问题。

2.3 构建产物验证的独门技巧

构建完成先别急着传服务器。我习惯在本地起一个静态服务器验证产物是否正常:

cd dist # 用npx起一个静态服务,模拟服务器环境 npx serve -s . -l 8080

打开http://localhost:8080,重点检查三件事:

  1. 页面是否正常渲染——不是白屏
  2. 点击页面里的链接,刷新浏览器——刷新后是否404(本地静态服务器可能复现不了Nginx的try_files配置,但至少能看出路由模式是否正常)
  3. F12看Console——有没有红色报错,特别是资源加载失败、跨域错误

如果你之前配置了history路由模式并且不带hash,本地npx serve刷新子路由也可能会404,这不算意外,正式在Nginx上配置了try_files就能解决。但如果页面加载就白屏,一定要先在这步解决,不要上传后再排查。

3. 服务器初始化:不是有一台机器就能直接用的

3.1 服务器选型与基础配置

国内的话,阿里云、腾讯云、华为云是主流选择,海外有AWS、Vultr、DigitalOcean等。作为一个个人博客或中小型React项目的部署目标,2核4G的配置绰绰有余,带宽按需买,一般5Mbps起步够用。系统我建议选Debian或Ubuntu LTS版——用的人多,教程通用,软件源干净,不像某些系统的包管理器会让你在安装Nginx时多折腾半小时。

如果你的服务器是全新的,到手第一件事不是装Node和Nginx,而是先做基础安全加固:

# 创建新用户,避免直接用root操作 adduser deploy usermod -aG sudo deploy # 修改SSH端口,可选但建议 # 配置SSH密钥登录,关闭密码登录 # 安装并启用防火墙 sudo apt update sudo apt install -y ufw sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable

这些操作虽然不是React部署的"核心",但在实际生产服务器上不做的话,过不了几天你就能在日志里看到大量暴力破解SSH的记录。另外服务器安全组(云控制台那边的规则)也要放行80和443端口,光改服务器内部防火墙没用,云平台自带的安全组会先拦一道。

3.2 安装Node.js和Nginx

服务器上需要Node吗?过去我直接回答"不需要",因为产物是纯静态文件。但后来发现很多人在服务器上可能还要重新构建、或者要跑一些脚本(比如配合CI的部署钩子),所以建议还是装一个。注意安装LTS版本即可,不要装最新版,免得遇到奇奇怪怪的兼容问题。

# 使用NodeSource安装指定版本的Node(以18.x为例) curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt install -y nodejs node -v npm -v

Nginx也一并装上:

sudo apt install -y nginx sudo systemctl enable nginx sudo systemctl start nginx

装完在浏览器访问服务器IP,如果看到Nginx默认欢迎页,说明80端口通了、Nginx在正常工作。看到这一步基本就放心一半了。

3.3 连接服务器的方式:从密码到密钥

在部署过程中,你需要频繁往服务器上传文件、执行命令,连接工具建议用VS Code的Remote-SSH插件。这个插件真的省事——打开VS Code,装好插件,配置好SSH连接,就能像在本地一样编辑服务器上的文件、直接使用终端。

VS Code连远程服务器需要先配置~/.ssh/config文件:

Host my-server HostName 你的服务器IP或域名 User deploy Port 22 IdentityFile ~/.ssh/id_rsa

配置好后在VS Code命令面板执行"Remote-SSH: Connect to Host",选my-server就能连上。这个方式是纯SSH协议,安全性没问题。如果你需要高速传输文件,可以用scp命令或者装个rsync,后面讲到自动化部署时会细说。

4. 上传与目录组织:别把项目源码扔服务器上

4.1 目录结构设计

很多新手部署完,服务器上还留着源码、node_modules、package.json,这种习惯不好。你上传到服务器的应该只有构建产物(build或dist目录里的东西),源码只存在代码仓库里就够了。

我个人的标准目录结构是这样的:

/var/www/ └── my-react-app/ ├── build/ # 静态文件(构建产物) ├── deploy.sh # 部署脚本(可选) └── nginx.conf # 参考配置(可选)

上传方式直接用scp:

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

如果文件很多、更新频繁,scp每次全量上传会比较慢。更好的方式是rsync,只同步有变更的文件:

rsync -avz --delete ./dist/ deploy@你的服务器IP:/var/www/my-react-app/build/

这里有几个参数可以解释一下:

  • -a:归档模式,保留文件权限和时间戳
  • -v:显示详细输出
  • -z:传输时压缩,减少流量
  • --delete:删除服务器上产物目录里本地已不存在的文件,保证服务器是构建产物的精确镜像

4.2 权限问题:为什么"Nginx 403 Forbidden"总是找上你

上传完文件后,经常会遇到"403 Forbidden"错误。罪魁祸首绝大多数是目录权限和Nginx运行用户不一致。

Nginx默认以www-data用户运行。如果你的/var/www/my-react-app目录是deploy用户创建的,默认权限可能是755,www-data用户没有读取权限。解决办法:

# 把项目目录所属修改为www-data用户组 sudo chown -R www-data:www-data /var/www/my-react-app # 目录需要读和执行权限 sudo chmod -R 755 /var/www/my-react-app

顺便说一句,如果你用了root用户上传文件,目录权限是700,那Nginx完全没权限访问,403就来了。自己的服务器上,文件权限尽量保持755(目录)和644(文件)就对了。

5. Nginx配置:部署React项目的核心关卡

5.1 一份能直接用的Nginx配置详解

这是整个部署过程中最关键的一步。我直接给出一份供参考的Nginx站点配置,React单页应用按这个改一下域名和路径基本就能跑:

server { listen 80; server_name example.com; # 改成你的域名或IP # 开启gzip压缩,减少传输体积 gzip on; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_min_length 1024; root /var/www/my-react-app/build; index index.html; # 关键配置:处理前端路由 location / { try_files $uri $uri/ /index.html; } # 静态资源缓存,提升二次访问速度 location ~* \.(js|css|png|jpg|jpeg|gif|svg|webp|woff2?)$ { expires 30d; add_header Cache-Control "public, no-transform"; } # 禁止访问隐藏文件 location ~ /\. { deny all; } }

这个配置里的灵魂是这一行:

try_files $uri $uri/ /index.html;

这行的作用:当用户访问https://example.com/about时,Nginx先去找/var/www/my-react-app/build/about这个文件,不存在再找/about/目录,还是找不到就把请求重写到/index.html。这样React的前端路由就接管了,页面组件根据URL渲染对应视图。少了这一行,刷新子路由就404,这是React部署最经典的一个坑。

5.2 从HTTP到HTTPS:Let's Encrypt证书配置

现在部署新站点,我默认直接上HTTPS。原因很简单:浏览器对HTTP站点的限制越来越多,比如获取地理位置、麦克风权限、PWA(以及navigator.serviceWorker)等API都要求安全上下文;另外如果API接口是HTTPS的,从HTTP页面去请求,会直接报跨域或Mixed Content错误。既然部署,别给自己留隐患。

用Certbot申请Let's Encrypt证书非常快:

# 安装certbot和nginx插件 sudo apt install -y certbot python3-certbot-nginx # 自动获取证书并改Nginx配置 sudo certbot --nginx -d example.com

Certbot会自动帮你改Nginx配置,加好证书路径、自动跳转。证书每90天过期,但Certbot会装一个定时任务自动续期,不需要你操心。

需要补充一个场景:如果你只有IP、没有域名,也别急着放弃HTTPS。Let's Encrypt不签IP证书,但可以通过自签名证书配合acme.sh等方式处理,不过对个人项目来说,用IP访问时直接用HTTP问题也不大——局域网内部署、开发环境测试,HTTP就够用了。

5.3 Nginx配置修改后的生效与检查

改完配置文件,别直接完事,执行一下检查再重载:

# 测试配置语法 sudo nginx -t # 平滑重载配置 sudo systemctl reload nginx

nginx -t这个检查很重要,我曾经手抖在配置里少写了一个分号,直接reload就把Nginx搞崩了。每次改完配置先nginx -t确认没有语法错误,再重载,已经成了肌肉记忆。

6. 踩坑实录:白屏、404、代理404、端口不通

6.1 完整排查链路:从白屏到定位问题

部署完后最常见的问题就是白屏。我把完整的排查链路写一下,遇到问题按这个顺序来,基本不会漏。

第一步:看页面源代码和Network面板

打开浏览器按F12,先看Console和Network:

  • 如果index.html都返回不了,看Nginx日志:sudo tail -f /var/log/nginx/error.log
  • 如果index.html返回了,但JS/CSS加载404,看资源的路径和服务器上的真实路径是否对应
  • 如果JS加载成功了但页面白屏,看Console有没有报错信息

第二步:确认构建产物的资源路径

举个例子,之前有个项目用Vite构建完的index.html里是/assets/index-xxxx.js,我在本地用npx serve打开正常,但传到服务器的/var/www/app下后,浏览器访问http://IP/,它去请求的是http://IP/assets/index-xxxx.js,但我的文件实际放在了/var/www/app/assets/,Nginx root配置的就是/var/www/app。这里看起来应该没问题,但检查后才发现项目里配置了base: './',导致资源路径变成了相对路径,在某些嵌套路由下就凑出了404路径。这种问题就要通过把base调成'/'或者按部署子路径来配。

第三步:查看Nginx配置和物理路径是否对得上

用curl直接在本机测试:

curl -I http://127.0.0.1/ curl -I http://127.0.0.1/assets/index-abc123.js

如果JS返回404,看看这个文件在服务器上到底存不存在、路径是否多了或少了一层。这一步能快速定位是Nginx配置问题还是文件没传对。

6.2 子路径部署的坑:base路径引发的连锁反应

如果你确定要把React项目部署到某个子路径下,比如http://example.com/react-app,Nginx配置要改成:

server { listen 80; server_name example.com; location /react-app/ { alias /var/www/my-react-app/build/; try_files $uri $uri/ /react-app/index.html; } }

注意这里用的是alias而不是root,区别在于:

  • root /var/www/my-react-app/build;+location /react-app/:实际访问路径为/var/www/my-react-app/build/react-app/index.html(会把location路径拼在后面)
  • alias /var/www/my-react-app/build/;:实际访问路径为/var/www/my-react-app/build/index.html(直接替换location路径)

同时构建时要在vite.config.ts里设置base: '/react-app/'(或在create-react-app里设置homepage),否则JS/CSS资源全按绝对路径请求,还是会404。子路径部署的坑在于"构建时的base、路由器的basename、Nginx的location"三者必须保持一致,漏一个页面就废。

6.3 接口代理配置:如何在生产环境解决/API跨域

很多React项目在开发时靠Vite的proxy或者WebpackDevServer的proxy把/api代理到后端,解决了跨域问题。但生产环境这些代理全部失效——你的Nginx才是那个"代理"。

后端接口单独在另一台服务器(或另一个端口)时,Nginx里加一个反向代理配置:

server { listen 80; server_name example.com; root /var/www/my-react-app/build; index index.html; location / { try_files $uri $uri/ /index.html; } # 代理/api到后端服务 location /api/ { proxy_pass http://127.0.0.1:8080/api/; 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_set_header X-Forwarded-Proto $scheme; } }

这里提供一个我在实际项目中常加的配置项:proxy_read_timeout。如果后端的接口处理比较慢,默认的60秒超时时间可能不够,建议设置成proxy_read_timeout 300;,避免长时间接口还没处理完,Nginx先给你断了。

6.4 端口无法访问的排查顺序

某个项目的页面打不开,但你在本地curl一切正常,这种时候极大概率是端口没开放。按顺序排查:

  1. 云服务器安全组/防火墙规则:登录云控制台,看80/443端口是否放行。这一步最容易被忽略——很多人配好了服务器里的ufw,但忘了控制台安全组。
  2. 服务器内部防火墙:sudo ufw status,确保80/443端口是allow状态。
  3. Nginx监听端口:sudo netstat -tlnp | grep nginx,确认Nginx确实监听了80/443。
  4. 本地网络测试:telnet 你的IP 80,从本地看端口是否通。

这四步走完,端口问题基本能定位出来。注意,腾讯云和阿里云在轻量应用服务器上还有一层"防火墙"设置,和ECS的安全组是两个概念,轻量服务器要单独去轻量控制台检查。

7. 版本更新与自动化部署:从手动到脚本化

7.1 手动更新流程的"标准操作"

项目上线后,每次发版就涉及到更新服务器上的文件。最简单的手动流程是:

# 本地执行 npm run build rsync -avz --delete ./dist/ deploy@你的服务器IP:/var/www/my-react-app/build/

这里注意--delete参数在"删除旧版本中已不存在的文件"时很有用,但也会把服务器上其他手工放进去的文件一并删除,所以使用前一定要确认目录结构。

真正线上的项目,在更新前还应该考虑"备份"——把旧版本复制一份:

cp -r /var/www/my-react-app/build /var/www/my-react-app/build_backup_`date +%Y%m%d%H%M`

这样新版本出问题了,能快速回滚:改Nginx的root指向或直接恢复目录。

7.2 用脚本一键完成"构建+上传+重载"

每次手动敲命令虽然不复杂,但次数多了总会漏步骤。我个人的做法是写一个简单的部署脚本,放在本地项目根目录:

#!/bin/bash # deploy.sh - 本地构建并部署到服务器 set -e # 任何一步失败,立即终止脚本 SERVER="deploy@你的服务器IP" REMOTE_DIR="/var/www/my-react-app" echo "===== 1. 本地构建 =====" npm run build echo "===== 2. 同步文件到服务器 =====" rsync -avz --delete ./dist/ $SERVER:$REMOTE_DIR/build/ echo "===== 3. 设置权限 =====" ssh $SERVER "sudo chown -R www-data:www-data $REMOTE_DIR && sudo chmod -R 755 $REMOTE_DIR" echo "===== 部署完成 ====="

在本地执行bash deploy.sh即可。脚本里用到了set -e,保证构建失败时不会继续往服务器上传坏文件——这个小细节帮我避免过好几次事故。

7.3 更进一步:用Docker和CI/CD彻底解放双手

如果项目要继续迭代、需要多人协作,脚本化还不够,更理想的方式是CI/CD + Docker。Docker部署React项目的核心是:

多阶段构建,让镜像只包含最终的静态文件:

FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci 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;"]

配合GitHub Actions,在push代码后自动构建并推送到服务器:

name: Deploy to Server on: push: branches: [ main ] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 18 - run: npm ci - run: npm run build - name: Deploy to server uses: appleboy/scp-action@v0.1.4 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} source: "dist/*" target: "/var/www/my-react-app/build" strip_components: 1

服务器上的/var/www/my-react-app目录挂载给一个Nginx容器(或直接用宿主机Nginx)。这套流程一旦跑通,以后每发一次版,push完代码就完事,页面自动更新。

不过如果你只是个人项目、更新频率一周一次,脚本化部署已经够用,没必要为了"自动化"而自动化。工具服务于人,部署流程越简单越不容易出错。

8. 最后的经验:安全和性能优化

8.1 隐藏服务器版本信息与安全响应头

Nginx默认会在HTTP响应头里暴露版本号,这个信息对攻击者来说是有用线索。隐藏掉它:

server_tokens off; # 添加基础安全响应头 add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always;

项目上线后,用curl -I http://你的域名看看响应头,如果没有明显的版本泄露,安全这块算基础分拿到了。

8.2 静态资源缓存策略

React项目构建后,JS/CSS文件通常带有hash指纹(比如index-abc123.js)。文件内容变了,hash就变,这意味着你可以放心对静态资源设置很长的缓存时间。我上面的配置里给了30天,如果你认为项目迭代很频繁,也可以只给7天。index.html本身不要设置缓存(或缓存几秒),否则用户访问的始终是旧版页面——这个问题可能比你想象中更容易踩到。之前有朋友就是因为给index.html也设置了expires 30d,导致发版后所有用户都要强刷才能看到新页面,体验非常糟糕。

正确的做法是:给带hash的静态资源设置长缓存,给index.html设置no-cache:

location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; }

8.3 内存和负载的简单考量

React静态站点的服务端压力其实不大,2核4G跑个Nginx托管静态文件完全没有问题。但如果同一台服务器上还跑了Node后端、数据库、Redis等一堆服务,就要注意整体的资源占用。用htop或free -h看一眼,内存长期在90%以上,就该想想是不是要升级配置或者优化服务数量了。

云服务器厂商一般都有监控报警,设一个CPU和内存的告警阈值,比如CPU超过80%持续5分钟就发短信通知,这样不用每天盯服务器,有问题早点知道。这个小设置非常值得花两分钟搞定。

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

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

立即咨询