ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Vue项目打包部署全攻略:从Nginx配置到路由缓存问题排查

Vue项目打包部署全攻略:从Nginx配置到路由缓存问题排查 做前端这几年Vue 项目打包部署这件事几乎绕不开。老实说不少同学本地 dev server 跑得飞快代码一提交、服务器上一发布就开始表演 404、白屏、样式丢失甚至接口全挂。其实大部分问题不是代码逻辑而是打包路径、路由模式、服务器回退规则、缓存策略这些部署层面的东西没有对齐。这篇文章把我平时在正式环境里跑通的一套 Vue 项目打包部署流程完整梳理一遍从构建配置、环境变量、Nginx 配置到问题排查尽量把每一步为什么这么做也讲清楚。适合刚开始接触部署的前端也适合那些部署过几次但一直被奇怪问题折磨的人。1. 打包部署前必须想明白的三件事1.1 你的项目最终要跑在什么环境下第一件事不是先敲打包命令而是先确定产物放哪。同一个 Vue 项目放在域名根路径、放在子路径、放在对象存储打包配置完全不同。放在根路径最省心publicPath直接用/HTML 里引用的 JS、CSS 都是绝对路径比如/static/js/app.xxx.js。这种情况下Nginx 配置里把root指到dist目录就能跑起来。如果项目要部署在https://xxx.com/admin/这种子路径下那就必须把publicPath设成/admin/同时 Vue Router 也要设置base: /admin/。否则你会发现首页能打开一旦刷新到某个子路由或者浏览器请求 JS 文件时路径全部跑到根域名下面去然后 404。还有一种常见场景是部署到静态托管比如对象存储或 CDN。这时候如果坚持用 history 路由模式就非常麻烦因为对象存储不会帮你做路由回退。多数人在这种场景下会退回到 hash 模式URL 里带#/刷新时浏览器始终请求的是index.html不需要服务端配合。所以动手之前先把“最终运行环境”定下来。这个决定直接影响后面所有配置。1.2 打包不是把代码“压一压”那么简单很多新手对打包的理解就是“把代码压缩得更小”其实 Vue 项目的打包是构建工具做的一整套资源加工流程。Vue CLI 底层是 webpackVite 项目则用 Rollup 做生产构建。无论哪个最终都会把.vue单文件组件里的 template、script、style 编译成浏览器能识别的 JS 和 CSS再做语法转译、代码压缩、Tree Shaking、按需加载、文件名 hash 等处理。最终产出一个dist目录里面有index.html以及一堆带 hash 的 JS、CSS、图片、字体文件。为什么本地 dev server 跑得好好的部署到线上就白屏因为本地开发时 dev server 是在内存里实时编译而且它本身就是个完整的静态服务器能处理所有路径。你把路由切换到/homedev server 会把内容挡下来返回给前端路由解析。但线上静态服务器没有这个概念它只认文件系统里的真实文件。如果你访问/home服务器找不到名为home的文件就直接 404 了。这也是为什么 Vue 部署教程里十有八九都会提到try_files配置。它本质上是告诉服务器找不到真实文件时把请求回退到index.html让前端路由自己去处理。1.3 环境变量和接口地址要分开管理我见过最坑的部署事故是把后端接口地址写死在代码里。本地联调用的是http://localhost:8081上线前忘记改用户打开页面后所有请求全部打到本地地址自然全挂。正确做法是用 Vue CLI 的环境变量机制按构建模式区分配置。项目根目录建.env.development和.env.production# .env.development VUE_APP_ENVdevelopment VUE_APP_API_BASE_URL/api# .env.production VUE_APP_ENVproduction VUE_APP_API_BASE_URLhttps://api.example.com然后在代码里通过process.env.VUE_APP_API_BASE_URL去拼接接口地址。执行npm run serve时加载 development 配置执行npm run build时加载 production 配置。这样从源头上避免把联调地址带到生产环境。需要注意webpack 在构建时会把process.env.VUE_APP_*变量直接替换成实际值也就是说接口地址是构建时写死进 JS 文件里的。如果上线后想改接口地址而不重新构建要么让后端做一层代理要么在 Nginx 里做请求转发要么引入运行时全局配置方案。对小团队来说最稳妥的方式还是重新构建一次因为流程简单、不容易出错。2. 构建配置细节把后路留好2.1 package.json 的 scripts 别只用默认 buildVue CLI 创建的项目package.json里通常有这些脚本scripts: { serve: vue-cli-service serve, build: vue-cli-service build, lint: vue-cli-service lint }日常开发用serve上线用build。但如果你有测试环境、预发布环境、生产环境最好把脚本拆细一点scripts: { serve: vue-cli-service serve, build: vue-cli-service build --mode production, build:staging: vue-cli-service build --mode staging, build:prod: vue-cli-service build --mode production }--mode指定的是构建模式配合对应的.env.staging、.env.production文件可以实现在不同环境注入不同接口地址和配置项。这一步看似多余但它能避免“测试环境验证没问题一上生产就挂”这种经典事故。还有一个建议在 CI 或服务器上构建时尽量用npm ci而不是npm install。npm ci会严格按照package-lock.json安装依赖速度快也不会因为依赖版本漂移导致构建结果和本地不一致。手动部署次数多了你会感谢这个习惯的。2.2 vue.config.js 里真正影响部署的配置项很多前端项目根本没有vue.config.js因为他们觉得默认配置够用。确实简单项目够用但只要涉及部署路径、接口代理、资源目录这些配置早晚要动。下面是我常用的一个基础配置模板const { defineConfig } require(vue/cli-service) module.exports defineConfig({ publicPath: process.env.VUE_APP_PUBLIC_PATH || /, outputDir: dist, assetsDir: static, indexPath: index.html, productionSourceMap: false, devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } })逐项说publicPath是最关键的一个。它决定构建出的 HTML 里引用 JS、CSS、图片的路径前缀。默认是/适合部署在域名根路径。如果部署在子路径就需要根据实际路径调整。outputDir是产物输出目录默认就是dist一般不用改。但如果你用 CI 发布可能会希望改成build或者按版本号输出目录方便留档。assetsDir指定 JS、CSS、图片等静态资源放在dist下的哪个子目录默认是static。比如assetsDir: static后产出的 JS 就在static/js/下。productionSourceMap强烈建议设为false。source map 在线上排查问题有点帮助但它会大幅增加产物体积而且暴露源码。如果确实要排查线上问题更推荐用错误监控平台收集堆栈而不是把 source map 部署到生产环境。devServer.proxy解决的是本地开发跨域问题。注意这个配置只在 dev server 里生效上线后完全没用。生产环境的跨域或接口转发得靠 Nginx 或后端配置。2.3 路由模式hash 和 history 不是随便选的Vue Router 有两种主流模式分别对应不同的部署要求。模式URL 示例刷新行为部署要求SEO 友好度hash 模式/#/home请求路径始终是/服务端返回 index.html任意静态托管都能用差history 模式/home浏览器请求/home服务端需要回退到 index.html需要可配置的服务器较好hash 模式的好处是部署极其省心随便找个静态服务器把dist一放就行。缺点是 URL 带#部分场景下分享链接不够美观也不利于搜索引擎理解页面如果做纯前端 SEO 会吃亏。history 模式是正式站点更常用的选择但前提是服务器必须支持回退规则。在 Nginx 里很常见的一段配置是location / { try_files $uri $uri/ /index.html; }它的含义是先尝试找真实文件找不到就把请求交给/index.html。这样 Vue Router 才能在 history 模式下正确接管路由。在代码里设置路由时还要注意base参数const router new VueRouter({ mode: history, base: process.env.BASE_URL, routes })process.env.BASE_URL通常由publicPath相关配置自动注入。如果你手动改了子路径一定要把 base 同步改掉。3. 实操录从 npm run build 到 Nginx 上线3.1 构建前检查清单先说个我踩过很多次的坑直接npm run build构建完就把dist扔上服务器结果白屏。后来养成习惯每次构建前都过一遍检查清单。第一确认依赖锁文件存在。项目里要有package-lock.json或yarn.lock构建时用npm ci或yarn install --frozen-lockfile保证依赖版本一致。第二确认环境变量。如果用了.env.production打开看一眼接口地址是不是生产地址。我见过有人同时开了多个终端环境变量缓存混乱构建出来居然是测试地址。第三确认代码里没有遗留的本地调试片段。比如console.log大量刷屏、debugger、写死的本地 IP。这些不会直接导致部署失败但会影响性能还可能泄露开发信息。第四确认dist不是旧目录。如果你在服务器上解压时直接覆盖旧文件可能残留。比如以前有app.abc.js新版本是app.xyz.js旧文件没删短期没事时间久了容易出缓存问题。准备工作没问题后开始构建npm ci npm run build:prod构建完成后看dist目录dist ├── index.html ├── favicon.ico └── static ├── css │ └── app.5f2d3c.css └── js ├── app.8d3f2a.js └── chunk-vendors.4c1e0b.jsindex.html是入口static/js下通常是业务代码和公共依赖的产物文件名都带 hash这就是缓存控制的依据。3.2 在 Nginx 里配一个标准的 Vue 站点如果你的项目部署在服务器上Nginx 应该是最常见的承载方式。下面是一个可以直接拿来改的配置假设dist已经上传到/var/www/vue-app/distserver { listen 80; server_name your-domain.com; root /var/www/vue-app/dist; index index.html; # history 模式的关键配置 location / { try_files $uri $uri/ /index.html; } # 带 hash 的静态资源可以放心缓存 location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ { expires 30d; add_header Cache-Control public, immutable; } # 禁止缓存 index.html保证发布后用户能拿到新入口 location /index.html { add_header Cache-Control no-cache, no-store, must-revalidate; } # 接口代理 location /api/ { proxy_pass http://backend-server:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } gzip on; gzip_types text/plain text/css application/javascript application/json image/svgxml; }这里最核心的是try_files。比如用户访问/about服务器先找/var/www/vue-app/dist/about找不到再找目录/about/还是找不到就回退到/index.html。整个过程对用户无感Vue Router 拿到 URL 后在内存中匹配路由正常渲染页面。如果没有这行配置history 模式部署后用户一刷新非首页路由立刻 404。很多新手踩的就是这个坑。3.3 接口代理不只是运维的事前端代码里的接口地址如果写的是相对路径/api/login那么生产环境浏览器会请求当前域名下的/api/login这个请求需要由 Nginx 转发到真实后端。上面的配置里location /api/的作用就是反向代理。前端请求/api/xxxNginx 把它转发给http://backend-server:8080/api/xxx同时把原始域名信息带给后端。这样做的好处很直接浏览器最终请求的是同一个域名不存在跨域问题不需要后端额外设置 CORSCookie 也更容易处理。如果你把后端接口写成了全地址https://api.example.com那 Nginx 代理就用不上了跨域问题会让联调变得很麻烦。所以我的习惯是项目里接口地址统一用前缀/api具体指向哪个后端交给环境变量和 Nginx 去控制。再说一下子路径部署。假设最终入口是https://example.com/admin/那么 Vue 项目需要// vue.config.js publicPath: /admin/// router base: /admin/Nginx 配置root /var/www/html; location /admin/ { try_files $uri $uri/ /admin/index.html; }并且把dist目录里的内容上传到/var/www/html/admin。这一套组合拳打下来子路径部署才不会出乱子。3.4 静态资源缓存策略让用户看到新版本前端部署最容易被忽视的问题就是用户浏览器缓存了旧资源。旧代码明明已经重新部署了用户看到的还是老页面有时候要强制刷新才能好体验非常差。解决办法要结合打包文件的 hash 机制。Vue 构建出来的 JS、CSS 文件名都带内容 hash比如app.8d3f2a.js。只要文件内容变了hash 就会变。这种文件适合告诉浏览器“长期缓存”因为它不可能变得模糊变了就不是同一个文件名了。而index.html是入口它里面的 JS、CSS 路径会随版本变化所以不能被浏览器缓存。上面配置里我单独给index.html加了Cache-Control: no-cache就是强制浏览器每次访问都向服务器确认一下这个文件有没有更新。静态资源用 30 天或一年的缓存时间入口 HTML 不缓存这是目前比较经典的前端缓存策略也适用于大部分 Vue 项目。4. 常见问题排查手册4.1 部署后刷新页面 404这是 history 模式最经典的问题。现象很典型首页能打开进入/about后刷新变成 404。排查思路依次是确认浏览器地址栏是不是/about没有#。打开 Nginx 配置看有没有try_files $uri $uri/ /index.html;。执行nginx -t检查配置语法然后nginx -s reload重新加载。如果服务器用了其他托管平台比如对象存储确认是否支持路由回退。不支持就老老实实换 hash 模式。如果try_files已经配置但还是 404可能是root路径不对服务器没找到dist文件夹下的index.html。这时候看/var/log/nginx/error.log信息会直接告诉你实际查找的路径。4.2 静态资源 JS/CSS 404部署后页面打开控制台一堆Failed to load resource: 404。这类问题大多数出在publicPath。举个例子你部署在/admin/但publicPath还是/那么index.html里引用的 JS 路径是/static/js/app.xxx.js。浏览器请求https://example.com/static/...而你的文件实际在https://example.com/admin/static/...自然 404。排查时可以打开index.html源码看script标签的src路径。然后问自己一个问题这个路径能直接在浏览器里访问到对应的 JS 文件吗能到就不是路径问题不能到先改publicPath。4.3 页面白屏控制台报错白屏比 404 更隐蔽因为页面真的返回了index.html但后续 JS 执行失败。常见原因有几种路由base没对导致mounted前就报错。接口请求失败后代码没有做错误兜底整个页面渲染流程中断。构建时process.env.VUE_APP_*变量为 undefined前端代码运行时抛出异常。我一般一分钟内先看 Network 请求。如果 JS、CSS 都返回 200再看 Console 报错。如果报错里有 “Cannot read properties of undefined”基本可以往环境变量方向查。如果报错出现在webpackJsonp相关代码则考虑是不是文件被截断重新上传一次dist。4.4 页面能打开但接口请求失败这种情况往往比白屏更好定位也更容易被当成“后端问题”甩锅。实际上前端环境变量错误也很常见。先打开 Network看接口请求的真实 URL。如果 URL 指向了localhost或某个不存在的测试域名基本就是.env.production里的VUE_APP_API_BASE_URL配错了重新构建发布。如果 URL 正确但状态码 404可能是 Nginx 代理路径问题。比如后端接口实际是/api/login你 Nginx 写的是proxy_pass http://backend:8080/;没有保留/api前缀后端就会接到/login导致 404。如果状态码 405可能是跨域预检请求没处理好需要后端支持 OPTIONS或者前端统一用代理避免跨域。我把常见的部署问题整理成一个速查表问题现象大概率原因快速处理方式刷新子路由 404history 模式缺少 try_filesNginx 配置回退JS/CSS 404publicPath 未匹配部署路径修改 publicPath白屏无报错资源加载失败但被静默吞掉看 Network 请求接口指向本地.env.production 未生效检查构建模式更新后还是老页面index.html 被缓存设置 no-cache 缓存头接口跨域生产环境没走代理Nginx 配置 /api 代理4.5 缓存导致更新后还是旧页面发布完新版本用户刷新还是老页面这个很多人会直接骂浏览器缓存。排查时先确认自己是不是在发布时覆盖了旧文件但没删除。如果static目录里旧 hash 文件还在不影响新页面引用但如果index.html还是旧的就会请求旧资源。所以发布时建议整目录替换而不是只上传新文件。然后看响应头。访问index.html如果响应头里有Cache-Control: no-cache说明服务器没拦住缓存如果没加就要按前面的配置加上。还有一个技巧发布后用“时间戳方式”快速验证。比如访问你的域名/index.html?v123如果内容变了说明资源本身没问题单纯是缓存策略没到位。5. 部署经验和最后的实用建议5.1 上线前先把发布流程走通畅很多人发布时习惯用手工上传直接在服务器上拖动文件。这在简单项目里还行但项目一旦复杂建议至少做到“构建产物独立、发布可回滚”。我现在的默认做法是本地或 CI 执行npm run build:prod把dist目录打成带时间戳的压缩包上传到服务器后解压到新目录再用 Nginx 的root或软链切过去。这样做的好处是新版出问题可以一秒切回旧版不会影响线上用户。比如服务器上可以这样组织/var/www/vue-app/releases/2025-06-01-10-30/dist /var/www/vue-app/current - /var/www/vue-app/releases/2025-06-01-10-30Nginx 里root /var/www/vue-app/current;发布时只需要重建软链并 reload。这个习惯帮我避免过好几次“发布后紧急回滚”的尴尬。5.2 检查日志比硬猜有效遇到部署问题最忌讳的是在代码里反复打 log 猜测。前端项目部署出问题先在浏览器里看 Network 和 Console很多时候问题已经浮在表面。如果 Network 显示请求失败再上服务器看 Nginx 日志tail -f /var/log/nginx/access.log tail -f /var/log/nginx/error.log日志会告诉你真实路径、状态码、请求时间。比如 404 时日志里会写出实际查找的文件路径对照一下就能定位是 root 配错还是 publicPath 配错。5.3 我每次部署前都会确认的一件事这几年踩过不少坑也慢慢总结出自己的一套固定步骤。每次部署前我都会做一件很小但对稳定性很有帮助的事在本地把构建产物跑起来预览一遍。Vue 构建出来的dist不能直接双击打开index.html因为默认是绝对路径。我经常用一条命令快速起一个本地静态服务器npx serve -s dist这样能提前发现资源路径、路由回退、文件缺失问题。虽然本地预览和服务器环境不完全一致但至少能挡掉一半的低级错误。最后再说句实在话本地能跑只是开始能稳定地在线上运行才算真正的完成。打包和部署看着枯燥但它一旦出问题比业务逻辑 bug 更难排查影响范围也更大。希望这篇实战记录能帮你把路径、回退、缓存、代理这几件事一次理清楚少走点弯路。
返回列表