一个Vue项目从本地开发环境跑到线上服务器,中间的弯弯绕绕比大多数人想象的多。我在帮朋友部署一个后台管理项目时,亲眼见过他买了阿里云服务器、装好Nginx、把dist目录传上去,结果打开公网IP只看到一个白屏,接着又是一通盲目操作,最后把系统搞到重装。这篇文章就是冲着这类问题来的——我会把一个Vue Web项目部署到阿里云服务器的完整过程拆开揉碎,从选服务器、做安全组规则、本地构建、Nginx配置,到HTTPS证书和部署后的排查思路,通通写清楚。不仅适合第一次碰服务器的新手,也适合部署过一两次但总在最基础问题上翻车的同学照着自查一遍。
1. 选服务器和系统:一个老部署者会怎么下手
很多教程上来就让你买服务器,这是最大的误导。部署Vue项目本质上只需要一台装有Linux系统、有公网IP、能跑Nginx的机器,但如果你的选型出了问题,后面所有步骤都会变得别扭。
1.1 阿里云轻量应用服务器还是ECS:按实际情况选择
阿里云上最常见的选择是轻量应用服务器和云服务器ECS。两者的核心差异不在于性能,而在于使用方式:
| 对比项 | 轻量应用服务器 | 云服务器ECS |
|---|---|---|
| 定位 | 开箱即用,面向单应用部署 | 通用计算,面向复杂架构 |
| 网络配置 | 自带防火墙控制台,操作直观 | 依赖安全组规则,需要理解端口授权 |
| 适合场景 | 个人项目、中小型Web站点、测试环境 | 生产环境、多服务部署、需要按需扩容 |
| 扩展性 | 受限,带宽套餐制 | 灵活,独立公网IP,带宽可调整 |
| 价格 | 通常更低 | 活动价差距不大,但后续付费项多 |
以部署一个Vue前端项目为例,如果你的后端也部署在同一台机器,或者只是纯前端静态页面,轻量应用服务器完全够用。选ECS的理由更多在于将来要上Docker、要扩容、要连负载均衡,或者你对安全组那套东西已经轻车熟路。
我个人的习惯是:小项目、博客、演示站直接用轻量;正经业务、要长期维护、可能横向扩展的,直接上ECS。别在这上面省那几十块钱,后面迁移一次的成本远高于差价。
1.2 系统镜像选择:Ubuntu还是Alibaba Cloud Linux
选系统这件事,社区里有无数争论,但放到部署Vue项目的场景里,核心诉求就三个:Nginx安装方便、命令网上搜得到、出了问题自己能排查。
Ubuntu 22.04 LTS是我用得最多的选择,原因很简单:apt包管理器对Nginx、Certbot这些常用软件支持非常友好,网上踩坑资料最多,遇到报错一搜全是答案。Alibaba Cloud Linux的优势是和阿里云生态结合得紧、内核优化过,但如果你不熟悉它,碰到问题反而难查。CentOS虽然存量用户多,但已经停止维护,新部署不建议碰了。
我推荐选择Ubuntu 22.04 LTS,64位版本。如果你有洁癖,可以先在本地用虚拟机或WSL把流程跑一遍,再上服务器操作。系统选完登录方式保持默认的密钥对认证,密码登录虽然简单,但容易被暴力破解,密钥认证要安全得多。
1.3 安全组和后端防火墙规则:先想好端口再放行
这一步是新手最容易忽略的。很多人买完服务器,装好Nginx,发现公网IP访问不了,第一反应是Nginx配置错了,结果其实是安全组没放行80端口。
无论你买的是轻量应用服务器还是ECS,都要先进入阿里云控制台找到对应的安全组或防火墙配置,检查以下端口:
- 22端口:SSH登录,必须放行,建议只放行你自己的IP,至少不要把来源设为0.0.0.0/0
- 80端口:HTTP访问,必须放行
- 443端口:HTTPS访问,后续加证书要用
- 8080或其他后端端口:如果后端服务要直接暴露,按需放行,否则不要开
这里有个细节:很多Linux发行版默认还开着firewalld或ufw,即使云控制台放行了端口,系统内部防火墙也会拦截。建议在服务器上直接执行一下检查:
sudo ufw status如果是inactive就跳过,如果是active,记得放行上述端口再继续。
2. 本地构建:把Vue源码变成可直接部署的静态包
很多人部署Vue项目时犯的一个根本性错误,是把整个项目源码传到了服务器上,然后在服务器上跑npm run dev。且不说服务器跑开发服务器有多不安全,光是性能表现和资源加载方式就和生产环境完全不一样。
Vue项目部署的正确姿势,是在本地执行构建命令,生成一个静态资源目录,然后只把这个目录传到服务器上交给Nginx托管。
2.1 Vite构建到底生成了哪些东西
Vite是目前Vue项目的主流构建工具。执行完npm run build后,项目根目录下会生成一个dist目录。这里的结构大致是:
dist/ ├── index.html ├── favicon.ico └── assets/ ├── index-abc123.js ├── index-def456.css └── logo.pngindex.html是入口文件,assets里是打包压缩后的JS、CSS和静态图片。文件名的hash是内容哈希,只要代码变化,文件名就会变化,这直接关系到浏览器缓存更新策略。
构建命令里的Vite配置会影响产物质量。检查一下你的vite.config.js,重点看build配置:
export default defineConfig({ build: { outDir: 'dist', assetsDir: 'assets', sourcemap: false, chunkSizeWarningLimit: 1500 } })sourcemap在生产环境一定设为false,否则等于把源码暴露给所有人,还会拖慢文件加载。
2.2 构建前必须确认的base、路由模式、接口地址
很多白屏问题的根源不在服务器,而在构建参数。三个地方必须提前检查。
第一是base路径。如果你的项目部署在域名根路径下,base设为默认的'/'即可;如果部署在子路径下,比如https://example.com/admin/,就要把base改为'/admin/'。否则index.html里引用的JS地址可能是/assets/index.js,实际在/admin/assets/index.js下,直接就404了。
第二是路由模式。Vue Router有两种模式:hash模式和history模式。hash模式URL里带#号,不需要任何服务端配置,但观感差,而且SEO不友好。history模式URL干净,但服务端必须配置try_files把请求转发到index.html,否则刷新页面就404。
第三是接口地址。前端代码里请求的API地址,不要写死成localhost。常见的做法是利用环境变量:
// .env.production VITE_API_BASE_URL='https://api.example.com'然后在代码中通过import.meta.env.VITE_API_BASE_URL读取。这样测试环境和生产环境打出来的包自动使用不同地址,不会出现本地好好的、上线后接口全部请求失败的情况。
2.3 dist包的自检动作
构建完成后,不要急着上传,先在本地用静态服务器验证一下:
npm run previewVite的preview命令会模拟生产环境,拉起一个本地静态服务器。打开浏览器访问,重点检查三项:首屏是否正常渲染、刷新页面是否404、接口请求是否指向了预期地址。这三项在本地都没问题,再往服务器上放也不迟。
这一步能过滤掉七成以上的部署问题。你想想,如果构建出来的包本身有问题,传上去再排查,等于把一条错误链路完整走一遍,时间成本高太多。
3. 连接服务器、上传文件、安装Nginx
选好服务器、构建好dist包之后,接下来就是和服务器打交道的时间了。整个过程可以拆成三个动作:连上去、传上去、装上服务。每一步都有一些常见的坑,我按执行顺序说一下。
3.1 SSH登录、创建部署账号、开放必要端口
用SSH连上服务器,如果你在买服务器时选择了密钥登录,可以直接使用本地终端或Windows Terminal连接。不建议用密码登录,也不建议用root账户跑日常操作。先创建一个部署账号:
sudo adduser deploy sudo usermod -aG sudo deploy然后编辑SSH配置文件/etc/ssh/sshd_config,把PermitRootLogin改为prohibit-password或no。改完后重启SSH服务:
sudo systemctl restart sshd新建一个SSH会话,用deploy账号登录,如果登录正常再断开root会话。这样即使服务器被入侵,攻击者也拿不到root权限,能少操很多心。
接下来是目录规划。我习惯这样组织:
/home/deploy/ └── www/ └── project-name/ └── dist/dist目录直接放构建产物,上层用项目名区分不同项目。这样做的好处是Nginx配置里只需要引用到项目名这一层,以后迭代升级直接把新的dist内容覆盖进去就行。
3.2 两种常见上传方式:scp/rsync与图形化工具
上传dist目录的方式有很多,我推荐用scp或rsync命令,方便且可控。
scp适合一次性上传:
scp -r ./dist deploy@你的服务器IP:/home/deploy/www/project-name/rsync更强大,支持增量同步、断点续传,对重复部署特别友好:
rsync -avz --delete ./dist/ deploy@你的服务器IP:/home/deploy/www/project-name/dist/注意rsync后面的斜杠。./dist/带斜杠表示把目录里的内容同步到目标目录;不带斜杠,会把dist目录本身也拷一份。--delete参数表示远端有但本地没有的文件会被删除,确保远端始终和本地保持一致。这个参数非常重要,尤其是部署新版本时,旧的JS文件可能残留,导致文件名引用错乱。
如果你不习惯命令行,也可以用WinSCP或FileZilla。不过图形化工具在上传大量小文件时速度很慢,而静态资源恰恰是典型的大量小文件场景。大量JS/CSS文件一个个传,效率低还容易漏。
3.3 安装Nginx并理解目录结构
服务器系统是Ubuntu的话,安装Nginx只需要两条命令:
sudo apt update sudo apt install nginx -y安装完成后,Nginx的默认页面就能通过公网IP访问了。先不要改配置,先用浏览器确认一下公网IP能访问到Nginx默认首页,这是检验安全组和防火墙是否放行80端口的金标准。
如果访问不了,回到阿里云控制台检查安全组是否放行了80端口,再检查系统防火墙。如果访问到了,恭喜,你已经走通了整条网络链路。
接下来需要理解Nginx几个关键目录和文件:
- /etc/nginx/nginx.conf:主配置文件,一般不动
- /etc/nginx/sites-available/:站点配置文件的存放目录,一个站点一个文件
- /etc/nginx/sites-enabled/:生效配置的目录,通常放软链接
- /var/log/nginx/access.log和error.log:访问日志和错误日志,排查问题第一信息来源
理解这些目录结构后再操作,你会对后续的配置心里有数。
4. 手写Nginx站点配置:静态资源、history路由、缓存一次搞定
Nginx配置是整个部署过程中含金量最高的一环。很多教程给的配置能跑,但经不起刷新、经不起流量、经不起迭代。我直接给出一份我在生产环境验证过的配置,然后逐段解释为什么这样写。
4.1 一个生产可用的server配置
在 /etc/nginx/sites-available/ 下新建一个配置文件,文件名用你的域名或项目名,叫 project-name:
server { listen 80; server_name example.com www.example.com; gzip on; gzip_types text/plain text/css application/json application/javascript image/svg+xml; gzip_min_length 1024; root /home/deploy/www/project-name/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /assets/ { expires 30d; add_header Cache-Control "public, immutable"; } 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_set_header X-Forwarded-Proto $scheme; } }然后建立软链接到sites-enabled:
sudo ln -s /etc/nginx/sites-available/project-name /etc/nginx/sites-enabled/project-name sudo nginx -t sudo systemctl reload nginxnginx -t是语法检查,任何配置改动后都要先跑一遍,确认通过再reload。不要直接restart,reload是无中断加载配置,restart会短暂中断服务。
4.2 try_files的机制:为什么刷新会404
配置里最关键的就是location /下的try_files $uri $uri/ /index.html;。这行的意思是:当请求一个路径时,先按原样找文件,找到就直接返回;找不到就找对应的目录,目录存在则返回目录下的index文件;还找不到就把请求交给index.html处理。
以history模式为例,用户在https://example.com/user/123这个地址刷新,浏览器会请求服务器上的/user/123这个路径。服务器上根本不存在这个文件,如果没有try_files,Nginx直接返回404。有了try_files,请求最后落到了index.html,Vue Router拿到URL后自己解析对应页面,页面就能正常渲染。
这是Vue history路由部署里最核心的一道配置。很多项目上线后一切正常,唯独刷新页面就404,八成就是漏了这行。
4.3 静态资源缓存与版本号策略
再看location /assets/下的配置:
location /assets/ { expires 30d; add_header Cache-Control "public, immutable"; }assets目录下的JS和CSS文件都带内容hash,文件名一变就说明内容变了。这类文件适合长缓存,设置为30天或者更久都没问题。immutable指令告诉浏览器这个文件在过期前不会变化,可以放心大胆使用本地缓存,减少请求。
这个策略依赖Vite构建时的hash机制。如果文件名不变化但内容变了,浏览器会一直用旧缓存,这就是很多人改了代码上线后发现还是旧页面的原因。所以部署新版本前,务必确认构建产物文件名是新的。Vite默认就是hash命名,你只要不在配置里把assetsDir改成固定名字就行。
5. 上线后的检查与故障排查:从白屏到接口异常
配置写完、Nginx重载完成,不代表部署就结束了。真正的考验从你打开浏览器访问公网IP那一刻才开始。这一章我按排查优先级,把最常见的几个故障场景和定位思路列清楚。
5.1 白屏:优先级最高的排查
白屏是Vue项目部署后最常见的翻车现场,表现就是打开页面一片空白,控制台可能没有任何报错。遇到白屏,第一件事不是看代码,而是打开浏览器的开发者工具,切到Network面板,刷新页面,看请求情况。
如果index.html请求返回200,但JS文件请求404,原因基本锁定在base路径或静态资源路径上。打开index.html的响应内容,看里面script标签的src路径是什么,再对照服务器目录是否有这个文件。如果路径是/assets/xxx.js,但你的项目部署在/admin/子路径下,那就是构建时base配置错了,需要改vite.config.js里的base值为子路径后重新构建。
如果JS文件也200了但还是白屏,打开Console面板看报错。最常见的报错是各种Uncaught TypeError,这类问题通常和代码本身有关,需要把本地构建产物再拉出来和线上对比。不过要提醒一句:本地跑npm run dev正常不代表构建产物没问题,dev和build是两套逻辑,必须以build产物为准。
5.2 资源加载404和MIME类型错误
资源加载404的情况除了base路径问题,还有一个隐蔽来源:文件上传不完整。rsync传着传着断了,或者用了--delete把目录删了但新文件没传完,都会导致数据不一致。
排查方法也很简单,在服务器上ls一下dist目录,对照本地文件数量和大小。更彻底的做法是在服务器上再跑一遍构建?不建议,服务器上要装Node环境不说,构建版本和本地环境可能不一致,容易偏差。直接本地构建后上传,服务器只负责托管。
MIME类型错误则比较特殊。当你看到控制台报Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of text/html,说明Nginx把JS文件当HTML返回了。这通常是因为请求的JS路径不存在,Nginx的try_files把请求兜到了index.html。回到资源404的思路排查,而不是纠结Nginx的MIME配置。Nginx自带的mime.types已经覆盖了绝大多数文件类型,不需要你手动加。
5.3 接口跨域与后端服务转发
如果前端页面能打开,但数据请求全部失败,先看请求的地址是什么。如果用相对路径,直接请求了当前域名,需要在Nginx的location /api/下做反向代理,把请求转发到后端服务地址,这就是上一章配置里那段proxy_pass的作用:
location /api/ { proxy_pass http://127.0.0.1:8080; }这样前端就不用关心后端部署在哪,统一走当前域名/api路径,由Nginx中转。既解决了跨域问题,又隐藏了后端服务细节。
如果后端服务部署在另一台服务器,把proxy_pass改成那台服务器的公网IP和端口,但要注意后端服务本身得接受跨域请求,或者在Nginx上添加代理请求头,比如proxy_set_header Host $host;,确保后端拿到的Host是原域名而不代理服务器的地址。
5.4 Nginx日志:排查问题的第一信息来源
很多人在服务器上排查问题时没有方向,对着浏览器控制台干瞪眼。我的建议永远是先看Nginx日志:
sudo tail -f /var/log/nginx/error.log sudo tail -f /var/log/nginx/access.logerror.log里记录了所有的请求异常,比如文件找不到、权限不足、连接超时等。access.log能看出请求的实际路径和返回状态码。把浏览器操作和日志对应起来,绝大多数问题都能在日志里找到直接线索。
举一个真实案例:有一次我部署完成后页面总是偶尔打不开,浏览器报502。排查下来发现后端服务因为内存不足被系统kill了,而Nginx的错误日志里赫然写着connect() failed (111: Connection refused)。没有日志,我可能还在前端代码里找原因。
6. 给站点加HTTPS和后续优化方向
HTTP访问在现在这个环境下已经不够看了,浏览器会提示不安全,部分浏览器功能也会受限。给站点加上HTTPS,在Nginx部署中算是一个收尾动作,但对站点安全和使用体验的提升立竿见影。
6.1 使用Let's Encrypt签发免费证书
Certbot是目前签发免费SSL证书最成熟的工具。安装方式:
sudo apt install certbot python3-certbot-nginx -y执行签发命令:
sudo certbot --nginx -d example.com -d www.example.com命令会自动修改Nginx配置,添加证书路径和443监听。执行过程中需要确认你的域名已经解析到了服务器IP,否则验证会失败。
Let's Encrypt证书有效期90天,必须配置自动续期:
sudo systemctl status certbot.timer系统已经默认带了续期定时器。手动测试续期是否正常:
sudo certbot renew --dry-run如果输出Congratulations,说明续期链路是通的,后面基本不用管。
6.2 Nginx中启用443并强制跳转
Certbot自动配置完后,你的Nginx配置里会多出443监听段和证书路径。再检查一下HTTP是否自动跳转到HTTPS。如果没有,手动加一个配置:
server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri; }这样用户访问http地址时会被302或301带走到https。301是永久跳转,浏览器会缓存跳转结果,如果你将来要回退HTTP,用户浏览器可能还保留着旧跳转,所以前期建议先用302测试,确认没问题再改成301。
这里还有一个常被忽略的注意点:如果你的前端代码里有硬编码的http请求,或者访问了外部的http资源,即使页面本身就是https,浏览器也会因混合内容拦截这些请求。部署完HTTPS后,回头检查一下页面里的视频、图片、接口地址,确保都是https开头。
6.3 自动化部署的进阶方向
基础部署跑通之后,手动上传dist再reload Nginx这套流程会让人越来越不耐烦,尤其是项目迭代频繁的时候。常见的优化路径有两步。
第一步是脚本化部署。把上传和重载打包成一个shell脚本:
#!/bin/bash npm run build rsync -avz --delete ./dist/ deploy@服务器IP:/home/deploy/www/project-name/dist/ ssh deploy@服务器IP "sudo systemctl reload nginx"每次发布版本只需要执行一次脚本。虽然仍然手动,但大大减少了出错机会。这个脚本建议放进项目仓库,团队所有人都能对着文档跑。
第二步是引入CI/CD,比如GitHub Actions、阿里云流水线等。代码提交到分支后自动构建、自动测试、自动部署。这套体系的好处不只是省事,更在于把部署过程标准化,谁触发都一样,不会出现有人传错目录的情况。等你在服务器上折腾过几次、熟悉了手动流程之后,再上自动化,会深刻体会到什么是真正的省心。
部署一个Vue项目到阿里云服务器,说到底就是在服务器上放一份静态文件,然后让Nginx把请求正确路由到那份文件。但十个人部署,十个人会遇到不同的坑,安全组没放行、base路径没配好、history模式没处理、缓存策略没设计,任何一个环节都可能让你卡半天。我个人最大的感受是:部署不是把文件丢上去就完事,而是在本地、服务器、网络三层里把逻辑跑通,并且验证每一层的输出。下次你再遇到白屏或404,先别急着到处搜命令,回到构建配置、Nginx配置和日志这三件事上按顺序查一遍,八成问题就浮出水面了。