news 2026/10/2 4:57:10

Vue 项目打包部署与 Nginx 上线实战:路由、缓存与回滚

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue 项目打包部署与 Nginx 上线实战:路由、缓存与回滚

1. Vue 项目打包部署的整体链路拆解

很多人第一次把 Vue 项目往服务器上搬的时候,都会经历这么一个阶段:本地npm run dev跑得好好的,页面丝滑,热更新秒响应,结果npm run build出来的东西丢到服务器上,打开浏览器一片白,控制台红字飘一片。这中间的落差不是因为你的代码写错了,而是因为开发环境和生产环境根本就是两套完全不同的运行模型——一个是 Node 起的开发服务器在内存里编译、按需返回模块,另一个是一堆静态文件被扔进 Nginx 的目录里,等着浏览器自己去请求。搞清楚这条链路上每一步到底发生了什么,比背十条 Nginx 配置命令有用得多。

1.1 从源码到线上,一次部署到底走了哪几步

把整条链路拆开看,其实只有四段:本地构建、产物校验、文件传输、服务器接管。

本地构建这一步,Vite 和 webpack 做的事情本质上是一样的——把你的.vue单文件组件、.ts、.scss、静态资源全部编译、转换、压缩、打成一堆浏览器能直接跑的.js、.css、图片和字体。区别在实现方式和速度上:Vite 生产构建走的是 Rollup,冷启动构建通常比 webpack 快一截;webpack 生态老、插件多,配置可调空间大,但构建一个中型项目动辄一两分钟起步。

产物校验这一步最容易被跳过,但它恰恰是省时间的关键。dist目录出来之后,别急着传服务器,先在本地起一个静态服务器跑一遍,比如npx serve dist或者在 IDE 里用内置的静态预览。这一步能提前暴露 80% 的路径问题、路由问题、静态资源 404 问题。传到服务器上再发现,你还得来回排错、重新上传,一来一回十几分钟就没了。

文件传输和服务器接管是最后两步。传输方式有scp、rsync、SFTP 工具、CI/CD 流水线自动发布,各有适用场景。服务器接管则是 Nginx、Caddy、Node 服务或者对象存储,决定了你的页面怎么被访问、怎么处理路由回退、怎么设置缓存。

我自己的习惯是:本地构建 + 本地静态预览 + 打一个 tar 包 +scp上传 + 解压覆盖 + reload Nginx。整个流程五条命令,两分钟内搞定,比任何可视化工具都快,而且可复现、可脚本化。

1.2 静态托管还是后端服务,先想清楚你的项目属于哪一类

不是所有 Vue 项目都适合直接丢静态服务器。判断标准很简单:这个项目在运行期还需要 Node 吗?

绝大多数后台管理系统、官网、活动页、H5 都是纯静态的——构建完就是一堆 HTML/CSS/JS,浏览器拿到就能跑,接口全靠fetch/axios打后端 API。这类项目用 Nginx 托管是性价比最高的方案,一台最低配的云服务器能扛住相当可观的并发,因为静态文件几乎不吃 CPU,瓶颈只在带宽和磁盘 IO。

另一类是需要在服务器端渲染或者做接口聚合的,比如 Nuxt、SSR 项目,或者你想在同一个服务里顺手把/api转发到后端。这种就得跑一个 Node 进程,用pm2或systemd守护,前面再挂一层 Nginx 做反向代理。好处是灵活,代价是内存占用和运维复杂度都上去了。

还有一种介于两者之间的:项目本身是静态的,但团队希望部署过程标准化、环境一致,那就用 Docker。把构建产物和 Nginx 一起打进镜像,服务器上只跑容器,谁都不用关心服务器上装了什么版本的 Nginx。这套方案在多环境、多项目共存的时候特别香,但单项目小团队用起来就有点重。

部署方式适合场景优点代价
Nginx 静态托管后台系统、官网、H5资源占用低、配置简单、并发强需要自己维护服务器
Node 进程托管SSR、接口聚合灵活、可处理动态逻辑内存占用高、需要进程守护
容器化部署多环境、多项目环境一致、发布可回滚学习成本、镜像体积
静态托管平台个人项目、演示站零运维、自动 HTTPS自定义能力受限

选型的核心不是哪个更先进,而是哪个跟你的团队能力、项目生命周期、预算匹配。给一个只有三个页面的官网配一套 K8s,那不叫专业,叫折腾。

1.3 环境变量与多环境打包,别等到上线才发现接口地址写死了

我见过太多项目把接口地址硬编码在axios的baseURL里,本地是http://localhost:3000,上线之后忘了改,页面能打开但所有请求全挂。这类问题完全可以在工程层面规避。

Vue CLI 和 Vite 都支持基于.env文件的环境区分。Vite 用import.meta.env.VITE_XXX,Vue CLI 用process.env.VUE_APP_XXX,前缀是强制的,防止你把服务器上的敏感变量无意中打进前端产物。典型的文件布局是这样:

.env # 所有环境共享 .env.development # npm run dev 时加载 .env.staging # 预发布 .env.production # npm run build 时加载
# .env.production VITE_API_BASE=https://api.example.com VITE_APP_TITLE=运营后台

代码里统一走一层封装:

// src/utils/request.js import axios from 'axios' const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE, timeout: 15000 }) service.interceptors.response.use( res => res.data, err => Promise.reject(err) ) export default service

注意:所有以VITE_或VUE_APP_开头的变量都会被打进最终的 JS 文件里,任何人打开浏览器都能看到。Token 密钥、数据库密码这类东西绝对不能放进去,前端没有秘密可言。

多环境打包命令也可以分开写,package.json里加一行"build:staging": "vite build --mode staging",部署时按环境选命令,避免手改配置出错。这一步做好了,后面切环境、加环境都只是加一个文件的事。

2. 打包前的关键配置与参数取舍

部署出问题,十次里有六次根因在打包配置上。配置不对,服务器上的 Nginx 写得再漂亮也救不回来。这一节把最容易踩的几个配置点挨个说透。

2.1 Vite 与 webpack 两套构建工具的配置要点

先看 Vite。它的配置文件是vite.config.js(或.ts),核心就几个字段:

import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' export default defineConfig({ base: '/', build: { outDir: 'dist', assetsDir: 'assets', sourcemap: false, chunkSizeWarningLimit: 1500, rollupOptions: { output: { manualChunks: { 'vue-vendor': ['vue', 'vue-router', 'pinia'], 'ui-lib': ['element-plus'] } } } }, resolve: { alias: { '@': path.resolve(__dirname, 'src') } } })

sourcemap生产环境建议关掉,产物能小一大截,同时避免源码暴露。manualChunks是拆包的关键,把体积大且长期不动的依赖单独拆出来,浏览器可以长期缓存它们,改业务代码时只更新业务 chunk,用户二次访问几乎不用重新下载第三方库。

再看 Vue CLI(webpack)那套:

const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, publicPath: '/', outputDir: 'dist', assetsDir: 'static', productionSourceMap: false, configureWebpack: { performance: { hints: false }, optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', priority: -10 } } } } } })

两者的base和publicPath是同一个东西的不同叫法,作用都是告诉构建工具:我的静态资源最终会被部署在哪个路径下。这个值填错,就是白屏的头号元凶。

2.2 publicPath 与 base 到底该怎么填

这个参数的本质是一道除法:浏览器请求资源的完整地址 = 部署路径 + 构建时写死的资源前缀。

假设你的index.html里被写入了<script src="/assets/index-abc123.js">,而这个文件实际放在https://example.com/assets/index-abc123.js,那就正常。但如果你的站点挂在子目录下,比如https://example.com/admin/,而文件实际在https://example.com/admin/assets/...,浏览器却跑去请求https://example.com/assets/...,结果必然是 404,页面白屏。

所以规则很清晰:

  • 部署在域名根目录(https://example.com/),base填'/'
  • 部署在子目录(https://example.com/admin/),base填'/admin/'
  • 用相对路径部署(产物放在哪都能跑),base填'./'

前两种是绝对路径,第三种是相对路径。相对路径看着方便,但它有个坑:如果页面用的是 history 路由,访问https://example.com/admin/user/list时,浏览器会以当前路径为基准去解析相对资源,请求变成https://example.com/admin/user/assets/...,直接 404。所以 history 模式必须用绝对路径,'./'只适合 hash 模式或者单层页面。

提示:base末尾的斜杠不能省。写'/admin'和写'/admin/'出来的资源路径完全不一样,前者会拼成/adminassets/...,找半天找不到问题。

2.3 路由模式对服务器配置的决定性影响

Vue Router 有两种主流模式:createWebHashHistory和createWebHistory。

hash 模式下 URL 长这样:https://example.com/#/user/list。井号后面的内容浏览器不会发给服务器,服务器永远只看到https://example.com/,返回index.html,前端自己解析路由。这种模式下服务器随便配,甚至不需要任何特殊处理。

history 模式下去掉了井号:https://example.com/user/list。这时候用户刷新页面,浏览器会真的向服务器请求/user/list这个路径。如果服务器上没有这个真实文件或目录,Nginx 默认返回 404。解决办法就是在 Nginx 里加一条回退规则,把所有找不到的路径都交回index.html,让前端路由接管:

location / { try_files $uri $uri/ /index.html; }

这行配置的意思是:先找有没有同名文件,再找有没有同名目录,都没有就把index.html返回去。这是 history 模式的标配,漏了它,用户一刷新就 404。

两种模式没有绝对优劣。hash 兼容性好、服务器零配置,但 URL 里带井号,部分场景(比如微信分享、SEO)不太好看。history 美观,但需要服务器配合,且构建时要留意base路径。新项目我一般直接用 history,配好 Nginx 就一劳永逸了。

2.4 构建产物体积优化与拆包策略

产物越大,首屏越慢,尤其是网络条件一般的用户。优化前先看清体积分布,Vite 可以用rollup-plugin-visualizer,webpack 用webpack-bundle-analyzer,跑一次就能看到哪个包最肥。

npm i -D rollup-plugin-visualizer
import { visualizer } from 'rollup-plugin-visualizer' export default defineConfig({ plugins: [vue(), visualizer({ open: true, gzipSize: true })] })

构建完会自动弹出页面,方块越大代表体积越大。常见的几个「重灾区」:

  • UI 库整包引入。Element Plus、Ant Design Vue 这种,如果没用按需引入,随便就是几百 KB。
  • 图表库。ECharts 全量引入体积很可观,改成按需引入能砍掉一半以上。
  • 工具库整包。lodash引lodash-es配合 tree-shaking,或者直接按方法引入。
  • 大图片直接 import。几十 MB 的图片打进产物,构建慢、加载慢,应该放 CDN 或对象存储。

按需引入 Element Plus 的配置大致是这样:

import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ] })

这套插件会在构建时自动引入你用到的组件和 API,不需要手写 import,体积也跟着瘦下来。实测一个中型后台从 2.3MB 降到 800KB 左右是常见的。

拆包的另一个收益是缓存利用率。把vue、vue-router、pinia拆成一个 chunk,把 UI 库拆成另一个,业务代码拆成第三个。业务代码一天发三次,第三方库半年不动一次,用户只需要重复下载那几百 KB 的业务 chunk,体验完全不一样。

3. 服务器环境准备与 Nginx 部署实操

配置对了,接下来就是把产物安全送上去、让服务器稳定地吐出来。这一节按顺序走一遍完整流程,命令都是可以直接抄的。

3.1 服务器基础环境与目录规划

先假设你拿到了一台全新的 Linux 服务器,系统是 Ubuntu 22.04 或 CentOS 7/8 那一类。第一件事不是装软件,是规划目录。我一般用这套:

/www/ ├── apps/ │ └── my-vue-app/ # 当前版本 │ ├── index.html │ ── assets/ ├── releases/ │ └── my-vue-app/ │ ├── 20250101-120000/ # 历史版本 │ ── 20250110-153000/ └── logs/ └── my-vue-app/

apps放当前生效的版本,releases存历史包用于回滚,logs放日志。这套结构看着多余,等你某次上线出问题需要三十秒内退回上一版的时候就明白它的价值了。

创建目录和用户:

sudo mkdir -p /www/apps /www/releases /www/logs sudo chown -R $USER:$USER /www

服务器上通常还需要这些基础工具:

# Ubuntu / Debian sudo apt update && sudo apt install -y nginx unzip curl # CentOS / RHEL sudo yum install -y nginx unzip curl

装完 Nginx 后确认服务状态:

sudo systemctl enable nginx sudo systemctl start nginx sudo systemctl status nginx

浏览器访问服务器 IP,看到 Nginx 默认欢迎页,说明基础环境通了。这一步不通,后面全都白搭,先排查防火墙和安全组——很多云服务器的 80 端口在控制台的安全组里默认是关的,本地curl通、外部访问不通,八成就是这个原因。

3.2 Nginx 站点配置逐行解读

配置文件的放置位置各发行版不同,Ubuntu 一般在/etc/nginx/sites-available/和/etc/nginx/sites-enabled/,CentOS 在/etc/nginx/conf.d/。我习惯统一放conf.d,加个my-vue-app.conf:

server { listen 80; server_name app.example.com; root /www/apps/my-vue-app; index index.html; access_log /www/logs/my-vue-app/access.log; error_log /www/logs/my-vue-app/error.log warn; location / { try_files $uri $uri/ /index.html; } location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot)$ { expires 30d; add_header Cache-Control "public, max-age=2592000, immutable"; access_log off; } location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; } gzip on; gzip_min_length 1k; gzip_comp_level 6; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_vary on; gzip_disable "msie6"; }

逐块解释一下,因为这几行每一行都有讲究。

root指向产物目录,index指定默认首页。

try_files $uri $uri/ /index.html;是 history 路由的生命线,前面说过,不重复。

静态资源那条location用正则匹配扩展名,给带 hash 的文件名设了 30 天强缓存。Vite 和 webpack 产出的文件名里自带内容哈希(比如index-a1b2c3d4.js),内容一变文件名就变,所以可以放心大胆地设长缓存。immutable告诉浏览器这个资源在有效期内绝对不会变,连条件请求都省了。

index.html单独拎出来设成不缓存。原因很直白:HTML 是入口,它里面引用了哪几个带哈希的 JS 文件,是每次发版都可能变的。如果 HTML 被缓存住,用户拿到的还是旧的 HTML,里面引用的旧 JS 可能已经被删了,页面直接白屏。这种「HTML 不缓存 + 静态资源长缓存」的组合,是目前前端部署缓存策略的最优解。

gzip 那几行是压缩开关。gzip_comp_level 6是压缩比和 CPU 消耗之间的平衡点,调到 9 收益很小但 CPU 会明显上去,调到 1 又压不干净。gzip_min_length 1k避免小文件压缩反而变大。gzip_types里没写text/html,因为 Nginx 默认就会压缩 HTML,写了反而会报重复定义的警告。

3.3 文件上传的几种方式与实操命令

本地构建完成后,产物在dist目录里。上传方式按推荐度排序。

方式一:rsync 增量同步(推荐)

rsync -avz --delete \ ./dist/ \ user@your-server:/www/apps/my-vue-app/

-a保留权限和时间戳,-v输出详情,-z传输压缩,--delete删除目标目录里源目录没有的文件——这个参数很重要,因为带哈希的旧文件如果不清掉,目录会越堆越大。rsync 只传变化的部分,第二次发布通常几秒钟就完事。

方式二:scp 全量上传

tar -czf dist.tar.gz -C dist . scp dist.tar.gz user@your-server:/tmp/ ssh user@your-server "rm -rf /www/apps/my-vue-app/* && tar -xzf /tmp/dist.tar.gz -C /www/apps/my-vue-app/"

打包上传的好处是文件完整性有保障,缺点是全量传输,产物大了就慢。

方式三:本地构建,服务器只负责托管

如果服务器配置够,也可以把源码传上去在服务器上构建。但我不太推荐这种做法——服务器上要装 Node、装依赖、跑构建,内存小的机器构建时容易 OOM,而且每次发版都在生产机上跑npm install,风险不小。

发布完成后 reload 一下 Nginx:

sudo nginx -t && sudo systemctl reload nginx

nginx -t是语法检查,先测再 reload,避免配置写错导致服务起不来。这个习惯一定要养成,尤其是在生产环境上。

3.4 HTTPS 与 gzip 之外的几项优化

HTTPS 现在基本是标配,浏览器对 HTTP 页面会标记「不安全」,接口调用也可能被限制。用 Let's Encrypt 的证书是免费且自动化的:

sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d app.example.com

certbot 会自动改写你的 Nginx 配置,加上 443 监听、证书路径和 HTTP 到 HTTPS 的跳转,并且注册一个定时任务自动续期。证书有效期 90 天,自动续期省心。

HTTPS 之外还有几项值得做的:

HTTP/2。在listen 443 ssl后面加上http2,多路复用能明显改善首屏加载时的并发请求表现。现在 Nginx 新版本写法是listen 443 ssl;加独立的http2 on;,具体看版本。

Brotli 压缩。比 gzip 压得更狠,一般能再省 15% 到 20%。需要额外编译模块,Ubuntu 上有现成的包可以装。

安全响应头。加上X-Content-Type-Options nosniff、X-Frame-Options SAMEORIGIN这类头,能挡掉一部分常见的注入和点击劫持问题。

访问日志按天切割。日志不切割,跑几个月就是一个几 G 的文件,排查问题时光是打开就够呛。用 logrotate 配一下,保留 14 天就够。

3.5 Docker 方式部署,把环境一起打包带走

如果你手上项目多、环境杂,或者团队希望「本地跑通就等于线上跑通」,Docker 值得投入。核心思路是多阶段构建:第一阶段用 Node 镜像构建产物,第二阶段只把产物拷进 Nginx 镜像,最终镜像里没有 Node、没有源码、没有 node_modules,体积能控制在 50MB 以内。

# ---------- 构建阶段 ---------- FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --registry=https://registry.npmmirror.com COPY . . RUN npm run build # ---------- 运行阶段 ---------- FROM nginx:1.25-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;"]

配套的nginx.conf:

server { listen 80; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location ~* \.(js|css|png|jpg|svg|woff2?)$ { expires 30d; add_header Cache-Control "public, max-age=2592000, immutable"; } gzip on; gzip_types text/css application/javascript application/json; }

构建和运行:

docker build -t my-vue-app:1.0.0 . docker run -d --name my-vue-app -p 8080:80 --restart=always my-vue-app:1.0.0

--restart=always保证服务器重启后容器自动拉起,不用手动干预。要更新版本就构建一个新 tag,停旧容器起新容器,回滚就是把旧 tag 再跑一遍,比文件覆盖式发布干净得多。

注意:.dockerignore一定要写,把node_modules、dist、.git、.env.local排除掉,否则构建上下文能到几百 MB,构建慢不说,还可能把本地敏感配置打进镜像。

4. 上线自检、缓存策略与回滚方案

部署完成不等于任务结束。真正让人睡不好觉的是「上线之后才发现有问题」。

4.1 上线后必须走一遍的自检清单

发版之后,我会按这个顺序快速过一遍,全程不超过三分钟:

  1. 打开首页,看有没有白屏。白屏第一件事是开 DevTools 的 Network,看 JS 是不是 404。
  2. 直接刷新当前路由,比如https://app.example.com/user/list,看会不会 404。会的话就是try_files没配。
  3. 强刷一次(Ctrl+Shift+R),确认缓存策略生效,新版本能拿到。
  4. 登录一遍,走一遍核心业务流,确认接口地址正确、跨域没问题。
  5. 看响应头,curl -I https://app.example.com/index.html,确认Cache-Control是no-cache,静态资源的Cache-Control带了max-age。
  6. 看 Nginx 错误日志,tail -f /www/logs/my-vue-app/error.log,有报错当场处理。

这六步能覆盖九成以上的上线事故。养成习惯之后,每次发布心里都有底。

4.2 缓存策略踩过的坑

缓存这事,配对了是性能加速器,配错了是故障放大器。我踩过的两次坑值得说一下。

第一次是index.html被 CDN 缓存了。用户访问到的还是旧 HTML,里面引用的 JS 文件名已经不存在,页面白屏,而且因为 CDN 缓存没过期,怎么刷新都没用,只能等缓存超时,用户体验极差。教训是:HTML 永远不缓存,CDN 上也要针对 HTML 单独配置不缓存规则。

第二次是静态资源没设immutable,浏览器每次都用 If-None-Match 去问服务器「这个文件变了没」,服务器返回 304。虽然 304 响应很小,但请求数一多,首屏加载还是能拖慢几百毫秒。加上immutable之后,浏览器在缓存期内根本不发请求,直接读本地。

还有一点是 CDN 和源站的缓存要一致。源站设了 30 天,CDN 设了 1 天,实际生效的是 CDN 那边的 1 天,回源频率比预期高很多。两边配置得对齐,或者干脆让 CDN 完全遵循源站的Cache-Control。

4.3 回滚方案,出问题时的最后一道保险

没有回滚方案的部署流程是不完整的。文件覆盖式发布的回滚很简单:发布前先把当前版本备份到releases目录。

#!/bin/bash set -e APP=my-vue-app DEPLOY_DIR=/www/apps/$APP RELEASE_DIR=/www/releases/$APP/$(date +%Y%m%d-%H%M%S) # 1. 备份当前版本 if [ -d "$DEPLOY_DIR" ] && [ "$(ls -A $DEPLOY_DIR)" ]; then mkdir -p "$RELEASE_DIR" cp -r "$DEPLOY_DIR/." "$RELEASE_DIR/" echo "已备份到 $RELEASE_DIR" fi # 2. 同步新版本 rsync -avz --delete ./dist/ "$DEPLOY_DIR/" # 3. 校验并 reload sudo nginx -t && sudo systemctl reload nginx echo "发布完成:$(date)"

回滚就是反向操作:

cp -r /www/releases/my-vue-app/20250101-120000/. /www/apps/my-vue-app/ sudo systemctl reload nginx

Docker 方案更简单,docker run直接指定旧 tag 就行,容器本身就是不可变的版本快照。

提示:releases目录别无限堆积,加一个清理逻辑保留最近 10 个版本就够,再多就是占磁盘。

5. 常见问题与排查技巧实录

这一节是我这些年攒下来的问题清单,按出现频率排序,遇到问题可以直接对照着查。

5.1 白屏、404 与样式异常的排查路径

白屏,控制台报 JS 404。九成是base/publicPath配错了。打开dist/index.html看一眼,里面引用的路径和你实际部署的路径对不上,改配置重新构建即可。

白屏,控制台报Unexpected token '<'。这个报错的意思是:浏览器请求 JS 文件,服务器返回的却是 HTML。通常是因为try_files把不存在的 JS 请求也回退到index.html了。检查一下静态文件是否真的上传完整,或者 Nginx 的静态资源location优先级被覆盖了。

刷新页面 404。history 模式没有配try_files。加上就好。

样式全乱,布局异常。这个情况在打包后比开发环境更容易出现,原因通常有三个。一是 CSS 被打包工具重新排序了,某些依赖加载顺序的样式失效,解决办法是把关键样式显式import到入口文件里,别依赖隐式顺序。二是第三方组件的样式没被按需引入插件识别出来,比如 Element Plus 的一些弹层、抽屉组件样式丢失,检查Components插件的resolvers配置是否完整覆盖。三是用了scoped但样式选择器写得太深,打包压缩后被重写,这种情况用:deep()显式穿透。

图片、字体 404。检查是不是用了绝对路径引用public目录之外的资源,或者路径里带了构建时的临时 hash。图片建议放public目录或用new URL('...', import.meta.url)的方式引用。

页面能打开,但接口全部跨域报错。开发环境有 Vite/webpack 的 proxy 挡着,生产环境没有了。两种解法:后端在响应头里加 CORS 允许你的域名,或者 Nginx 加一层反向代理:

location /api/ { proxy_pass https://api.example.com/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }

代理之后前端请求/api/xxx就是同源的,跨域问题自然消失。注意proxy_pass末尾那个斜杠——带了斜杠/api/user会被代理成/user,不带就是/api/user,少一个斜杠路径就多一段,这个坑很多人踩过。

5.2 常见问题速查表

现象最可能的原因快速定位方法解决方式
打开就白屏base / publicPath 路径不对看 index.html 里的资源路径改成正确的部署路径
刷新子路由 404未配置 history 回退直接访问子路由看状态码加try_files $uri $uri/ /index.html
JS 返回 HTML静态文件缺失或被回退Network 看响应体内容检查上传完整性
样式错乱CSS 顺序或按需引入问题对比开发与生产 DOM 的 class显式引入样式、用:deep()
接口跨域生产无代理看控制台 CORS 报错后端配 CORS 或 Nginx 反代
更新后还是旧页面HTML 被缓存看响应头 Cache-ControlHTML 设 no-cache
静态资源每次重新下载未设长缓存看响应头是否有 max-age加 expires 和 immutable
首次加载慢产物太大用 visualizer 分析体积拆包、按需引入、CDN
部署后 502后端进程挂了或端口不通看 Nginx error.log检查上游服务状态
部分用户访问异常CDN 节点缓存不一致换网络环境复现刷新 CDN 缓存、对齐策略

5.3 几个能省下大量时间的实操心得

用脚本代替手工操作。发布流程一旦写成 shell 脚本,就再也不会有「忘了执行某一步」这种事。哪怕只有五条命令,也值得写成脚本。

构建日志要留痕。在index.html里注入一个版本标识,比如<meta name="build-version" content="1.2.3-20250110">,出问题时打开控制台就知道用户当前跑的是哪一版,比问用户「你刷新了吗」高效一百倍。

先在预发布环境试一遍。预发布环境用和线上完全一致的 Nginx 配置、一致的域名结构,只是指向不同的目录。所有配置改动先在预发布验证,确认没问题再同步到线上。

别在生产机上直接改文件。所有变更都走「本地改动 - 提交 - 发布」的流程,生产机上手工改的每一行配置,都会成为未来某次事故的伏笔——因为没人知道它是什么时候加的、为什么加。

备份永远在下一次操作之前。这是我做了几年运维之后最深刻的体会。删目录、覆盖文件、reload 配置之前,先备份、先nginx -t,养成条件反射,能避免绝大多数不可挽回的失误。

给静态资源上 CDN。如果用户分布在全国各地,把assets目录的内容推一份到 CDN,源站只留index.html,首屏加载能快上不少。做法是在构建时把base指向 CDN 域名,产物上传到 CDN,HTML 里引用的就是 CDN 地址了。注意发版时 CDN 和源站的更新要同步,别出现 HTML 引用了还没上传到 CDN 的 JS 这种情况。

整套流程跑通之后,你会发现 Vue 项目的部署其实没那么玄乎——构建配置管好路径和拆包,Nginx 管好路由回退和缓存,脚本管好发布和回滚,三件事各司其职。真正常见的故障,基本都集中在路径和缓存这两块,把这两块吃透,剩下的就是熟练度问题了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 4:57:10

飞书多维表格实战:2分钟搭建自动催办与机器人推送流程

上周三下午&#xff0c;同事在群里甩了一句"谁能帮忙做个能自动催办的项目跟进表"&#xff0c;我回了句"给我两分钟"。两分钟后&#xff0c;一个带状态看板、逾期自动提醒、数据还能被机器人定时推到群里的表就躺在群里了。用的不是 Excel&#xff0c;也不…

作者头像 李华
网站建设 2026/10/2 4:56:44

主页劫持反复改不回?流氓软件手工清除与注册表实战

主页劫持、流氓软件这两个词&#xff0c;只要自己装过系统、帮朋友修过电脑的人都不会陌生。上周邻居抱来一台老笔记本&#xff0c;说 Chrome 一打开就跳到某个陌生导航站&#xff0c;自己在设置里改回来&#xff0c;过两分钟又跳回去&#xff0c;装了两款杀毒软件全盘扫了一遍…

作者头像 李华
网站建设 2026/10/2 4:55:40

DX12实战:从三角形到PBR材质的完整渲染流程与踩坑记录

如果你已经把DX12的窗口、管线和三角形跑起来了&#xff0c;恭喜&#xff0c;下一道坎就是给场景加材质。我最近在“学一下DX12&#xff08;二&#xff09;加入pbr”这个节点上折腾了很久&#xff0c;今天把踩坑过程整理出来。这里的pbr说的是Physically Based Rendering&#…

作者头像 李华
网站建设 2026/10/2 4:55:04

Python餐饮推荐系统:三层架构+SQLite+PyQt5落地实践

简介&#xff1a;本资源是一份面向Python开发者与计算机专业学生的全栈型推荐系统实战项目&#xff0c;聚焦个性化餐饮场所推荐场景&#xff0c;解决用户决策效率低、健康饮食难适配、冷启动与数据稀疏等实际问题。资源以1个104KB的Word文档&#xff08;.docx&#xff09;形式交…

作者头像 李华
网站建设 2026/10/2 4:54:19

智能体安全工程化:六层防御、访问控制与评测体系实战

你可能已经发现了&#xff0c;过去半年里整个行业对 AI 安全的态度正在发生一个微妙的变化&#xff1a;两三年前大家讨论的是“如何让模型不胡说八道”&#xff0c;现在讨论的是“如何让智能体在业务系统里不越权、不泄密、不把钱打错账户”。模型幻觉当然还是问题&#xff0c;…

作者头像 李华
网站建设 2026/10/2 4:53:59

Jev TypeSafe AI工具链:从编译时类型校验到本地部署实战

1. 从热搜词里挖出的真实需求&#xff1a;Jev 到底是个什么东西最近一段时间&#xff0c;不管是在技术群还是各种开发者社区&#xff0c;总能看到有人在问“Jev 是什么”“Jev 模型怎么申请”“Jev 本地部署难不难”。我一开始也以为又是哪个厂商换了个马甲做营销&#xff0c;直…

作者头像 李华