Windows下用Nginx部署Vue项目这个事,很多人觉得简单,无非就是npm run build然后把dist目录扔给Nginx托管。但真上手做的时候,刷新404、接口跨域、静态资源加载不出来、改完代码浏览器还是旧页面,这些问题一个接一个冒出来,尤其是动静分离这块,配不好整个部署就白搭。我前前后后帮团队配过不少Windows环境的Nginx,今天直接把一套能用的完整方案和一些踩坑记录整理出来,手把手带你把这事理顺。
1. 部署思路:先理解Nginx在Windows下承担的三种角色
在动手写配置之前,先想清楚Nginx在这一整套部署里到底在干什么。很多人上来就改nginx.conf,改完发现哪都不对,就是因为没搞明白Nginx同时扮演了三个角色。
第一个角色是静态文件服务器。Vue打包出来的dist目录里全是静态资源:index.html、js、css、图片字体等,Nginx需要把这些文件按路径正确返回给浏览器。这是最基础的,也是动静分离里“静”这部分。
第二个角色是反向代理服务器。生产环境下前端页面要请求后端接口,如果直接把后端服务地址暴露在浏览器里,一方面会有跨域问题,另一方面也不安全。Nginx会把前端过来的/api请求转发给真正的后端服务,浏览器看起来请求的是同一个域名,实际是Nginx在中间做了一手转发。这是“动”的部分。
第三个角色是缓存控制中心。静态文件可以设置长时间的浏览器缓存,入口index.html必须设置为不缓存或协商缓存。通过Nginx的expires指令和Cache-Control响应头,同一套配置能对不同资源下发完全不同的缓存策略。
我把这个思路讲清楚,是想强调一点:动静分离不是非得在目录结构上把静态资源和接口物理拆到两台服务器(虽然那样理解也没错),而是在Nginx这一层把不同类型的请求分流处理,各走各的规则。理解了这一层,后面写配置就是水到渠成的事。
2. 环境准备:Windows下Nginx和Vue项目的三个前置事项
2.1 装好Nginx并了解Windows版的启动方式
Windows版Nginx是一个解压即用的zip包,去官网下载稳定版(建议用1.24.x系列,别追最新,稳定压倒一切),解压到不含中文和空格的目录,比如D:\nginx。目录结构里重点关注两个地方:conf\nginx.conf是全局配置文件,html目录是默认站点目录。
很多人第一次启动Nginx都是直接双击nginx.exe,然后发现任务管理器里有好几个nginx.exe进程,又不知道怎么看日志,出了什么问题一头雾水。我建议在命令行里操作,先把当前目录切到Nginx解压目录,然后这样启动:
cd /d D:\nginx start nginx.exestart命令会启动Nginx并立刻返回,不会霸占当前命令行窗口。启动后可以用tasklist | findstr nginx确认进程存在,然后浏览器访问http://localhost,看到Welcome页面就说明装好了。
2.2 Vue项目打包前的两个配置检查
Vue项目打包用的是npm run build,默认生成到项目根目录下的dist文件夹。但在打包之前,有两个配置必须要检查,不然打包出来的东西部署到Nginx上大概率出问题。
第一是vue.config.js里的publicPath。这个值决定打包产物里所有静态资源的引用路径前缀。默认是/,意思就是资源路径是/js/app.js这种绝对路径。如果将来你的站点部署在域名根路径(比如http://192.168.1.10:8088/),那用/没问题。但如果部署在子路径下(比如http://192.168.1.10:8088/vue-app/),就必须改成相对路径或子路径前缀。我先按最常见的根路径部署来讲,publicPath保持/即可。
第二是路由的mode。Vue Router默认是hash模式,URL长这样:http://xxx/#/home,带着一个#。这种模式部署后刷新不会出问题,因为#后面的内容压根不会发给服务器。但很多人希望URL干净一些,把mode改成了history,URL变成http://xxx/home这种,问题就来了:浏览器直接访问http://xxx/home时,Nginx会在磁盘上找home这个文件或目录,找不到就返回404。后面我会专门说怎么用try_files解决这事,但你现在先记住:history模式必须配合Nginx配置,否则刷新直接白屏404。
2.3 规划端口与目录结构
为了避免和电脑上已有的服务冲突,我习惯把前端静态站点监听在8088端口,后端接口服务监听在8080端口,前端构建产物放在D:\vue-app\dist目录。这套规划在生产环境也够用,测试环境更是绰绰有余。目录规划有一个细节:在Windows上写Nginx的root路径时,既可以用正斜杠D:/vue-app/dist,也可以写成D:\\vue-app\\dist,千万别用单反斜杠,会被当作转义字符处理,路径直接失效。
3. 静态资源部署:让Vue页面先跑起来
3.1 第一个可用的server块写法
打开conf\nginx.conf,在http块内部新增一个server块。我先给一份最小可用配置,这份配置能让Vue打包产物在根路径下正常跑起来:
server { listen 8088; server_name localhost; root D:/vue-app/dist; index index.html; location / { try_files $uri $uri/ /index.html; } }这里的关键就在try_files $uri $uri/ /index.html;。它的意思是:用户请求某个路径时,先看看磁盘上有没有对应文件($uri),有就直接返回;没有的话再看看有没有对应目录($uri/),目录里有index.html就返回它;都没有,就把/index.html返回给浏览器,让Vue Router接管路由。
这一行配置就是history模式不404的命根子。没有它,你访问http://localhost:8088/home,Nginx会去磁盘找D:\vue-app\dist\home这个文件,找到才怪,直接404。有了它,不管用户访问/home还是/about还是/detail/123,Nginx一律把index.html丢给浏览器,然后前端路由自己判断该渲染哪个页面。把这一步想明白,很多“刷新就404”的疑惑瞬间就解开了。
3.2 验证配置和启动服务
每次改完nginx.conf,别急着重启,先做一次语法检查:
nginx -t看到nginx: configuration file ... test is successful这样的输出,说明语法没问题,然后重载配置:
nginx -s reloadreload会平滑重载配置,不需要停止再启动,这个操作在Windows下同样是支持的。如果改了配置但浏览器访问没变化,先确认是不是真的reload成功了。nginx -t有报错就仔细看提示,绝大多数是少写了分号或者括号没闭合。
接着把Vue项目打包,npm run build产出dist目录后,把内容完整拷贝到D:\vue-app\dist,浏览器访问http://localhost:8088,看到你的页面就说明静态部署这一环通了。
3.3 前端的打包细节与产物检查
打包完成后建议打开dist目录看一眼结构,正常情况下会有index.html和static(或js、css等,取决于assetsDir配置)目录。打开index.html的文件内容,检查里面的资源引用路径是不是/js/app.js这种以斜杠开头的形式。如果打包时publicPath配错了,你会看到类似./js/app.js或../js/app.js的相对路径,这种在根路径部署下通常也能跑,但在子路径场景下就另当别论了。
另外,如果你用的Vue CLI创建的项目,vue.config.js里建议显式指定assetsDir: 'static',这样所有静态资源都会归拢到static目录下,和入口index.html分开。这个习惯对我后面讲动静分离的目录匹配非常有帮助,你可以把它理解为把不同职责的文件先物理分区,后面Nginx配置时就能用路径前缀精准分流。
4. 动静分离配置:静态资源缓存策略和接口转发一次讲透
4.1 动静分离的本质:按请求特征分流
很多人刚接触“动静分离”这个词时,容易觉得很高端,其实拆开就是一句话:把静态文件请求和动态接口请求分开处理。静态文件走文件系统,直接读磁盘返回,配合强缓存让浏览器少发请求;动态请求走反向代理,转发给后端服务处理,不设置长缓存,保证每次都能拿到最新数据。
为什么要分开?先说静态资源。Vue打包后的js、css文件通常都带哈希指纹,比如app.8f3k2j.js。文件名变了就代表内容变了,没变就说明内容没变。针对这类文件,我们可以设置一个很长的缓存时间(比如30天),浏览器第二次访问时直接拿本地缓存,连请求都不发,加载速度瞬间提升。但index.html不能这么干,因为它是整个应用的入口,每次部署都要确保浏览器能拿到最新的,如果它被缓存了,那其它带新指纹的资源也引不进来,用户看到的就是旧版页面。
再说动态接口。接口返回的数据是实时变化的,肯定不能缓存,但我们也希望它走Nginx统一转发,而不是在前端代码里直接写死后端地址。这既解决了跨域问题,也让后端服务的地址不出现在浏览器端,算是一层基础防护。
4.2 缓存策略落地的两种写法
第一种是按目录前缀匹配,适合你打包时手动指定了assetsDir: 'static'或者明确把所有静态资源放在某个固定目录的场景。比如我上面提到把Vue构建产物的资源都收拢到static目录后,可以这样写:
location /static/ { alias D:/vue-app/dist/static/; expires 30d; add_header Cache-Control "public, max-age=2592000"; }注意alias的坑:location /static/后面如果跟alias,那么alias后面的路径要能直接拼接上请求路径中/static/之后的部分。比如请求/static/js/app.js,Nginx会去找D:/vue-app/dist/static/js/app.js。如果你用的是root而不是alias,那拼接方式就完全不同了,root D:/vue-app/dist/static的结果是去找D:/vue-app/dist/static/static/js/app.js,多了一层目录,直接404。我见过太多人在alias和root上栽跟头,核心就一句话:root会拼接完整的请求URI,alias会把location匹配的部分替换成指定路径。
第二种写法是按文件后缀匹配,不管文件在哪个目录,只要是js、css、图片等静态资源就命中规则:
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf)$ { expires 7d; add_header Cache-Control "public, max-age=604800"; }这种正则匹配的场景是:如果你不想改Vue的目录结构,资源散落在不同路径下,用后缀匹配更省事。~*表示不区分大小写的正则匹配,\.是转义点号,最后的$锚定结尾。后缀匹配的粒度粗一些,但胜在省心,不用管资源在哪个目录。
4.3 动态接口反向代理的写法与斜杠问题
接口这一层,在server块里加一个location规则:
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_pass后面到底带不带斜杠。带和不带,转发结果天差地别:
proxy_pass http://127.0.0.1:8080/;(带斜杠):请求/api/user/list会被转发成http://127.0.0.1:8080/user/list,/api前缀被去掉。proxy_pass http://127.0.0.1:8080;(不带斜杠):请求/api/user/list会被完整转发成http://127.0.0.1:8080/api/user/list,/api前缀原样保留。
这要根据你后端接口的实际路径来定。大多数情况后端接口原本就没有/api前缀,那前端约定所有接口统一加/api前缀,Nginx转发时把前缀剥掉,这就得用带斜杠的写法。如果你的后端服务本身就要求带/api路径,那就不带斜杠直接透传。这个选择没有对错,只看后端到底怎么定义的。
然后回到前端,开发环境里Vue项目通常用vue.config.js里的devServer.proxy做代理,生产环境就把这些代理配置迁移到Nginx。Vue里的请求路径建议写成相对路径,比如axios.get('/api/user/info'),这样开发环境走Vue代理,生产环境走Nginx代理,前端代码不用改一行。
4.4 动静分离配置好后验证哪些点
配置完,重启Nginx,重点看这几个地方:
打开浏览器开发者工具,切到Network标签,刷新页面,能看到两类请求。第一类是js、css、图片等静态资源,响应头里的Cache-Control如果显示public, max-age=...以及Expires字段,说明缓存策略生效了。第二类是/api/开头的XHR请求,点开看响应头,确认是后端服务返回的数据,而不是Nginx返回的404或其它错误。再看请求的Proxy头,确认真的是通过Nginx转发的路径。
如果接口请求报了404,优先检查proxy_pass的斜杠问题。如果接口返回了538之类的错误,多半是后端服务本身没启动或路径不对,这时候直接在服务器本机用curl http://127.0.0.1:8080/user/list测试后端地址是否通,能快速缩小排查范围。
5. 一份可直接抄作业的完整配置与部署校验清单
5.1 完整nginx.conf示例
下面这份配置,是我在Windows上实际用过的,涵盖了静态托管、history路由、动静分离、接口代理和常用优化项,可以直接整体替换到http块里的server部分:
server { listen 8088; server_name localhost; # 开启gzip压缩 gzip on; gzip_min_length 1k; gzip_comp_level 5; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_vary on; root D:/vue-app/dist; index index.html; # 入口html不做强缓存,保证每次部署后能拿到最新 location = /index.html { add_header Cache-Control "no-cache, must-revalidate"; } # 静态资源:按目录匹配,长缓存 location /static/ { expires 30d; add_header Cache-Control "public, max-age=2592000"; } # 静态资源:按后缀匹配,覆盖不在/static/下的资源 location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf)$ { expires 7d; add_header Cache-Control "public, max-age=604800"; } # 接口反向代理 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_connect_timeout 15s; proxy_read_timeout 60s; } # 前端路由history模式兜底 location / { try_files $uri $uri/ /index.html; } }这份配置里有几处细节值得说。第一个是location = /index.html用了精确匹配,=表示只匹配这个完整路径,优先级最高。当浏览器访问/index.html时走这条规则,不缓存;访问其它静态资源时分别走各自的缓存规则;接口请求走代理规则;剩下所有路径全部交给try_files兜底。不同请求各找各的出口,不会互相干扰。
第二个是gzip开启后,静态资源传输体积能减少60%~70%,尤其js、css这种文本文件效果显著。但要留意,gzip_types必须把你要压缩的类型写全,默认只压缩text/html,不写这三行等于白开了。图片一般不建议开gzip,因为图片本身已经是压缩格式,再压一遍纯属浪费CPU。
5.2 部署后的自检清单
Nginx配置搞定、服务跑起来之后,别急着收工,按这份清单过一遍:
- 浏览器访问
http://localhost:8088,首页正常显示,控制台没有红色报错。 - 点击进入一个有二级路由的页面,比如
/home、/detail/1,然后按F5强制刷新,页面不404、不白屏。 - 开发者工具Network里,
js、css请求的响应头Cache-Control已带上max-age,index.html的响应头Cache-Control是no-cache或未设置长缓存。 - 任意抓一个
/api/开头的接口请求,确认正常返回数据,状态码不是404或502。 - 如果页面里用到了WebSocket(比如实时消息),记得额外加
proxy_http_version 1.1和Upgrade相关头,location /ws/的代理规则跟普通/api/不太一样。 - 检查浏览器Network里的请求延迟,静态资源应该是毫秒级,如果发现某个
js文件要好几秒,看看是不是没开gzip或缓存没命中。
5.3 多项目部署的扩展思路
如果你手里的Vue项目不止一个,想在同一个Nginx上部署多个单页应用,思路也不复杂。可以在http块里加多个server,每个server监听不同端口,对应一个项目。但更常见的需求是同一个域名下用子路径区分项目,比如/app1和/app2分别指向两个Vue应用。这种场景就不能简单用root了,得用alias配合try_files调整:
location ^~ /app1/ { alias D:/vue-app1/dist/; try_files $uri $uri/ /app1/index.html; }这里面有个要注意的地方:try_files的最后一项/app1/index.html必须写访问URL对应的路径,而不是磁盘文件的绝对路径。原因是try_files的最后一个参数是内部重定向URI,Nginx会重新走一遍location匹配。让/app1/index.html再次命中当前location,然后被alias映射到磁盘的D:/vue-app1/dist/index.html。这个逻辑第一次看确实容易绕晕,我自己也在这里卡过不少时间,建议理解不了就直接抄配置跑一遍,眼见为实。
6. 常见问题与排查技巧实录
6.1 端口被占用怎么处理
Windows上最经典的坑就是端口冲突。listen 8088,结果启动时Nginx报错或者启动后访问的不是你的页面,大概率是这个端口被别的程序占了。排查命令:
netstat -ano | findstr :8088输出结果里最后一列是占用端口的进程PID,然后打开任务管理器查这个PID对应的进程。如果是无关程序,要么换端口,要么用taskkill /PID 1234 /F强制结束它。我遇到不少次是电脑里装了一些软件自带Nginx或Apache,悄悄就把80或8080端口占了,用这个命令一查一个准。
6.2 修改配置后不生效
改完nginx.conf明明执行了nginx -s reload,浏览器还是旧效果。先确认nginx -t语法检查通过,再确认你重载的是不是同一个Nginx实例。Windows下如果之前双击运行过nginx.exe,可能已经在后台驻留了进程,而命令行里执行的nginx -s reload找不到对应进程,就会报错。这时候最粗暴有效的方法,是把所有nginx.exe进程结束干净,再重新启动:
taskkill /IM nginx.exe /F start nginx.exe另外留意一个Windows特有的坑:改配置文件时如果文件被其它编辑器占用,保存可能失败,Nginx读出的是旧内容。改完配置顺手nginx -t一下,能通过就说明Nginx读到的是你写的最新版本。
6.3 刷新404但首页正常
这是最典型的history模式问题。现象:访问http://localhost:8088正常,点路由跳转也正常,一按F5就404。原因前面已经分析过,就是Nginx在磁盘上找不到对应的前端路由路径。解决方法就是给location /加上try_files $uri $uri/ /index.html;。如果你加了还不生效,检查一下是不是有其它location规则优先级更高,把本应走兜底逻辑的请求拦截了。比如某个静态资源匹配规则的范围把home这类路径也吞进去了,就会造成干扰。
6.4 静态资源404或加载路径不对
打开页面发现样式全丢了,控制台一堆Failed to load resource,这类问题九成是publicPath或root路径配置不对。先用开发者工具看请求的完整URL,如果请求的是http://localhost:8088/static/js/app.js,但磁盘上文件实际在D:\vue-app\dist\js\app.js,那就是打包时assetsDir和Nginx的location路径对不上。要么改打包配置把资源统一放static,要么改Nginx的匹配规则跟实际目录一致。这类问题在Windows上尤其容易出现在路径写错的情况下,比如大小写不一致、反斜杠误写,root和alias用混。
6.5 接口404和跨域问题的区别
接口返回404,先分清楚是Nginx层就找不到后端服务,还是后端真返回了404。看响应状态码:502/504通常说明Nginx连不上后端,检查后端服务是否启动、端口是否对、防火墙是否拦截;403说明有权限限制。如果接口直接返回的是后端的业务错误码,说明转发已经通了,问题在后端自己。跨域请求在部署到Nginx后基本都能解决,因为浏览器看到的只有localhost:8088这一个源,接口请求也是发到localhost:8088再被Nginx转发,不存在跨域。要是发现部署后还是报CORS错误,先确认前端代码里请求的地址是不是真的走的是Nginx域名,而不是写死了后端IP加端口。
6.6 更新部署后用户还是看到旧版本
这个和缓存策略强相关。如果index.html被浏览器缓存了,部署新版本后用户拿到的还是旧入口文件,后面的资源更新全部白搭。我在4.2节里已经把index.html单独设置成了no-cache,这是最标准的解法。还有一个辅助手段,是给入口HTML加个版本参数,比如部署脚本里自动更新index.html中引用的资源链接。不过加了no-cache之后,这个辅助手段一般用不上。
特别注意:
no-cache不等于不缓存。它表示浏览器和Nginx之间可以缓存这个文件,但每次使用前必须向服务器确认版本是否最新,是最新就用缓存里的,有新版本就拉新的。这比直接no-store(完全不缓存)体验更好,既保证更新及时,又省了重复下载的流量。
7. 日常维护的一点建议
配置稳定跑起来之后,还有几件小事建议顺手做好。Nginx的logs目录下会生成access.log和error.log,Windows下日志文件增长很快,尤其在频繁调试的时候,建议定期清理或者直接用任务计划程序写个定时脚本。我自己习惯在error.log里重点看[error]级别的报错,很多配置问题都会在这里留线索,排查起来比瞎猜快得多。
另外,每次改配置之前,先备份一份nginx.conf副本,标注好日期。这个习惯帮我省过不少事。动静态分离配置一旦复杂起来,变量和location规则增多,想回滚时没有一个干净的历史版本会非常痛苦。反正Windows下复制一个文件成本极低,费不了什么事。
最后再交代一下版本选择的问题。Nginx在Windows上的性能表现,和生产环境Linux相比确实有一点差距,并发高的时候可能不稳定。如果你的项目未来要面临较大流量,建议还是优先考虑Linux服务器。但如果是内部系统、中小型项目或者只在Windows环境做演示验证,用Windows版Nginx完全够用。我自己现在维护的几个内部系统就是Windows Server跑Nginx,跑了大半年没出过问题,稳定性和维护便利性都挺满意的。部署这种事,没有绝对的最好方案,只有最贴合你当前环境的方案。