拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Windows IIS 部署 Vue:History 路由、反向代理与权限排错

Windows IIS 部署 Vue:History 路由、反向代理与权限排错

1. 先把问题想透:Vue 项目为什么要落到 IIS 上

在 Windows 环境里做前端交付,很多人第一反应是丢到 Nginx 上,但对大量以 Windows Server 为主力服务器的团队来说,IIS 才是那台机器上现成、稳定、运维熟悉的东西。它自带图形化管理界面,Windows 身份验证、日志、应用程序池回收这些机制都不用额外装,域环境里做权限控制也顺手。所以标题里说的 Windows 下 IIS 部署 VUE 项目,本质是解决一件事:把npm run build出来的那一堆静态文件,变成一个可以被浏览器正常访问、刷新不报错、接口能走通的线上站点。

这里有个认知必须先掰正。Vue 项目打包之后,产物就是纯静态的 HTML、CSS、JS 和图片字体,它本身不需要 Node 运行时。IIS 在这条链路里干两份活:一份是静态文件服务器,负责把 dist 目录里的东西按正确的 MIME 类型吐给浏览器;另一份是反向代理,负责把前端发出的/api/xxx请求转到后面真正跑业务的 Java、.NET、Node 服务上。这两件事想清楚了,后面所有配置都是围绕它们展开的。

适合读这篇内容的人大概有三类。第一类是刚学会 Vue、本地npm run dev跑得挺欢,一上服务器就懵的新人;第二类是接手了别人项目,只知道要"发布到 IIS"但不知道从哪下手的前后端兼顾型开发者;第三类是做私有化交付的,客户现场只有 Windows Server 和 IIS,没有别的选择。下面我把整套流程拆成环境准备、构建配置、站点配置、权限调优、排错五个部分,每一步都给出可直接复制的配置和实测过的坑。

2. 部署前的环境准备与版本梳理

2.1 IIS 功能组件到底该勾哪些

Windows Server 上装 IIS,最快的路径是服务器管理器的"添加角色和功能",一路点到 Web 服务器(IIS)。但默认勾选是残缺的,很多人装完之后发现web.config里的 rewrite 规则不生效、静态压缩没效果,都是因为组件没选全。需要重点确认的几项:

  • Web 服务器 - 常见 HTTP 功能:默认文档、目录浏览、HTTP 错误、静态内容、HTTP 重定向。这几项是基础,缺了连 index.html 都出不来。
  • 性能:静态内容压缩、动态内容压缩。前端打包后的 JS 动辄几百 KB 到几 MB,不开压缩首屏会很难看。
  • 安全性:请求筛选。默认情况下 IIS 会拦截 URL 里带特殊字符的请求,遇到路径参数报 404.11,多半是它干的。
  • 应用程序开发:这块按需勾。纯静态 Vue 项目其实用不上 ASP.NET,但如果后端是 .NET 8,就得另外装 Hosting Bundle,而不是靠这里的选项。
  • 管理工具:IIS 管理控制台、IIS 管理脚本和工具。前者是图形界面,后者包含appcmd.exe,做批量配置时很有用。

注意:Windows 10 / 11 专业版上开 IIS 走的是"启用或关闭 Windows 功能",路径是控制面板 → 程序 → 启用或关闭 Windows 功能 → Internet Information Services。家用版没有完整的 IIS,别在这上面浪费时间。

装完之后浏览器打开http://localhost,看到 IIS 默认欢迎页就说明本体没问题。这一步看着简单,但它是后面所有配置的地基,我见过太多人跳过验证,结果后面排错时连"IIS 本身是否正常"这个变量都排除不掉。

2.2 Node 与构建环境的版本对齐

构建这一步通常不在服务器上做,而是在开发机或者 CI 机器上完成,只把 dist 目录拷过去。这个习惯我强烈建议保持,原因有两个:服务器上装 Node 会引入一堆不必要的运行时和依赖,而且不同人本地的 Node 版本不一致,构建产物也可能有差异。

版本对齐上要盯住三件事。Node 的大版本要统一,node -v对一下,团队里有人用 16、有人用 20,打包出来的产物在极端情况下会有兼容性差别,尤其是 Vite 对 Node 版本有硬性要求。包管理器锁死一个,npm、pnpm、yarn 混用会导致 lock 文件冲突,构建结果不稳定。构建命令写进 package.json 的 scripts 里,不要靠口头传递,npm run build:prod这种带环境标识的命名,比一句"你打个包"靠谱得多。

在服务器上验证产物是否正确有一个很土但很有效的办法:把 dist 目录随便用个静态服务器跑一下,比如npx serve dist,本地能正常访问再往 IIS 上放。这样能把"构建问题"和"IIS 配置问题"彻底分开,排错效率至少翻一倍。

2.3 目录规划与文件拷贝方式

站点根目录建议单独规划,不要用C:\inetpub\wwwroot这种默认位置堆一堆项目。我的习惯是D:\sites\下按项目名建目录,每个项目里再分frontend和backend,前端目录只放 dist 的内容,不带外层文件夹。这个"不带外层文件夹"很关键,很多人把 dist 整个拷进去,结果访问http://ip/dist/index.html才对,这就是典型的目录层级搞错了。

拷贝方式上,内网直接共享文件夹最省事,跨网络就用压缩包加远程桌面,或者走 CI 产物下载。不管用哪种,拷完一定要核对文件数量,尤其是 hash 命名的 JS 文件,漏一个就是白屏。文件数量对不上的时候,八成是拷贝过程中某几个大文件被跳过了。

3. Vue 构建产物的配置调整

3.1 base 与 publicPath 的取值逻辑

这是部署到 IIS 时最容易翻车的地方。Vue CLI 项目看vue.config.js里的publicPath,Vite 项目看vite.config.js里的base。它的作用是告诉打包工具,将来这个站点会被挂在哪个 URL 路径下。

如果站点直接挂在根路径,也就是访问http://ip/就能看到首页,那base保持默认的/就行,publicPath设成'/'。但如果站点挂在子目录下,比如http://ip/admin/,那必须把base改成'/admin/',注意前后两个斜杠都不能少。少了开头的斜杠,资源路径变成相对路径,在某些嵌套路由下会解析到错误的层级;少了结尾的斜杠,拼接出来的路径会变成/adminjs/app.js,直接 404。

// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ base: '/admin/', plugins: [vue()], build: { outDir: 'dist', assetsDir: 'assets', sourcemap: false } })

提示:如果项目将来可能会被部署到不确定的路径下,可以用base: './'走相对路径。代价是 history 路由模式下深层路由刷新时会出问题,所以只推荐给纯 hash 路由的项目用。

3.2 路由模式决定了要不要配 rewrite

Vue Router 有 hash 和 history 两种模式,它们对 IIS 的要求完全不同,这是整篇内容里最核心的一个分支点。

hash 模式的 URL 长这样:http://ip/#/user/list。井号后面的内容浏览器不会发给服务器,服务器永远只看到http://ip/,所以它天然不需要任何服务端配置,把 dist 丢进去就能跑。缺点是 URL 难看,而且对 SEO 不友好。

history 模式的 URL 是http://ip/user/list,干净好看,但代价是:用户在/user/list这个页面按 F5,浏览器会真的向服务器请求/user/list这个路径,而 IIS 上根本没有这个文件,于是 404。解决办法就是配置 URL Rewrite,把所有找不到实际文件的请求统统回退到index.html,让前端路由自己接管。

选哪个,我的建议是:后台管理系统、内部工具,直接用 hash 模式,省事;面向公众、有 SEO 需求的站点,用 history 模式,然后老老实实把 rewrite 配好。别为了"URL 好看"硬上 history 又不配回退规则,那是给自己挖坑。

3.3 打包命令与环境变量

生产环境打包一定要区分环境变量,最典型的是接口地址。开发时baseURL是/api,生产可能是https://api.xxx.com。Vue CLI 用.env.production文件,Vite 也是同样的机制,注意 Vite 里只有VITE_开头的变量才会被注入到客户端代码中,写成API_URL是拿不到的,这个坑我踩过。

# .env.production VITE_API_BASE=/api VITE_APP_TITLE=运营管理平台
// 代码里这样读 const baseURL = import.meta.env.VITE_API_BASE

推荐生产环境把接口地址写成/api这种相对路径,然后由 IIS 反向代理转发到后端。这样做的好处是前端产物和环境解耦,同一份 dist 放到测试环境和生产环境都能用,不用重新打包。如果直接写死后端域名,跨域问题、证书问题都会跟着来。

4. IIS 站点配置全流程

4.1 创建站点与应用程序池

打开 IIS 管理器,左侧展开服务器节点,右键"网站"→"添加网站"。填三样东西:站点名称随项目起、物理路径指向 dist 目录、绑定里选 HTTP、端口填 80 或你规划好的端口、主机名留空或者填域名。

创建完站点后,去"应用程序池"里找到自动生成的那个池,双击进去改两个地方。.NET CLR 版本选"无托管代码",因为纯静态站点不需要 CLR,选错版本会白白增加开销。托管管道模式选"集成"。这两项改完,右键应用程序池点"回收",让配置生效。

端口这块有个细节:如果 80 端口已经被默认网站占了,要么先停掉默认网站,要么给自己的站点换端口。判断端口是否被占用,命令行netstat -ano | findstr :80一看便知,最后一列是进程 PID,任务管理器里对一下就知道是谁在用。

4.2 安装 URL Rewrite 与配置回退规则

history 路由的回退规则依赖URL Rewrite Module,这个模块 IIS 默认不带,需要单独下载安装。没装的时候,你在 web.config 里写 rewrite 规则,IIS 直接给你报 500.19,错误信息里会提到"无法读取配置节 rewrite,因为它缺少节声明",看到这句基本就是它没装。

装好之后,在站点根目录放一个web.config,内容如下:

<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <rewrite> <rules> <rule name="VueHistoryMode" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> <add input="{REQUEST_URI}" pattern="^/api/" negate="true" /> </conditions> <action type="Rewrite" url="/index.html" /> </rule> </rules> </rewrite> </system.webServer> </configuration>

逐行解释一下。match url=".*"表示匹配所有请求。三个 condition 是过滤条件:第一个判断请求的不是真实文件,第二个判断不是真实目录,第三个判断不是/api/开头的接口请求——这条很重要,如果不排除接口路径,前端调接口会被 rewrite 成 index.html,返回一堆 HTML,后端直接懵。negate="true"是取反的意思。最后 action 把符合条件的请求重写到index.html。

如果站点部署在子目录/admin/下,url="/index.html"要相应改成/admin/index.html,这一点经常被漏掉。

4.3 反向代理转发后端接口

前端静态资源搞定后,接口怎么走通是第二道关。方案有两种,一是前端直接请求后端完整地址,靠后端配 CORS 解决跨域;二是前端只请求/api,由 IIS 转发到后端,前端和后端同源,跨域问题直接消失。我强烈推荐第二种,配置统一、排查简单、上线后不用改前端代码。

实现第二种需要Application Request Routing(ARR)模块。装完后在 IIS 管理器根节点找到"Application Request Routing Cache",右侧点"Server Proxy Settings",勾选"Enable proxy"并应用。这一步不做的话,后面的 rewrite 转发会报 502。

然后在站点的 web.config 里加一条代理规则,注意要放在回退规则之前:

<rule name="ApiProxy" stopProcessing="true"> <match url="^api/(.*)" /> <action type="Rewrite" url="http://127.0.0.1:8080/{R:1}" /> </rule>

{R:1}是反向引用,代表正则里第一个括号匹配到的内容。请求/api/user/list会被转发成http://127.0.0.1:8080/user/list。如果你的后端接口本身也带/api前缀,那把 action 的 url 改成http://127.0.0.1:8080/api/{R:1},或者把 match 改成^api/(.*)但在转发时保留前缀,具体看后端怎么定义的,这里没有统一答案,实测一次就知道了。

4.4 默认文档与 MIME 类型补全

站点功能里双击"默认文档",确认index.html在列表里且排在最上面。IIS 默认列表里通常是Default.htm、Default.asp这些,index.html有时不在,缺了就手动加一行。

MIME 类型这块,新版 IIS 对.js、.css、.svg、.json基本都认识,但.woff、.woff2、.webp、.mjs、.avif这些较新的格式可能没有映射,表现是字体加载失败、图片不显示,控制台一堆 404.3。补的方式是在 web.config 的staticContent节里加:

<staticContent> <remove fileExtension=".woff2" /> <mimeMap fileExtension=".woff2" mimeType="font/woff2" /> <remove fileExtension=".webp" /> <mimeMap fileExtension=".webp" mimeType="image/webp" /> <remove fileExtension=".mjs" /> <mimeMap fileExtension=".mjs" mimeType="text/javascript" /> </staticContent>

先remove再mimeMap是个保险动作,避免服务器上已经有同名映射导致配置冲突报错。如果确定服务器上没有这个扩展名的映射,直接 mimeMap 也行,但多写一行 remove 更稳妥,我一般都会带上。

5. 权限、缓存与性能调优

5.1 应用程序池权限设置失败的应对

热词里提到的"应用程序池权限设置失败,请手动为其设置 LocalSystem 权限"这个报错,其实是个权限与标识的匹配问题。IIS 应用程序池默认用ApplicationPoolIdentity这个虚拟账户运行,它对站点目录本身没有读写权限,如果站点目录放在非系统盘、或者从别的机器拷贝过来时权限没继承,就会出各种拒绝访问的提示。

正确的做法不是无脑改成 LocalSystem,而是给IIS_IUSRS和对应的应用程序池虚拟账户赋权。操作路径:右键站点目录 → 属性 → 安全 → 编辑 → 添加,输入IIS_IUSRS确定,给"读取和执行""列出文件夹内容""读取"三项即可。静态站点只需要读权限,不需要写权限,给写权限反而是安全隐患。

如果确实遇到设置应用程序池标识时报 0x80005000 这类错误,通常是因为指定的账户名在当前机器上不存在,或者域账户的凭据不对。这时候换成ApplicationPoolIdentity再赋权,问题基本就没了。用 LocalSystem 跑网站是个下策,它权限过高,一旦站点有上传功能被利用,影响面会很大。

5.2 静态资源缓存策略

Vue 打包出来的 JS 文件名一般带 hash,比如app.8f3a2b.js。这类文件内容变了文件名就变,天然适合长期缓存。而index.html绝对不能缓存,否则用户拿到的是旧 HTML,里面引用的却是已删除的旧 JS,表现就是白屏。

在 web.config 里加clientCache配置:

<staticContent> <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" /> </staticContent> <httpProtocol> <customHeaders> <add name="Cache-Control" value="no-cache, no-store" /> </customHeaders> </httpProtocol>

上面这段是全局长缓存的思路,但index.html需要单独处理。更精细的做法是在站点里对.html做单独配置,或者在构建产物里通过 meta 标签控制。实际操作中,如果你发现每次发版用户都要强刷,八成就是 index.html 被缓存了,这条经验值一个通宵。

5.3 压缩配置与首屏体积控制

IIS 的静态压缩默认只认text/html、text/css、text/plain这几种,.js、.json、.svg都不在默认列表里,需要手动加。在 web.config 里:

<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files"> <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" /> <staticTypes> <add mimeType="text/javascript" enabled="true" /> <add mimeType="application/javascript" enabled="true" /> <add mimeType="application/json" enabled="true" /> <add mimeType="image/svg+xml" enabled="true" /> </staticTypes> </httpCompression>

注意:httpCompression这个节默认被锁定,直接在 web.config 里写会报 500.19。需要先在 IIS 管理器里进入根节点,打开"配置编辑器",把system.webServer/httpCompression这一节设为解锁(Unlock Section)。

压缩开没开,用浏览器开发者工具的 Network 面板看响应头有没有Content-Encoding: gzip就知道。一个 1.5MB 的 JS 压到 400KB 左右是常见水平,首屏时间能砍掉一大截。如果开了压缩体积没变化,先确认是不是动态压缩和静态压缩搞混了——静态压缩要求文件在磁盘上是没压过的原始文件,IIS 自己压缩后缓存到临时目录。

6. 常见报错与排查速查

6.1 刷新页面 404 与子目录资源丢失

页面能打开,点导航也正常,一按 F5 就 404,百分之九十九是 history 模式没配回退规则,或者规则写了但 URL Rewrite 没装。判断方法很简单:直接在浏览器地址栏手输一个不存在的路径,如果返回的是 IIS 的默认 404 页面而不是你 Vue 项目的 404 页面,那就是回退没生效。

另一种情况是资源全部 404,页面白屏,控制台报/assets/xxx.js not found。这通常是base或publicPath配错了,尤其是部署到子目录时。一个快速的验证手段:在浏览器里直接打开 dist 目录下的index.html,看里面<script src="...">的路径是什么,跟你实际部署路径对不对得上,一眼就能看出问题。

6.2 500.19 与其他配置类报错

500.19 是 IIS 配置错误的总称,具体原因看错误详情里的"配置错误"那一行。常见的三种:一是 URL Rewrite 模块没装,前面说过;二是 web.config 里的节被锁定,比如 httpCompression;三是配置文件里有语法错误,比如标签没闭合、属性名拼错。

排查时把错误详情里的"配置源"文件路径和行号记下来,直接定位到那一行看。另外 IIS 管理器的"配置编辑器"是个好工具,改之前先在里面看目标节是否存在、是否被锁定,比盲改 web.config 高效得多。

6.3 权限类与应用程序池类故障

现象可能原因解决方向
401.3 未授权目录缺少 IIS_IUSRS 读权限给目录加 IIS_IUSRS 读取权限
403.14 禁止目录浏览index.html 不在默认文档列表在默认文档中加 index.html 并置顶
503 服务不可用应用程序池被停用或频繁回收查看池状态与事件日志,检查内存限制
500.30 启动失败后端是 .NET,Hosting Bundle 缺失安装对应版本的 Hosting Bundle 并重启
502 网关错误ARR 代理未启用或后端地址不通开启 Enable proxy,telnet 测后端端口

这张表是我自己排错时总结的,覆盖了日常八成的故障。把它贴在电脑边上,遇到问题先对号入座,能省不少时间。

6.4 日志文件的正确打开方式

IIS 的访问日志默认在C:\inetpub\logs\LogFiles\W3SVC{站点ID}下,按天生成.log文件。站点 ID 在 IIS 管理器的网站列表里能看到。日志里记录了每次请求的 URL、状态码、耗时,排查"某些请求慢""某些请求 404"这类问题时,比看浏览器控制台准。

我常用的一个技巧是把日志拖到 Excel 里按状态码筛选,如果某个资源频繁返回 404,说明构建产物里缺文件或者路径不对;如果某个接口耗时稳定在几秒,那问题在后端,不在 IIS。把这个区分清楚,就不会出现前端、后端、运维三方互相甩锅的场面。

7. 备份还原与多环境发布的习惯

IIS 的配置分散在applicationHost.config和各个站点的web.config里,手动备份很容易漏。我推荐用appcmd.exe做整体备份:

%windir%\system32\inetsrv\appcmd.exe add backup "20250101_before_release"

恢复的时候:

%windir%\system32\inetsrv\appcmd.exe restore backup "20250101_before_release"

这个命令会把整个 IIS 配置打包存起来,包括所有站点、应用程序池、绑定信息。做重要变更之前跑一遍,出问题回滚只要几秒钟。我见过有人手动改 applicationHost.config 改崩了,最后只能重装 IIS,那种滋味不好受。

多环境发布上,我的实践是同一份 dist 到处用,差异全部靠 web.config 和反向代理规则吸收。测试环境的 proxy 指向测试后端,生产环境指向生产后端,前端产物一个字都不用改。这样发布流程就变成了:构建一次 → 校验产物 → 拷到目标目录 → 回收应用程序池,四步走完,出错的概率极低。

最后分享一个我自己踩过坑之后固定下来的小动作:每次发版前,先在站点目录里把旧的 dist 改名成dist_bak_日期,再把新的放进去,跑一圈没问题再删旧的。整个过程不涉及复杂脚本,就两个重命名加一次回收,但它救我过好几次——有一次新版打包漏了个静态资源,发现时已经在线上跑了十分钟,直接把目录名换回来,两秒钟恢复。这种土办法看着不高级,但在没有完整 CI/CD 的项目里,它是最实在的保险。

返回列表