1. 项目概述与方案选型
1.1 这次要完成什么
你在 WebStorm 里写了一个 Vue 项目,本地跑npm run dev一切正常,但总不能把开发服务器永久开在自己电脑上给别人访问。所以要做两件事:第一,用 WebStorm 把 Vue 源码打包成浏览器可以直接运行的静态文件(dist 目录);第二,把这堆静态文件上传到服务器,再用 Nginx 托管起来,让用户通过域名或 IP 访问到你的页面。
整体流程不复杂,核心链路就一条:源码 -> WebStorm 执行 npm 脚本 -> 生成 dist 目录 -> 上传服务器 -> Nginx 指向 dist -> 浏览器访问成功。但这条链路上每一步都有可踩的坑,尤其是 Vue 路由用了 history 模式、接口地址写死、静态资源用了绝对路径这几种情况,打包出来看着是好的,部署上去全白屏,新手很容易在这上面卡一整天。
1.2 为什么用 WebStorm 来操作
很多人觉得打包就是敲npm run build,用不用 IDE 无所谓。但 WebStorm 在工程管理上的优势非常明显。它对 Vue 语法、TS 类型、ESLint 的校验是开箱即用的,写代码的时候就能拦截掉不少低级错误。真正到了打包环节,WebStorm 内置的 npm 工具窗口可以直接可视化运行 package.json 里的脚本,点一下运行,控制台输出和你敲 CLI 完全一样,还能保存运行配置,下次一键重跑。
对于不常接触命令行的前端同学来说,WebStorm 提供的这个图形化入口非常友好。你不需要记npm run build还是npm run prod,在 npm 工具窗口能直接看到所有脚本名。我建议你不管用什么工具,都把 WebStorm 的 npm 窗口用熟练,因为后面跑测试、检查 lint、生成构建报告都用得上。
1.3 部署方案怎么选
Vue 打包产物的本质是静态文件,所以部署方案灵活度很高。主流有以下几种:
- 最简单的做法:把 dist 里的文件通过 scp、rsync 或 FTP 传到服务器,然后包一层 Nginx 静态站;
- 进阶一点:把 dist 提交到 Git 仓库,服务器上拉代码再拷贝到 web 目录;
- 再往上走:配置 CI/CD 流水线,push 代码后自动构建、自动发布。
标题既然点名“部署到服务器”,我就以手动部署为主线,因为手动部署一次能让你把整个链路彻底摸透。了解底层逻辑之后,你再看 CI/CD 的配置会轻松很多。服务器这边默认用 Linux + Nginx,这是目前 Vue 项目最主流的托管方式,静态文件解析效率高,配置也简单。
1.4 这篇内容适合谁看
适合刚接触 Vue 项目、第一次准备把自己写的页面挂到公网服务器上的开发者。也适合那些本地能跑起来、一到服务器就白屏的倒霉蛋。内容从环境准备开始讲,一直说到排查问题,不挑操作系统,Windows 和 macOS 都通用,只要别把命令里的路径照抄错。
2. 环境准备与 WebStorm 配置
2.1 Node.js 环境必须对齐版本
打包 Vue 项目第一步是确认 Node.js 版本。WebStorm 本身不负责编译 JS,它只是把你的命令转交给 Node.js 环境去执行,所以 Node 版本错了,后面全是白忙活。
Vue CLI 创建的老项目(vue.config.js 那套)一般要求 Node 12 以上,Vite 创建的新项目(vite.config.js 那套)要求 Node 16 甚至 18 以上。你在终端里先执行node -v和npm -v看一下版本。如果版本太老,建议直接去官网下载 LTS 版本装最新稳定版。
装完 Node.js 之后,WebStorm 需要知道你用的是哪个 Node 解释器。
注意:如果你电脑上装了多个 Node 版本(比如 nvm 管理),一定要在 WebStorm 里指定同一个解释器,否则命令行能跑,WebStorm 里报找不到 Node,非常容易踩。
打开 WebStorm 的Settings | Languages & Frameworks | Node.js,把 Node interpreter 选到你的 node 可执行文件路径。npm 那一栏会自动识别。
2.2 WebStorm 里跑 npm 命令的三种方式
第一种:打开项目里的 package.json,你会在文件右侧看到一个绿色的运行箭头,或者一个写着 npm 的小面板,直接点脚本名称即可执行。
第二种:点击底部工具窗口的Terminal,在这里敲命令。这个方式和在系统终端里敲命令完全一样,而且它自动继承了当前项目的环境变量,我平时用这种方式更多。
第三种:通过Settings | Tools | Tasks | npm添加运行配置。这种方式适合自定义命令,比如npm run build -- --mode=production,可以把固定参数存成一个运行配置,下次直接点运行。
三种方式没有高下之分,我个人的习惯是:日常调试用 Terminal,打包和发版用 WebStorm 的 npm 配置,因为配置可以命名部署构建、测试构建,下次一眼就能认出是哪个环境。
2.3 推荐的项目结构
接手的 Vue 项目无论用 Vue CLI 还是 Vite,都需要确认几个关键目录和文件是否存在。一个标准的项目通常包含:
package.json:项目依赖和脚本入口;node_modules:依赖包目录,一般由npm install生成;public/或static/:静态资源目录,打包时会原样拷贝到 dist;src/:源码目录;vue.config.js或vite.config.js:构建配置入口。
如果你拿到的是一个空目录,只需要在 WebStorm 的 Terminal 里执行npm create vue@latest或按照官方文档初始化项目即可。这里不展开脚手架细节,但要注意:用 WebStorm 打开项目时,最好让 IDE 索引完成再操作,否则脚本窗口可能显示空白。
2.4 初始化构建脚本检查
在 WebStorm 底部 npm 工具窗口里展开 scripts,正常情况下能看到serve、build、lint、preview之类的脚本。打包请认准build脚本,一般是vue-cli-service build或者vite build。
先运行一次npm install确保依赖齐全。Windows 上如果出现node-sass或者python相关的编译报错,说明项目的原生依赖比较老,可以直接在 Terminal 里执行npm install --force或者npm install --legacy-peer-deps碰碰运气。Linux 服务器上也一样。
3. 打包环节的关键细节
3.1 一次完整构建过程发生了什么
执行npm run build之后,WebStorm 会把命令交给 npm,npm 再调用构建工具将 Vue 的 SFC(单文件组件)编译成普通的 HTML、CSS、JavaScript。
Vue CLI 底层走 webpack,会先解析入口文件,接着递归读取所有 import 依赖,通过 loader 处理 vue、js、css、图片等资源,最后做代码合并、压缩、资源指纹生成,输出到 dist 目录。Vite 的思路不太一样,它开发时用原生 ESM,快得离谱;构建时则用 Rollup 完成打包,同样生成 dist。
不管底层用哪个,最终产物都是浏览器能直接识别的静态文件,不需要后端在运行时做任何编译。这也是 Vue 项目能部署到任意静态服务器上的原因。
3.2 打包前必须确认的几个配置
这是打包环节最影响成败的部分。我见过太多人本地npm run dev是好的,build 完部署上去就白屏,原因基本都出在下面几点。
第一,publicPath 或 base 配置。Vue CLI 项目里,vue.config.js中的publicPath默认是/,意思是打包后的 JS、CSS 资源路径会写成/js/app.js。如果你的网站部署在域名根路径,没问题;如果部署在子路径,比如http://server:8080/myapp/,资源路径就会变成/js/app.js,请求打到根路径去,自然 404 白屏。
解决办法很简单:
// vue.config.js module.exports = { publicPath: './' }Vite 项目对应的是base配置:
// vite.config.js export default { base: './' }设成相对路径后,资源加载会基于当前页面路径来找,安全性更高,子目录部署和根路径部署都能跑。
第二,路由模式。Vue Router 默认是 hash 模式,URL 长这样:http://server/#/home,部署最省心。如果为了好看改成 history 模式,URL 变成http://server/home,服务器没有做 try_files 重写的话,刷新一下就是 404。这个坑我在下一章配置 Nginx 时会详细讲,反正打包前先确认自己项目路由用的是哪个模式。
第三,接口地址。项目里写的axios请求地址如果是http://localhost:8080/api,打包后还是这个地址,用户浏览器拿到页面后请求的却是用户自己的 localhost。这个问题一般通过区分环境变量解决:在.env.production里写VUE_APP_API_URL=https://api.example.com,代码里读process.env.VUE_APP_API_URL。Vite 项目则用import.meta.env.VITE_API_BASE。
3.3 执行打包与产出检查
在 WebStorm 的 npm 工具窗口里双击build,或者直接在 Terminal 里npm run build,构建过程会输出到控制台。构建完成后,项目根目录会多一个dist文件夹。
我建议构建结束后不要急着上传,先在本地做一次预览。Vue CLI 项目可以执行:
cd dist npx serveVite 项目一般自带 preview 脚本:
npm run preview浏览器打开预览地址,如果能正常显示页面并且没有红色报错,说明这次构建的产物没问题。这一步能帮你把“代码问题”和“部署问题”隔离开,后面服务器上出问题时就少一个排查方向。
3.4 打包体积优化选项
打包体积太大会影响首屏速度,顺带说几个优化点。
先查看npm run build最后输出的文件列表,看看哪些 chunk 体积最大。Vue CLI 项目可以用webpack-bundle-analyzer插件辅助分析。常见优化操作是路由懒加载,即在路由配置里把组件改成:
const Home = () => import('@/views/Home.vue')这样首页不会一次性把全部页面代码拉下来。然后是第三方库优化,比如 lodash 改成按需引入,Element UI 或 Ant Design Vue 使用按需加载插件。对于小项目,这些优化可以放到后期,不急于第一次部署就做。
4. 服务器部署实操
4.1 服务器与 Nginx 环境准备
部署要用的服务器建议选 Linux 系统,Ubuntu 22.04 或 CentOS 7+ 都行。你拿到服务器后先做一件事:用 SSH 连上去,然后安装 Nginx。
Ubuntu/Debian 系统:
sudo apt update sudo apt install nginxCentOS/RHEL 系统:
sudo yum install nginx安装完成后执行sudo systemctl status nginx,看到 active(running)就说明基础环境跑起来了。如果服务器有防火墙,记得放行 80 端口:
sudo ufw allow 80还需要确认你买服务器的云服务商安全组策略里,入方向规则是否放行了 80 端口。很多人本地 Nginx 配好了,防火墙也关了,外网还是打不开,十有八九是云控制台的安全组没放行。
4.2 把 dist 传到服务器的几种办法
打包生成的 dist 目录通常在本地项目根目录下。接下来要做的就是把 dist 里的内容上传到服务器。
第一种推荐先用 scp,简单直接。在本地 Terminal(不是 WebStorm 里的 Terminal,除非你配置了 SSH)执行:
scp -r dist/* root@your_server_ip:/var/www/vue-app/前提是服务器上先创建好目录:
ssh root@your_server_ip mkdir -p /var/www/vue-app如果你有固定域名,并且想后续反复同步,可以试试rsync,支持增量同步,传大项目时比 scp 快很多:
rsync -avz --delete dist/ root@your_server_ip:/var/www/vue-app/如果想用可视化界面操作,FileZilla 或者 WebStorm 自带的 FTP 部署功能都可以。WebStorm 在Tools | Deployment | Configuration里配置 SFTP,设置好服务器地址、账号密码、映射目录后,右键 dist 目录就能直接上传。这种方式对不熟命令行的新手最友好,但要注意别把整个 dist 目录里的缓存文件传漏了。
下表简单对比一下几种方式:
| 方式 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| scp | 简单,一条命令 | 全量拷贝,慢 | 一次部署 |
| rsync | 增量同步,快 | 命令参数稍多 | 频繁更新 |
| FileZilla | 可视化 | 需要额外软件 | 新手入门 |
| WebStorm Deployment | 不离开 IDE | 配置映射略麻烦 | 喜欢 IDE 操作 |
4.3 Nginx site 配置详解
文件传上去之后,最关键的一步来了:配置 Nginx 站点指向你的 dist 目录。
在 Ubuntu/Nginx 里,推荐在/etc/nginx/sites-available/下新建配置文件,然后在sites-enabled里建软链接。创建一个vue-app文件:
sudo vim /etc/nginx/sites-available/vue-app写入如下配置:
server { listen 80; server_name your_domain_or_ip; root /var/www/vue-app; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这段配置里,root指向你上传静态文件的路径,try_files是专门为 history 路由模式准备的兜底方案。当用户访问/home这个路径时,服务器发现磁盘上没有对应的home文件,也不会直接返回 404,而是回退到index.html,由前端路由接管页面渲染。
如果你用的是 hash 路由模式,try_files有没有都不太影响,但写上不会出错。
再看/api/的proxy_pass,这是提供给前端打包后接口请求的代理。如果你没有后端接口,或者接口已经配置了跨域,可以去掉这个 location。有了它,前端代码里写/api/login,请求会被 Nginx 转发到本机 8080 端口,避免浏览器触发跨域拦截。
启用站点配置:
sudo ln -s /etc/nginx/sites-available/vue-app /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx执行nginx -t会检查配置语法,看到syntax is ok再 reload,不然配置错了会直接把 Nginx 搞挂。
注意:如果服务器上同时存在默认站点
/etc/nginx/sites-enabled/default,并且监听了同一个端口,你需要把默认站点删掉或改掉,否则访问时会打到默认页面而不是你的 Vue 应用。
4.4 部署后的验证清单
配置完成后,打开浏览器输入服务器 IP 或域名,正常会看到 Vue 应用的首页。
第一次验证时,我按下面这个顺序排查:
- 首页是否正常渲染,F12 控制台有没有红色报错;
- Network 面板里 JS、CSS 是否都加载成功,状态码是不是 200;
- 刷新一个内页路由,比如
/home,确认不会变成 404; - 点一个需要调用接口的功能,看
/api请求有没有正常返回。
这四条都过了,这次部署就算成功了。如果某一步翻车,直接跳到下一章看排查方法。
5. 常见问题排查手册
5.1 打包后页面白屏
白屏十有八九是资源路径错误。你打开浏览器 F12,看 Console 报错,如果是类似Failed to load resource: the server responded with a status of 404 (Not Found),再去 Network 面板看是哪个 JS 文件 404。
这时候检查打包配置publicPath或base。如果项目部署在域名根路径,publicPath: '/'是正常的;如果部署在子路径,比如http://ip:8080/foo/,就要设置成/foo/或相对路径./。相对路径虽然省事,但有的时候会遇到 CSS 里的图片、字体路径问题。比较稳妥的做法是把 vue.config.js改为:
publicPath: process.env.NODE_ENV === 'production' ? '/your-sub-path/' : '/'然后 Nginx 增加对应 location,或者把站点根目录指向子路径。具体看你项目实际部署的位置。
另外还要检查是不是路由 history 模式在本地 preview 没问题,但在服务器上刷新 404。这种情况按 5.2 处理。
5.2 路由刷新 404
如果你的 URL 是http://server/home这种没有#的形式,刷新一下就 404,基本可以确定是 history 模式服务端没兜底。
处理办法就是刚才配置里的:
location / { try_files $uri $uri/ /index.html; }注意这行配置必须写在location /块里,如果只写在某个子路径下,内页路由照样 404。
如果你用的是 Apache 或者其他 Web 服务器,原理一样,都是把不存在的路径重写到 index.html。如果你完全不想折腾服务端,也有一个省事方案:把 Vue Router 改成 hash 模式。
const router = new VueRouter({ mode: 'hash', routes })Vite 项目的路由配置类似,把createWebHistory改成createWebHashHistory。坏处是 URL 里多个#,不够美观;好处是部署难度骤降,任何静态文件服务器都能直接跑,不会出现刷新 404 的问题。
5.3 接口连不上或跨域
部署后页面能打开,但数据全是空的,接口请求一直报错,这种情况要分几类看。
第一类,接口地址写死了localhost。前面说过,环境变量没区分,打包产物里全是http://localhost:8080,用户浏览器一执行就会去请求用户自己的电脑,必挂。重新配置.env.production里的请求地址,然后重新打包。
第二类,接口域名是 https,页面是 http,浏览器会报混合内容拦截。要么页面也走 https,要么接口降级为 http,总之协议要一致。
第三类,跨域问题。如果接口地址和页面地址不同源,浏览器会拦截。解决办法是在 Nginx 层做反向代理,前端始终请求同源地址/api/xxx,Nginx 再把请求转发到真实后端。
因此我们在 Nginx 配置里提前写好/api/的 proxy_pass,就是为这个场景准备的。要注意proxy_pass http://127.0.0.1:8080;后面有没有斜杠,行为有区别。带斜杠的http://127.0.0.1:8080/会去掉/api前缀,不带斜杠则保留完整路径。按需选择即可。
5.4 更新后还是旧页面
改了代码,重新 build 再上传,用户浏览器还是旧页面,这种问题根源在缓存。
最科学的应对方案是用好文件名指纹。Vue CLI 和 Vite 打包时,带 hash 的文件名规则一般是app.8f3k2c.js,只要文件内容变化,文件名就会变,浏览器自然会请求新文件。所以你需要保证 index.html 不被强缓存。
Nginx 里可以这样处理:
location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; } location /static/ { expires 30d; add_header Cache-Control "public, immutable"; }这样 HTML 每次都回源检查,带 hash 的静态资源可以放心长缓存。如果你没有自定义静态资源目录,而是按默认放在根路径,可以根据实际目录结构调整 location。关键思路是:HTML 不缓存,JS/CSS 缓存。
上传文件时,用 rsync 的--delete参数可以清理服务器上已经不再使用的旧文件,避免旧资源还在但是新代码引用了新文件名,最终把服务器磁盘堆满。
5.5 WebStorm 侧提示问题
如果你在 WebStorm 里点 npm 脚本没反应,首先检查右下角有没有显示 Node.js interpreter 未配置。到Settings | Languages & Frameworks | Node.js选对 Node 路径,问题马上解决。
如果构建时内存溢出,控制台报JavaScript heap out of memory,可以在 package.json 的 build 脚本里加参数:
"build": "node --max_old_space_size=4096 node_modules/@vue/cli-service/bin/vue-cli-service build"或者给 Vite 项目设置:
NODE_OPTIONS=--max_old_space_size=4096 npm run buildWindows 下NODE_OPTIONS写法略有差异,可以搜索一下对应系统语法。
6. 个人踩坑后的几点体会
这套 Vue 打包部署流程,我自己第一次跑通花了两天整,其中一大半时间耗在白屏和跨域上。走通之后你会发现,真正难的不是命令,而是理解打包生成的资源路径、路由和服务器的关系。
说几个后来才悟到的经验:
第一,所有路径问题先分清“前端资源路径”和“浏览器 URL 路径”。前者由 publicPath 或 base 控制,后者由 Nginx 的 root 和 try_files 控制,两者不是一回事,很多人搞混。
第二,无论用哪种 IDE,打包前先在本地把 dist 用静态服务器跑一遍,成本极低,但能提前暴露 80% 的部署问题。省下的时间远比敲那两行命令多。
第三,Nginx 配置改完一定要nginx -t检查再 reload。我有一次手滑少写了一个分号,整个站直接挂了,云监控疯狂报警。多一条检查流程不是保守,是保命。
最后再说一个小技巧:如果你经常需要更新部署,建议花五分钟写一个简单的部署脚本,把打包、上传、远程刷新三步合并成一条命令。比如在本地项目根目录放一个deploy.sh,内容用 rsync 指向服务器路径,配合 SSH 免密登录,以后更新就一行搞定。脚本别写太复杂,核心就三步:npm run build、rsync -avz --delete dist/ root@server:/var/www/vue-app/、ssh root@server "nginx -t && systemctl reload nginx"。第一次配好,之后每一次发布都会很舒服。
希望这篇把能踩的坑都写到的实战记录,能帮你少走点弯路。