本地npm run dev跑得好好的,build也没报错,结果传到服务器上一打开全白屏。这个场景我见过太多次了,而且不只是刚转行的新手,在职前端一不小心也会踩进去。所以这篇就从一个真实的Vue项目出发,从打包到上线,把部署到阿里云服务器的每一步都拆开来讲,包括Nginx配置、history路由处理、反向代理、HTTPS、Docker部署方案,还有我在实战里踩过的各种坑。
这篇内容适合三类人:一是刚接触部署、只知道“npm run build然后传上去”的前端开发;二是自己做个人项目、想独立完成服务器上线的全栈学习者;三是团队里需要建立标准化前端部署流程的同学。看完之后,你不仅能把Vue项目顺利跑在阿里云服务器上,还能理解每一步背后的原理,以后再遇到白屏、404、跨域这类部署问题,能自己快速定位。
1. 部署前必须做好的三件事
很多人在部署这一步翻车,不是因为操作多难,而是准备工作没做齐。服务器没买对、域名没备案、本地还没验证build产物就直接传上去了,结果一上线全是问题。这里我先把部署前最容易忽略的三件事讲清楚。
1.1 服务器配置与系统选择
如果是个人学习项目或者小流量应用,阿里云的轻量应用服务器完全够用,2核2G内存、3M带宽的配置跑一个Vue静态站点加个轻后端绰绰有余。如果是正式上线的业务,建议ECS起步规格选2核4G,因为除了前端静态文件,后面还有Nginx、后端服务、日志采集这些要占资源。带宽这块别贪小便宜,3M带宽并发一高就很吃力,预算允许直接上5M。
系统镜像方面,我习惯选Ubuntu 22.04 LTS或者Debian系列,因为包管理工具apt比某些老系统好用很多,安装Nginx、配置证书都省事。阿里云控制台创建实例的时候会自动生成一个安全组,默认只放行了22端口(SSH用),所以购买时段落里,第一件事就是把安全组的80和443端口加入方向规则,授权对象填0.0.0.0/0。这一步不做,后面Nginx配得再好,公网也访问不了。
1.2 域名、解析与备案
我先说结论:只是自己测试,可以用服务器公网IP加非标准端口访问;正式做项目,一定要绑定域名并且完成备案。国内服务器有个硬性规则,域名解析到服务器后,必须通过ICP备案才能正常使用80和443端口,备案流程一般要10到20个工作日,所以域名和备案一定要提前规划,别等项目写完了才想起来。
域名解析很简单,在阿里云DNS控制台添加一条A记录,主机记录填www或者@,记录值填服务器公网IP,TTL默认就行。如果你没备案又想先看效果,可以把Nginx监听端口改成8080之类的非标准端口,在安全组里放行对应端口,再用http://IP:8080访问。这种方案临时验证可以,但线上正式环境我强烈不建议这么做,一个是浏览器会有“非标准端口”的怪提示,另一个是搜索引擎收录、微信分享这类场景都很挑剔。
1.3 先确认你的项目在本机“真的能打包”
很多人部署失败,其实问题在打包阶段就埋下了。dev模式跑得好好的,不代表build产物没问题。我在本地验证的流程是固定的:
npm run build npx serve -s dist -l 8080serve -s的意思是SPA路由重写,访问不存在的路径会回退到index.html,这和线上Nginx的try_files作用类似。打开http://localhost:8080,把页面点一圈,再看看Network面板里有没有404的资源。这一步能提前发现组件库样式缺失、图片路径写死、接口地址被硬编码进代码之类的问题。确认无误之后,再进入上传服务器的阶段。
2. Vue项目打包:别小看一个build
打包这一步看着就一行命令,但里面的配置细节能直接影响线上表现。这一节我会把vue.config.js里几个关键项的原理讲透,并给你一套可以直接抄的配置。
2.1 打包前必须检查的配置文件
Vue CLI项目根目录下的vue.config.js是打包的“总开关”。我一般会这样配置:
// vue.config.js const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, publicPath: '/', outputDir: 'dist', assetsDir: 'static', productionSourceMap: false, lintOnSave: false })这里重点解释四个项:
publicPath:静态资源引用时的基础路径。默认/,表示资源路径是/static/js/xxx.js,适合部署在域名根目录。如果你要用http://ip:8080/my-app这种子路径访问,就得改成/my-app/或者用相对路径./。很多白屏问题就是这里配置不对引起的。assetsDir: 'static':把所有js、css、图片集中到static目录下,方便后面Nginx按路径做缓存策略。productionSourceMap: false:生成环境关掉sourceMap,能显著减小dist目录体积,也避免源码映射暴露在公网。transpileDependencies: true:有些第三方依赖没做好ES5兼容,打开这个能降低一部分低版本浏览器报错概率。
如果你的项目用的是Vite,对应的是vite.config.js里的base字段,作用和publicPath一样,部署在根目录就填/,子路径就填相对路径。
2.2 接口地址别写死:环境变量要会用
我发现很多部署失败的项目,接口地址是硬写在代码里的,比如axios.defaults.baseURL = 'http://192.168.1.100:8080/api'。打包的时候把这个IP一起打进去,环境一换全乱套。正确做法是区分环境变量。
在项目根目录创建两个文件:
# .env.development(本地开发) VUE_APP_ENV = development VUE_APP_BASE_URL = /api # .env.production(线上构建) VUE_APP_ENV = production VUE_APP_BASE_URL = /api代码里统一用process.env.VUE_APP_BASE_URL来拼接接口地址。为什么要统一成/api?因为线上我们通常走Nginx反向代理,把/api开头的请求转发到后端服务,这样浏览器只和同源域名通信,不产生跨域问题。这个机制后面第5节会详细讲,现在先记住这个习惯:接口地址一律写相对路径,不要写死IP和端口。
2.3 打包后先本地预览dist产物
打包完成后,dist目录不能只看一眼文件在不在,要真实运行起来验证。一方面用上一节说的npx serve -s dist -l 8080跑起来看页面,另一方面做一次“资源路径体检”:按F12打开Network,刷新页面,看到js/css文件的状态码全是200,页面控制台没有红色报错,才算通过。
这里我再补充一个判断静态资源路径对不对的小技巧。如果你在浏览器地址栏访问http://localhost:8080/,页面正常;但访问http://localhost:8080/static/js/index.js出现404,说明资源基础路径和实际部署路径对不上。反之,如果页面里加载的js路径变成了/static/static/js/xxx.js这种双层的,就是assetsDir和publicPath组合出了问题。出现这类情况,优先检查vue.config.js里的路径配置。
3. 把dist上传到服务器的正确姿势
本地验证通过,接下来就是把dist目录传到服务器。这一步看似简单,但目录选错、上传工具不对,也会给后面埋坑。
3.1 选择合理的服务器目录
我见过很多人把Vue项目的dist直接解压到/usr/share/nginx/html,和Nginx默认页面混在一起。这样做不是不能跑,但后续维护很痛苦:项目多了分不清,权限混乱,回滚也麻烦。我推荐的目录结构是:
/var/www/ └── vue-app/ # 每个独立项目一个目录 ├── index.html └── static/ ├── css/ └── js/创建目录的命令:
sudo mkdir -p /var/www/vue-app sudo chown -R $USER:$USER /var/www/vue-appchown这一步是为了让当前用户有写权限,不然scp上传的时候会提示Permission denied。如果你用的是root用户登录,可以跳过权限修改,但我是建议日常操作别用root跑服务,权限太宽,出问题不好追溯。
3.2 上传方式对比与推荐命令
上传方式有几种,我列个表给你参考:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| scp | 简单直接,系统自带 | 断点续传差,大文件慢 | 小项目首次上传 |
| rsync | 增量同步,自动覆盖差异 | 命令参数略复杂 | 每次迭代发布推荐 |
| WinSCP/FinalShell | 图形化,可视化操作 | 不适合自动化 | Windows图形化操作 |
| 宝塔面板 | 网页上传,带文件管理 | 额外安装面板程序 | 不想折腾命令行的用户 |
如果你习惯命令行,直接用它是最省事的。首次上传用scp:
scp -r ./dist/* root@你的服务器IP:/var/www/vue-app/后续迭代我更推荐rsync,因为它是增量同步,只传有变化的文件,还能用--delete参数把远端多出来的旧文件删掉,保证服务器上的内容和本地dist完全一致:
rsync -av --delete ./dist/ root@你的服务器IP:/var/www/vue-app/注意./dist/结尾的斜杠不能省,它表示同步目录内容本身,而不是把dist这个文件夹再套一层。上传完成后,在服务器上对比一下文件数量和总大小:
du -sh /var/www/vue-app find /var/www/vue-app -type f | wc -l和本地dist对比,数值对得上再进行下一步。
4. Nginx安装配置与history路由处理
Nginx是整个Vue部署链路里的核心角色。它负责接收浏览器请求、返回静态文件、处理路由回退、做反向代理。这一步配置对了,项目就算真正跑起来了。
4.1 安装Nginx并理解配置目录
Ubuntu系统安装Nginx很简单:
sudo apt update sudo apt install nginx -y sudo systemctl enable nginx sudo systemctl start nginx安装完以后,浏览器直接访问服务器IP,看到Welcome to nginx页面就说明服务正常。然后理解一下Nginx的目录结构,这些文件各管各的:
| 文件/目录 | 作用 |
|---|---|
| /etc/nginx/nginx.conf | 主配置文件,全局参数 |
| /etc/nginx/sites-available/ | 所有站点的配置文件都放这里 |
| /etc/nginx/sites-enabled/ | 软链到available里的配置,启用站点 |
| /etc/nginx/conf.d/ | 另一种放置站点配置的目录,与sites方式二选一 |
Ubuntu的Nginx默认会启用/etc/nginx/sites-available/default这个默认站点。我习惯在sites-available里新建一个项目配置文件,再软链到sites-enabled,这样以后一个项目一个文件,互不干扰。
4.2 一个Vue项目的标准server配置块
下面是一份我常用的Vue单页面应用Nginx配置,可以直接复制,把server_name换成你的域名:
server { listen 80; server_name www.example.com example.com; root /var/www/vue-app; index index.html; # 核心配置:SPA路由回退 location / { try_files $uri $uri/ /index.html; } # 带hash的静态资源长缓存 location /static/ { expires 30d; add_header Cache-Control "public, immutable"; } # index.html 不缓存,保证每次发布后用户拉到新版本 location = /index.html { add_header Cache-Control "no-cache"; } }配置里最关键的是try_files $uri $uri/ /index.html;这一行。它的意思是:当用户访问一个路径时,先找有没有对应文件,有就返回文件;没有就尝试找目录;都没有就回退到/index.html,由前端路由接管。这一行决定了Vue Router的history模式能不能正常刷新,我后面专门讲。
写完配置后,启用站点:
sudo ln -s /etc/nginx/sites-available/vue-app.conf /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx注意,每次改配置之前一定要先执行nginx -t检查语法,提示“syntax is ok”再reload。这是我在服务器上养成的习惯,可以避免改错配置导致Nginx直接挂掉。
4.3 让Vue Router的history模式不再404
这是Vue部署时最经典的一个问题:本地用hash模式,地址长这样/#/home;上线前改成history模式,地址变成/home,然后在服务器上一刷新页面就404。
原因其实不复杂。hash模式下,#后面的内容纯属前端状态,浏览器不会把它发给服务器;history模式下,/home是一个真实请求路径,浏览器会把它发给服务器说“请给我这个页面”,但服务器上根本没有/home这个文件,只有/index.html,于是Nginx默认返回404。try_files就是解决这个问题的:找不到文件时回退到index.html,让前端路由去解析路径。
所以如果你用了history模式,Nginx配置里必须有那行try_files。如果你还是坚持用hash模式,那可以省掉回退逻辑,但我个人建议新项目都上history,毕竟URL更干净,也更利于SEO和分享。
4.4 静态资源缓存与gzip优化
Vue打包出来的js/css文件名都带hash,比如index.8f3d9c2a.js,只要内容变了hash就变。这种文件非常适合长缓存,用户第一次访问后,下次直接从本地缓存读取,不用再向服务器请求,加载速度会有明显提升;而index.html没有hash,必须走no-cache,否则用户可能一直拿到旧版本页面。这也解释了为什么缓存配置要分两套写,不是所有文件都适合缓存几十年。
gzip压缩可以进一步减少传输体积。在/etc/nginx/nginx.conf的http块中加上:
gzip on; gzip_min_length 1k; gzip_comp_level 6; gzip_types text/plain text/css application/javascript application/json image/svg+xml;配置完成后reload,用这个命令验证gzip是否生效:
curl -H "Accept-Encoding: gzip" -I http://你的域名/响应头里出现Content-Encoding: gzip就说明压缩已经生效。这一步做完,页面的首次加载时间几乎能减掉一半以上,是性价比最高的性能优化手段。
5. 前后端联调:Nginx反向代理、WebSocket与视频流
前端静态页面跑通只是第一步。真实项目一定涉及接口请求,而前后端分离架构下,接口跨域是上线时必踩的坑。这一节我把生产环境接口联调的几种方案讲清楚,顺便讲讲WebSocket和m3u8视频流怎么走代理。
5.1 开发时proxy与生产环境的差异
本地开发时,webpack devServer的proxy配置只是把“/api开头的请求转发到后端”这件事限定在本地开发服务器里。build之后的静态文件没有这个代理能力,浏览器发出的/api请求直接打到你的域名上,如果Nginx没有对应转发规则,Nginx会在站点根目录找/api这个文件,当然是404或者返回index.html。这就是“本地接口通,线上必挂”的根源。
解决思路有两条:一是后端开启CORS,允许前端域名跨域访问;二是用Nginx反向代理,把/api请求转发给真实后端服务。两条都可行,但我的推荐是方案二:这样后端服务可以完全不出现在公网,前端冷启动也不需要依赖后端修改配置,两边解耦更彻底。
5.2 用Nginx /api反向代理后端
在server块里加入下面这段:
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_read_timeout 60s; }这里有个细节特别容易翻车:proxy_pass结尾到底带不带斜杠。
- 写成
http://127.0.0.1:8080/;,客户端请求/api/login会被转发成http://127.0.0.1:8080/login,前缀/api被去掉了。 - 写成
http://127.0.0.1:8080;不带斜杠,客户端请求/api/login会被转发成http://127.0.0.1:8080/api/login,路径原样保留。
如果你的后端接口路径本身不带/api前缀,就用第一种;如果后端网关也需要/api前缀来路由,就用第二种。这个规则我一开始也常搞混,建议你按自己后端真实情况选,配完用curl http://你的域名/api/xxx验证一下。
5.3 WebSocket的升级与转发配置
如果Vue项目用到WebSocket,比如聊天、推送、实时进度条,代理配置要额外加两行:
location /ws/ { proxy_pass http://127.0.0.1:9501/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }核心在于Upgrade和Connection这两个Header。WebSocket建立连接时,浏览器会发送一个协议升级请求,Nginx默认的HTTP/1.0代理不支持这个机制,必须显式声明proxy_http_version 1.1,并把Upgrade和Connection透传给后端,否则前端只会看到连接反复断开。前端连接地址也要统一走ws(s)://你的域名/ws,不要写内网IP。
5.4 补充:播放m3u8视频流的代理配置
Vue项目里播放m3u8视频流(HLS)是另一个高频场景。m3u8本质是一个索引文件,里面写着一串ts分片地址,播放器拿到m3u8后,会按分片地址逐个请求视频数据。如果m3u8文件里写的是相对路径/live/segment_001.ts,这些分片请求会发到当前域名下,你要在Nginx做一层转发,指到实际提供视频流的服务或存储:
location /live/ { proxy_pass http://127.0.0.1:8888/; proxy_set_header Host $host; }前端播放时,视频地址就用https://你的域名/live/channel.m3u8。这里有一个很容易被忽略的点:如果网站已经开启HTTPS,m3u8和ts地址也必须全部走HTTPS,否则浏览器会拦截混合内容,视频直接黑屏,控制台报Mixed Content错误。这个坑我在实际项目中遇到过不止一次,排查思路往往要绕很久,你先知道这个规律,能少走很多弯路。
6. 进阶方案:用Docker部署Vue项目到阿里云
如果你所处的团队对环境一致性要求高,或者你希望部署流程能一键搞定、快速回滚,那Docker是绕不开的方案。它把构建、运行、依赖全部封装进镜像,实现了“在哪跑都一样”的效果。
6.1 为什么选择Docker以及适用场景
传统部署方式在每台服务器上都要手动安装Nginx、配置路径、拷贝文件,一旦要换服务器或者水平扩展,整个流程重来一遍,还很容易出现“上一个环境能用,新环境崩了”的情况。Docker把Nginx版本、配置文件、dist产物全部打进镜像,任何装了Docker的服务器都能直接跑,环境差异被彻底消解。
但Docker也不是银弹,如果只是单机部署一个个人Vue项目,没有扩容需求,传统Nginx部署反而更直观。我的建议是:个人小项目用传统方式,团队正式项目上Docker,两者分开选型。
6.2 多阶段构建的Dockerfile写法
下面直接给一份生产可用的多阶段Dockerfile:
# 第一阶段:构建vue项目 FROM node:18-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm config set registry https://registry.npmmirror.com RUN npm install COPY . . RUN npm run build # 第二阶段:拷贝dist到nginx镜像 FROM nginx:1.25-alpine COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]对应的nginx.conf(注意这是容器内的配置,不需要写sites-available那套复杂结构):
server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /static/ { expires 30d; add_header Cache-Control "public, immutable"; } }多阶段构建有个很明显的好处:最终镜像里只有Nginx运行环境和dist静态文件,没有Node、没有源码、没有node_modules,镜像体积能控制在几十MB,部署和启动都很快。我见过有人把node环境和源码全打进一个镜像,镜像体积几个GB,启动还慢,完全没有必要。
6.3 docker compose管理发布与更新
项目根目录创建docker-compose.yml:
version: '3' services: vue-app: build: . image: vue-app:latest ports: - "80:80" restart: always首次构建和启动:
docker compose build docker compose up -d更新版本时,本地改完代码后执行:
docker compose build --pull docker compose up -d查看日志:
docker logs -f vue-apprestart: always保证服务器重启后容器自动拉起,避免人不在场时服务起不来的问题。我还会在Dockerfile里显式加一行时区配置:
ENV TZ=Asia/Shanghai不加的话容器默认UTC时间,日志和定时任务的时间会差8个小时,排查问题的时候很容易被误导。
7. HTTPS与域名绑定:线上安全必备
到了这一步,你的项目已经在IP或域名上跑起来了,但还有一个现代Web应用绕不开的问题:HTTPS。没有HTTPS,浏览器会亮“不安全”的警告,很多高级API用不了,WebSocket连不上,搜索引擎的收录权重也会受影响。所以我强烈建议所有部署到云服务器的Vue项目都配上HTTPS。
7.1 申请SSL证书的两种方式
一种是阿里云上的免费SSL证书。登录阿里云控制台,搜索“数字证书管理服务”,申请免费证书,填写域名并完成DNS验证后,大约几分钟就能签发,有效期通常为一年。证书签发后下载Nginx版本的证书文件,里面包含.pem和.key两个文件。
另一种是Let’s Encrypt的免费证书,配合acme.sh自动续期,适合不想每年手动操作的人。个人项目用阿里云免费证书体验更简单,跟着控制台点就行。
7.2 配置Nginx的HTTPS server块
把下载好的证书上传到服务器的/etc/nginx/ssl/目录:
sudo mkdir -p /etc/nginx/ssl然后修改站点配置文件,增加443监听:
server { listen 443 ssl; server_name www.example.com example.com; ssl_certificate /etc/nginx/ssl/example.com.pem; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; root /var/www/vue-app; index index.html; location / { try_files $uri $uri/ /index.html; } } # HTTP强制跳转HTTPS server { listen 80; server_name www.example.com example.com; return 301 https://$host$request_uri; }ssl_protocols建议至少TLSv1.2,TLSv1.0和1.1因为安全性问题早就被主流浏览器弃用了。HTTP跳转用301,这样用户访问http地址时会自动跳到https,体验不中断。
7.3 HTTPS对Vue项目的影响
这里我想展开说一个特别容易踩的盲区:HTTPS站点里千万不要夹杂HTTP的资源请求。比如你后端接口用的是http://,图片用http://,m3u8视频用http://,浏览器都会拦截掉,页面看起来像坏了。控制台会出现Mixed Content的报错,这个坑的排查成本相当高。
如果Vue项目用到了WebSocket,连接地址也必须从ws://改成wss://,否则在HTTPS页面下同样会被阻止。所以你在接入第三方服务和后端接口时,要统一确认它们有没有支持HTTPS,如果有任何一个不支持,就要提前做兼容方案,比如通过Nginx再代理一层,把外部HTTP服务间接变成同源的HTTPS请求。
8. 部署遇到过的坑:常见问题与排查实录
最后一章,我把这些年部署Vue项目踩过的坑整理成速查表,再挑几个“高频雷区”展开讲讲,方便你对症下药。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 页面白屏 | 静态资源路径错误、dist上传不完整 | 看Console报错和Network,检查publicPath |
| 刷新后404 | 未配置try_files | Nginx加try_files $uri $uri/ /index.html; |
| 样式异常、图标不显示 | 字体/图片静态资源404 | 检查assetsDir、publicPath、资源是否完整上传 |
| 接口请求404 | Nginx反向代理路径错误 | 核对proxy_pass末尾是否带斜杠 |
| 接口跨域 | 后端未配置CORS或代理未生效 | 优先用Nginx /api反向代理 |
| 端口访问不通 | 安全组未放行、防火墙拦截 | 检查阿里云安全组和系统ufw/iptables |
| 502 Bad Gateway | 后端服务未启动或代理端口错误 | 检查后端进程、端口、Nginx error.log |
| 用户一直看到旧版本 | 浏览器缓存了无hash的index.html | html配no-cache,静态资源长缓存 |
8.1 白屏问题:第一步永远是看Console和Network
白屏是Vue部署中出现频率最高的问题,排查顺序我建议固定下来:先按F12打开Console,看有没有报错信息;再切到Network,刷新页面,看js/css状态码是不是200。如果Console里有“Uncaught SyntaxError”或“Unexpected token‘<’”,大概率是Nginx把JS文件当文本返回了,也就是请求路径错误,服务器返回了index.html而不是真正的js文件。这时重点检查publicPath配置和文件是否真的在对应路径。
如果Console提示“ChunkLoadError”或者404,说明分包文件没找到。这种情况先确认dist是否完整上传,再看静态资源路径是否和assetsDir一致。我教徒弟的笨办法是:在浏览器地址栏直接访问https://你的域名/static/js/xxx.js,能直接看到js内容,说明路径没问题;返回HTML说明路径层级错了。
8.2 路由刷新404:try_files的正确打开方式
这个问题我在前面讲过,这里再补充一个典型场景:有些开发者把try_files写在了location /static/里,或者压根没写。刷新404其实是Nginx在报真的404,因为服务器上没有/home这个文件。修改配置加一行:
location / { try_files $uri $uri/ /index.html; }改完记得nginx -t && systemctl reload nginx,不要光改不生效,以为配错了。
8.3 上传后页面还是旧版本:缓存问题
这个问题容易被误解成“没部署成功”,其实是浏览器把没有hash变化的index.html缓存住了。只要你给index.html设置了Cache-Control: no-cache,给带hash的js/css设置了长缓存,这个问题基本能根除。如果用户仍然反馈看到旧版本,可以在构建后给index.html文件名也加hash,或者让部署脚本在发布时先清CDN缓存,这是大型项目比较规范的做法。
8.4 接口跨域报错:先确认是预检失败还是代理没生效
浏览器报跨域时,我习惯先看请求发出去没有。Network里看不到这个请求,说明根本没到达后端,问题在前端或代理;能看到请求、看到响应,但被浏览器拦截,报Access-Control-Allow-Origin,那才是真正的跨域问题。走Nginx反向代理的项目,通常不会出现第二种情况,因为浏览器访问的是同源地址。如果后端选择用CORS方案,就要保证后端对OPTIONS预检请求正确处理,否则跨域是必然的。
8.5 端口不通:先安全组,再防火墙,最后Nginx
按这个顺序排查能少走很多冤枉路:先在阿里云控制台确认安全组放行了80/443端口;再在服务器执行sudo ufw status确认系统防火墙没拦住;最后systemctl status nginx确认Nginx进程活着。很多人急着检查Nginx配置,结果问题出在最前面的安全组,这就很浪费时间了。另外,Nginx只监听IPv4的话,有些云环境IPv6访问会失败,可以在配置里加listen [::]:80;同时监听IPv6,这类问题比较隐蔽,遇到“为什么手机能访问电脑不能”的情况,值得怀疑一下这个点。
8.6 一个隐藏坑:“vue打包后布局异常”的元凶
热搜词里有个“vue打包后布局异常”,我实际排查过几个案例,发现大部分原因根本不是代码逻辑,而是静态资源路径出错导致CSS或字体加载不全,呈现出来的效果就是“布局乱了”。比如Element Plus的图标字体、某些背景图,在本地开发用的是绝对路径,打包后publicPath没配好,线上加载404,图标消失后某些组件的宽度高度就跟着变形。遇到“布局乱了”这种问题,我的建议是先看Network里有没有404的字体或图片,排查完资源路径再看代码。有时候不是什么复杂bug,就是一个路径问题。
结尾
部署过几个项目之后,我的体会越来越深:Vue部署本身不复杂,难的是让整个发布流程稳定、可重复、不依赖某一个人。所以我最后分享一个小习惯——写一个部署脚本,把打包、上传、reload串起来。我自己的deploy.sh长这样:
#!/bin/bash npm run build rsync -av --delete ./dist/ root@你的服务器IP:/var/www/vue-app/ ssh root@你的服务器IP "nginx -t && systemctl reload nginx"有了这个脚本,以后发布就一行bash deploy.sh,不用再复制命令。如果是Docker方案,就把中间那段换成docker compose build && docker compose up -d,思路是一样的。以前我手动敲命令部署,每次都要提心吊胆,生怕漏了什么;现在流程固定下来,上线变成一件很自然的事。
希望这篇能帮你把Vue项目稳稳地跑在阿里云上。如果你在部署过程里遇到什么奇怪的坑,欢迎按我前面说的排查思路走一遍,大多数问题都能在Network和日志里找到答案。