直接动手写。
先说一个很多人都踩过的场景:本地npm run dev跑得飞起,一打包丢到服务器的apache目录下,打开页面白屏、接口404、刷新404,甚至css直接加载不出来。其实问题不在你的代码写得怎么样,而是“开发环境的思路”没有切到“生产环境的思路”。Vue项目打包部署这件事,本质上就三件事:把代码构建成浏览器能直接执行的静态文件、把Apache配成能正确托管这些文件的服务、再把路由和接口的“跳转逻辑”跟服务器对齐。这篇文章我按自己的实际部署流程走一遍,把你可能遇到的各种坑提前踩平。适合刚接触Vue部署的同学,也适合部署过后台管理系统但被history路由折腾过的人。
1. 部署前必须理解的打包逻辑
1.1 Vite构建到底干了什么
Vue项目默认用Vite做构建,开发模式是“按需编译+热更新”,浏览器访问的其实是源码经过实时转换后的内容。但生产环境完全不同,你需要执行打包命令,Vite会把.vue文件、JavaScript、CSS、图片、字体等资源做压缩、合并、混淆,然后输出到一个纯静态目录(默认是dist)。这个目录里的产物只有一个目的:扔到任意一个静态服务器上,通过HTTP直接访问。
理解这一点很重要——它决定了你很多配置的思维方式。比如你在本地写/api/user这种接口路径,开发环境会走Vite的proxy代理转发到后端;但打包之后,proxy配置就不生效了,因为编译产物里没有“Vite服务器”这个概念,只有Apache或Nginx这类真实服务器。所以部署前的第一件事,就是搞清楚你的“静态资源路径”和“接口请求路径”在服务器环境下应该长什么样。
1.2 设定正确的publicPath(base配置)
Vite打包默认的资源引用路径是/,也就是绝对路径。如果你的站点部署在域名根目录,比如https://example.com/,那没问题。但现实中很多项目是部署在子路径下的,比如https://example.com/admin/,或者你的Apache虚拟主机配置了类似于Alias /app "D:/www/app"这样的映射,这个时候如果你还是用默认的/作为基准路径,所有js和css文件就会去请求https://example.com/assets/xxx.js,而服务器上根本不存在这个路径,最终结果就是白屏。
解决办法是在vite.config.js里设置base:
// vite.config.js export default defineConfig({ base: './', // 相对路径,适配任意子目录部署 // 或者写具体的子路径 // base: '/admin/' })我自己的习惯是:如果只有一个独立站点,直接用'/',清晰干净;如果是要部署到子目录或者以后可能迁移,就用'./'。相对路径的好处是,无论你把它丢到/app还是/test下面,资源引用都能自动适配,不会出路径问题。但注意,如果你用了createWebHistory这种HTML5 History路由模式,base配置只影响静态资源的引用,不影响路由地址,两者要分开处理。
1.3 生产环境接口地址单独用环境变量管理
打包部署最忌讳的就是把接口地址写死在代码里。尤其是前端项目对接的后端接口,开发环境可能是http://localhost:8080,测试环境是http://test-api.example.com,生产环境又是https://api.example.com。如果你在代码里写死任意一个,换环境就要重新改代码重新打包。
我习惯在项目根目录新增一个.env.production文件:
# .env.production VITE_API_BASE_URL=https://api.example.com然后在代码里统一封装请求基础路径,比如用axios时:
// src/utils/request.js import axios from 'axios' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 })这样打包时就把VITE_API_BASE_URL替换成对应环境地址。要注意的是,import.meta.env暴露给前端代码的变量必须以VITE_开头,否则拿不到。如果你发现打包后接口请求还是localhost,先看看命名是不是没遵守这个规则。
2. Apache服务器核心配置拆解
2.1 确认Apache的模块加载情况
Apache能托管Vue项目,依赖几个关键模块:mod_dir(目录访问)、mod_rewrite(URL重写)、mod_mime(文件类型识别)。大部分Apache发行版默认都装了,但Windows下的Apache或者某些精简安装包可能没启用或没编译。
在httpd.conf里检查这几行是否被注释掉:
LoadModule rewrite_module modules/mod_rewrite.so LoadModule mime_module modules/mod_mime.so LoadModule dir_module modules/mod_dir.so如果找不到这些行,说明你的Apache可能没有编译mod_rewrite,需要重装或换发行版。如果你用的是宝塔面板这类集成环境,一般可以在“软件管理 → Apache → 模块管理”里看到启用的模块列表。
我部署时最喜欢直接写.htaccess而不改主配置,因为虚拟主机的配置改动容易影响其他站点,而.htaccess只在当前目录生效,出了问题只影响当前应用,回滚也简单。
2.2 httpd.conf vs .htaccess:优先级和适用场景
Apache的配置规则有个顺序:主配置(httpd.conf或conf.d下的文件)优先,.htaccess是“分布式配置文件”,在请求进入时逐级读取,执行优先级最低,但可以覆盖部分主配置。换句话说,如果主配置里已经设置了AllowOverride None,那么.htaccess文件会被完全忽略。
所以部署Vue项目时,第一件事就是确保项目所在目录开启了AllowOverride All:
<Directory "D:/www/app"> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory>如果你有权限管理服务器主配置,我建议用虚拟主机的方式部署,清晰又独立;如果你用的是虚拟主机或托管环境,没法改主配置,那.htaccess就是几乎唯一的选择。
2.3 配置虚拟主机托管Vue应用
虚拟主机配置有两种常见写法:基于域名和基于端口。基于域名的适合真实上线;本地调试或内网测试则常用端口区分。
一个比较标准的Vue项目虚拟主机配置,大概是这样的:
<VirtualHost *:80> ServerName myapp.example.com DocumentRoot "D:/www/app/dist" <Directory "D:/www/app/dist"> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> ErrorLog "logs/myapp-error.log" CustomLog "logs/myapp-access.log" common </VirtualHost>这里的关键点有两个:DocumentRoot必须指向打包产物目录(也就是dist),而不是源码目录;Directory权限必须设置正确,否则会报403 Forbidden。
注意:403错误里最容易被忽略的是
Require all granted。Apache 2.4开始,默认的授权逻辑和2.2完全不同,老教程里写Order allow,deny的配置在新版本里已经被标记为过时,强烈建议跟着2.4的语法走。
3. 打包构建与路由器模式选择
3.1 执行构建与产物检查
在你的Vue项目目录下执行:
npm run build构建完成后,dist目录下会生成index.html、assets文件夹等。建议在部署前先看一眼index.html里的资源引用路径:
<script type="module" crossorigin src="/assets/index-xxxx.js"></script>如果src是以/开头的,说明base是绝对路径根目录;如果是./开头,就是相对路径。这一步能帮你提前判断部署之后静态资源到底会从哪里加载。
还有一个经常被忽略的东西:public目录里的文件。Vite会把public目录下的文件原封不动地复制到dist根目录,比如favicon.ico、robots.txt、CNAME等。如果你发现某张图片或某个静态文件打包后找不到了,先看它是不是放在src/assets里被构建处理了,而不是放在public里。
3.2 createWebHistory和createWebHashHistory怎么选
Vue Router提供两种路由模式:createWebHistory(History模式)和createWebHashHistory(Hash模式)。它们的区别在于URL表现和服务器要求。
- Hash模式:URL形如
/index.html#/user/list,#后面的内容不会发送到服务器,所以服务器不需要做任何额外配置就能支持刷新。但缺点是不美观,且部分场景(如微信支付回调对URL有严格校验)可能出问题。 - History模式:URL形如
/user/list,是标准路径。看起来最正常,但服务器必须把所有前端路由都重写到index.html,否则刷新或直接访问深层路径会404。
我个人的习惯:对外面向用户的系统,只要服务器配置能跟上,一律用History模式。仅内部工具或没法配置服务器的静态托管场景,才考虑Hash模式。但这里没有对错,只有适合不适合。
3.3 为什么刷新会404:深入理解服务器回退逻辑
假设你部署了History模式的路由,用户访问https://example.com/user/1,服务器收到请求后会去DocumentRoot下找user/1这个路径——当然找不到,因为它不在磁盘上,是前端路由管理的内容。
Apache的mod_rewrite能做一件事:当请求的路径在文件系统中不存在时,把请求内部重写到index.html。这样前端路由接管之后,由Vue Router根据URL渲染对应页面。
规则如下:
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L] </IfModule>我来解释一下这三行的意思:
RewriteRule ^index\.html$ - [L]:直接访问index.html时不再重写,避免循环。RewriteCond %{REQUEST_FILENAME} !-f:如果请求的是真实存在的文件(如JS、CSS、图片),直接放行。RewriteCond %{REQUEST_FILENAME} !-d:如果请求的是真实存在的目录,直接放行。RewriteRule . /index.html [L]:其余所有情况,一律重写到index.html。
这个规则的核心思想就是:先放走所有真实静态资源,剩下的全部交给前端路由。
4. 实操部署全流程
4.1 完整配置文件示例
以一台Linux服务器为例,我把整个部署过程中涉及的配置整理成一个可直接套用的模板。Apache站点配置文件一般放在/etc/httpd/conf.d/或/etc/apache2/sites-available/下,按你的发行版选择。
<VirtualHost *:80> ServerName vue-deploy-demo.example.com DocumentRoot "/var/www/vue-app/dist" <Directory "/var/www/vue-app/dist"> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory> # 禁止访问隐藏文件 <FilesMatch "^\."> Require all denied </FilesMatch> ErrorLog "${APACHE_LOG_DIR}/vue-app-error.log" CustomLog "${APACHE_LOG_DIR}/vue-app-access.log" combined </VirtualHost>对应这份Vue项目根目录下新建的.htaccess:
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L] </IfModule> # 静态资源缓存策略(可选) <IfModule mod_expires.c> ExpiresActive On ExpiresByType text/css "access plus 7 days" ExpiresByType application/javascript "access plus 7 days" ExpiresByType image/png "access plus 30 days" </IfModule>注意index.html的缓存单独处理:index.html不要设长缓存,否则你发新版后用户始终拿到旧壳。一般建议index.html设置为no-cache。
4.2 上传构建产物的几种方式
构建产物上传服务器,核心是把dist目录完整同步到DocumentRoot指向的位置。结合我自己的实操经验,推荐以下方式:
FTP/SFTP:最直观,适合小项目。用FileZilla这类工具把
dist目录拖上去就行。但要注意:上传前最好清空目标目录里的旧文件,避免历史垃圾文件干扰,尤其当你改过资源名或删除过页面时,旧的JS/CSS残留在服务器上,会导致浏览器加载到冲突版本。rsync:Linux服务器上最常用的增量同步工具,效率高、支持压缩传输,而且只传变更文件。
rsync -avz --delete dist/ user@your-server:/var/www/vue-app/dist/这里的--delete一定要加,它表示同步时删除远端多余的文件,保证两端目录完全一致。
- Git钩子/CI/CD:如果你的项目已经纳入版本管理,可以配置Webhook或GitLab CI/CD,在代码合并或打Tag时自动执行
npm run build并同步到服务器。这种方法最适合生产环境频繁发版的团队,能避免人为操作的遗漏。
我的建议是:上线前期用SFTP + 手动清目录就好,因为这时候改动频繁,出现问题方便排查;真正稳定了再上CI/CD。不要一开始就追求全自动化,自动化程度越高,排障链路越长。
4.3 常见部署场景:子目录部署
还有一种很常见的场景:一台服务器上放了多个Web应用,Vue项目只是其中一个子目录,比如http://example.com/oa/。这种情况下,Vite的base要设为/oa/,Apache配置要新增Alias映射。
Alias /oa/ "/var/www/oa-app/dist/" <Directory "/var/www/oa-app/dist"> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory>同时.htaccess里的RewriteBase也要改成/oa/。如果你忘了改RewriteBase,会出现一种很诡异的情况:首页能访问,点击跳转到其他页面也没问题,但一刷新就404。原因就是重写出来的目标路径不对,服务器把请求重写到了根目录的index.html,而根目录根本没有这个文件。
还有一种偷懒的做法是直接把.htaccess里的内容改成:
RewriteRule . /oa/index.html [L]效果一样,只是写死了路径。
5. 常见问题与排查方法
5.1 白屏:JS加载失败还是路由没匹配?
白屏是Vue部署后最常遇到的问题。打开浏览器开发者工具,找到Console面板和Network面板,判断属于哪种情况。
如果Console报错Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html",说明服务器对.js文件的响应不是JavaScript类型,而是被重写成了index.html。这通常意味着你的Rewrite规则里的!-f条件不生效,或Apache未正确加载mod_mime。检查两点:
- 静态文件是否真的存在于目标目录。如果
dist里的JS文件名是index-abc123.js,但服务器目录下确实没有这个文件,那Rewrite规则判断“不是真实文件”然后正常重写到index.html,就会出现MIME类型错误。 - 用浏览器直接访问该JS路径,看看返回的是文件内容还是HTML页面。
如果Console报错是某个JS运行时错误,比如Uncaught TypeError: Cannot read properties of null,那一般不是部署问题,而是代码里某处拿不到DOM节点,属于业务代码问题,得回开发环境复现。
提示:白屏的时候不要只盯着屏幕,按F12看Network里哪些请求是红色,往往一眼就能定位。
5.2 history路由刷新404的三种原因
刷新404基本可以锁定在Rewrite规则或服务器配置层面,常见原因有三个:
第一,.htaccess没有生效。检查AllowOverride是否真的为All。很多教程只给.htaccess内容,不提这个前置条件,结果大家复制粘贴后死活不生效。
第二,REWRITE模块没有加载。Windows下的Apache经常出现这种情况。
第三,RewriteBase写错。尤其是子目录部署时,RewriteBase /会导致所有重写路径都指向根目录,404自然就来了。
排查时可以在.htaccess里放一段测试代码试探:
RewriteRule ^test$ /test.html [R=302,L]然后访问/test,如果浏览器跳转到了/test.html,说明Rewrite没问题;如果还是404,说明模块或配置压根没生效。
5.3 缓存导致线上永远“还是旧版本”
有时候你明明上传了新代码,用户或你自己刷新后看到的还是旧版本。这个坑比404还隐性。原因可能有两个:
一是服务端缓存。Apache的mod_cache模块或反向代理层(比如CDN)对静态资源做了长缓存,导致浏览器拿到的还是旧文件。
二是浏览器缓存。尤其index.html被浏览器、服务端都缓存后,整个页面入口就是旧壳,里面的JS引用也是旧的。
解决方案:
- 给
index.html设置Cache-Control: no-cache,让它始终回源验证; - 给带hash的静态资源设置长缓存,通常一年都没问题,因为文件名变了就是新文件,变了就自然会加载新文件。
Apache配置可以在.htaccess里加:
<FilesMatch "index\.html$"> Header set Cache-Control "no-cache, no-store, must-revalidate" </FilesMatch> <FilesMatch "\.(js|css|png|jpg|woff2)$"> Header set Cache-Control "public, max-age=31536000, immutable" </FilesMatch>注意Header指令需要mod_headers模块支持。这套方案我在实际项目里用来处理“用户永远看到旧版本”投诉,效果很好。
5.4 接口404或跨域问题
部署后页面能打开,但所有接口请求都404或超时,这个也很常见。原因一般是:
- 后端接口没部署到位或路径不对。
- 后端服务跨域限制,前端请求被拦截。
你在代码里把VITE_API_BASE_URL配成https://api.example.com,但服务器那边没允许你的前端域名跨域访问,就会出CORS错误。让后端配合放开跨域:
// Node.js后端示例 res.setHeader('Access-Control-Allow-Origin', 'https://your-frontend-domain.com') res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')如果你的前后端部署在同一台Apache上,还可以通过反向代理的方式解决跨域问题。比如前端在/api开头的请求直接代理到后端的127.0.0.1:8080:
ProxyPass /api/ http://127.0.0.1:8080/ ProxyPassReverse /api/ http://127.0.0.1:8080/这样前端代码里baseURL直接写/api即可,浏览器同源策略不会拦截,后端也不需要处理CORS。这种方案在生产环境里比“前端直连后端接口”更省心,还能顺便隐藏后端服务器地址。
5.5 Windows环境部署的专属坑
如果你是在Windows上用Apache部署,有几个额外问题特别容易踩:
- 路径分隔符:Apache配置文件里路径建议用正斜杠
/或双反斜杠,别用单个反斜杠,容易歧义。 - 端口占用:80端口经常被其他程序占用(比如IIS、Docker),启动Apache会失败。可以改用8080端口,或者把IIS停掉。
- 目录权限:Windows下Apache默认以某个系统用户运行,偶尔会出现“有权限但页面403”的情况,需要检查目录的“安全”选项卡里是否给了Users组读取和运行权限。
- 中文路径:千万不要把Vue项目放在带中文或空格的目录里,Apache解析路径时偶尔会抽风,尤其是旧版本。
6. 部署完成后的自检清单
经历过几次“改完配置、重启服务、还是不行”折磨之后,我整理了一个自检清单,每次部署完按顺序过一遍:
- 直接访问域名根目录,页面首页是否正常渲染。
- 按F12查看Console是否有红色报错。
- 查看Network里首个HTML请求的状态码和资源引用路径。
- 直接访问一个深层路由(如
/user/1),确认刷新不404。 - 检查任意带hash的JS/CSS请求是否返回200并正确加载。
- 登录页面走一遍登录流程,确认接口请求地址和响应正常。
如果以上都通过,这次部署基本就算稳了。如果有一项不过,也别慌,按上面几个小节的方式逐步排查。
7. 从部署延伸出去的几点建议
Vue打包部署只是把应用“送上线”的第一步。实际运维中,后面还有持续发布、版本回滚、日志排查等一堆事。这里根据自己的经验提几个建议。
第一,发布记录一定要做。每次部署前记一下版本号、变更内容、时间,出问题的时候能快速定位“这次改了什么导致线上挂掉”。
第二,保留上一次的构建产物,别急着删。发布后如果发现线上有严重Bug,把上一个版本的dist目录直接恢复回去,比修复代码再构建再上传快得多。我的做法是在服务器上保留一个release_20240101这样的备份目录,紧急情况一键切回。
第三,日志是排查问题的最可靠信息源。Apache的错误日志和访问日志里,几乎记录了你能遇到的所有异常。部署前确认ErrorLog和CustomLog已经打开,路径你知道在哪。很多线上问题,看日志比猜快得多。
第四,有条件的话做一下HTTPS。Apache开启SSL非常简单,配置好证书后你的站点就同时支持HTTP和HTTPS访问。如果前后端都在HTTPS下,接口跨域也少很多麻烦。部署Vue项目时,把80端口的HTTP流量做一个301跳转到443,也是一种标准做法:
<VirtualHost *:80> ServerName myapp.example.com Redirect permanent / https://myapp.example.com/ </VirtualHost>如果你用的是Let's Encrypt这类免费证书,续期脚本写好后基本不用操心,稳定得很。
再补充一个体验优化的小技巧:打包时开启Gzip压缩可以显著减少传输体积。如果你的Apache支持mod_deflate,在.htaccess里加几行配置就行。
<IfModule mod_deflate.c> AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json </IfModule>Vite打包出来的JS本身就是压缩过的,但文本类型的Gzip还能再少一大截。实测大部分Vue项目打包后的gzip体积约为原始体积的25%~35%,对页面加载速度的提升非常明显。