IIS 部署 Vue:解决 history 模式刷新 404 与重写配置

发布时间:2026/10/1 12:03:28
IIS 部署 Vue:解决 history 模式刷新 404 与重写配置 周五晚上八点打包好的dist目录整个丢进 IIS 的站点根目录浏览器打开首页——正常点导航跳转——也正常手贱按了一下 F5页面直接 404。这个场景我见过太多次了多到一看标题「IIS 部署 vue 项目」就知道对方卡在哪一步。问题不在 Vue也不在 IIS 有 bug而在于 Vue Router 的history模式把「路径」当成了前端状态而 IIS 从头到尾都认为「路径」对应磁盘上一个真实存在的文件或者目录。两边对同一个 URL 的理解完全不一致冲突就必然发生。这篇东西想解决的就是这一类问题把一个已经打包好的 Vue 项目稳稳当当放到 IIS 上跑起来刷新不 404、子目录部署不白屏、静态资源不缓存错、接口能转发、出报错能自己查出来。适合两类人看一类是前端出身、第一次碰 IIS 的同学另一类是常年写后端、被临时抓来发版的全栈。全文不依赖任何特定框架版本Vue 2、Vue 3、Vite、Vue CLI 打包出来的产物都适用因为到了 IIS 这一层它只认识 HTML、JS、CSS 和一堆静态文件。1. 刷新就 404 的根因Vue 的路由和 IIS 的文件查找不是一回事1.1 从一次真实的 404 现象说起假设站点根目录是D:\web\mysite里面躺着index.html、assets目录。你在浏览器里输https://example.com/user/listIIS 收到请求后做的事非常朴素把user/list拼到根目录后面去磁盘上找D:\web\mysite\user\list这个文件。找不到回 404。它根本不知道也不关心你前端里定义过一条/user/list的路由。而你的开发环境为什么没事因为npm run dev起的是 Vite 或 webpack-dev-server它们默认带了一个historyApiFallback的兜底任何找不到物理文件的请求统统返回index.html。浏览器拿到index.htmlVue 跑起来路由匹配到/user/list页面渲染。所以本地开发从来没暴露过这个问题它被 dev server 悄悄盖住了。部署到 IIS 上来本质就是把这个兜底逻辑在服务端再实现一遍。实现手段就是 URL Rewrite把「找不到物理文件」的请求重写到index.html让 Vue 自己去解析。逻辑就这么简单剩下的全是细节。1.2 hash 模式为什么「天生免疫」这类问题如果你的路由用的是createWebHashHistory()URL 长这样https://example.com/#/user/list。注意井号后面的部分浏览器在发 HTTP 请求时根本不会带上它发给 IIS 的永远是https://example.com/。IIS 老老实实返回index.htmlVue 拿到 hash 自己做匹配。整个过程 IIS 只服务了一个静态文件完全不需要重写规则。这就是为什么很多人简单粗暴地把模式改成 hash 就「修好了」。但代价也很明显URL 里带个井号分享出去难看SEO 不友好部分第三方回调比如登录回跳遇到井号会有额外处理成本。所以我的建议是——能用 history 就用 history把重写规则配对一次配好后面几十年都不用管。除非你的项目部署在完全没法改服务端配置的环境里比如某些托管平台只给一个纯静态目录那才退回 hash。1.3 先决定模式再决定要不要装 URL Rewrite顺序不要搞反。正确流程是打开src/router/index.js或router.js确认是createWebHistory()还是createWebHashHistory()。如果是 hash直接扔文件到 IIS配好默认文档index.html就能跑不用装 URL Rewrite。如果是 history先装 URL Rewrite 模块再写规则。我在实际项目里踩过的一个坑是接手别人的项目路由文件里写的 history 模式但打包配置里又加了publicPath: ./同时 IIS 上还配了个「虚拟目录」而不是「应用程序」三层因素叠在一起导致重写规则怎么写都对不上。所以后面第 3 章会专门讲子目录部署时的路径对齐那是最容易翻车的地方。2. 装 IIS 之前先想清楚哪些功能必须开哪些能省2.1 URL Rewrite 模块的版本与安装来源IIS 本体在 Windows 里是「功能」但 URL Rewrite 是独立组件Windows 功能列表里没有它。这点是新手最容易卡住的地方他们打开服务器管理器翻了个底朝天也找不到「重写」这两个字。获取方式是从 IIS 官方站点下载独立安装包选rewrite_amd64_zh-CN.msi这个版本现在常见的是 2.1 版。安装过程没什么可说的一路下一步。装完之后不会立刻在管理器里出现需要关掉 IIS 管理器重新打开或者干脆执行一次iisreset。你会看到站点功能区多了一个「URL 重写」的图标双击进去能可视化配规则。顺便说一个实用判断技巧如果你在站点根目录放了带rewrite节点的web.config但服务器上没装这个模块IIS 不会忽略它而是直接给你一个 500.19错误信息里带着0x8007000d和「无法识别的配置节 rewrite」。看到这个组合基本可以断定是模块没装而不是配置文件写错。2.2 Windows 功能里必须勾选的那几项Windows Server 2019 上路径是「服务器管理器 → 添加角色和功能 → Web 服务器(IIS)」。有一堆子项容易漏我列一下对 Vue 项目真正有用的功能项位置是否必须静态内容常见 HTTP 功能必须漏了连 HTML 都返回不了默认文档常见 HTTP 功能必须决定访问目录时返回哪个文件HTTP 错误常见 HTTP 功能建议开方便看到真实状态码目录浏览常见 HTTP 功能建议关避免目录结构外泄静态内容压缩性能建议开JS/CSS 体积能小七八成动态内容压缩性能代理转发场景才需要URL 授权规则安全性一般不用除非要做访问控制WebSocket 协议应用程序开发项目里有实时推送才需要Win11 家用版是没有 IIS 的在「启用或关闭 Windows 功能」里翻不出这一项必须是专业版或企业版。这点很多人在自己笔记本上折腾半天最后发现是系统版本问题。另外 Win11 上启用之后inetmgr命令不一定能直接调起来可以在开始菜单搜「IIS 管理器」或者去「管理工具」里找。注意Windows Server 上装完 IIS 后默认站点会占用 80 端口。如果你要部署的站点也用 80记得先把默认站点停掉或者删掉否则两个站点抢端口表现是「访问到的是另一个页面」很容易误判成缓存问题。2.3 应用程序池按「无托管代码」配不是随便选纯静态的 Vue 产物没有任何 .NET 代码在跑所以应用程序池的.NET CLR 版本应该设成**「无托管代码」**。选成v4.0虽然也能跑但会白白加载一个 CLR 运行时占内存、拖慢首字节响应而且一旦这台机器上还跑着别的 .NET 应用容易在权限和临时目录上打架。托管管道模式选「集成」这个默认就是不用改。再就是身份标识。默认是ApplicationPoolIdentity这是最稳妥的选择。它对应的实际账户是IIS AppPool\你的池名。很多教程让人改成LocalSystem或者Administrator我不建议——那是为了绕过权限报错代价是把整个 Web 目录暴露在最高权限下。正确的做法是留在ApplicationPoolIdentity然后去目录权限里给这个虚拟账户单独授权。另外热词里出现的「iis 中没有 .net8」这个和 Vue 部署其实没关系。如果你这台 IIS 上同时要跑一个 .NET 8 的后端 API那需要单独安装 ASP.NET Core Hosting Bundle装完iisreset才会在模块列表里出现AspNetCoreModuleV2。前后端是两个独立进程配置上也应该拆成两个应用程序池互不干扰。3. web.config 逐行拆解一条能用的重写规则长什么样3.1 最小可用版本与每个节点的职责先看一个能跑起来的最小版本位置是站点根目录下的web.config?xml version1.0 encodingutf-8? configuration system.webServer rewrite rules rule nameVue History Fallback stopProcessingtrue match url(.*) / conditions logicalGroupingMatchAll add input{REQUEST_FILENAME} matchTypeIsFile negatetrue / add input{REQUEST_FILENAME} matchTypeIsDirectory negatetrue / /conditions action typeRewrite url/index.html / /rule /rules /rewrite /system.webServer /configuration逐行说清楚它干了什么match url(.*) /里的url不是完整的 URL而是站点根目录之后的相对路径并且不带开头的斜杠。比如请求/user/list?a1这里的url是user/list查询字符串不包含在内。(.*)就是全部捕获。stopProcessingtrue的意思是这条规则命中之后后面的规则不再执行。这个属性非常关键如果漏了会和其他规则叠加产生难以预测的结果。action typeRewrite url/index.html /用的是 Rewrite 而不是 Redirect。区别很重要Rewrite 是服务端内部改写浏览器地址栏 URL 不变用户仍然看到/user/listRedirect 是返回 301/302浏览器地址栏会变成/index.htmlVue 拿不到原始路径路由直接失效。所以这里必须用 Rewrite。3.2 两个条件判断为什么必须加 negate如果没有那两个条件会发生什么所有请求——包括/assets/index-abc123.js、/logo.png——都会被重写到index.html。浏览器请求一个 JS 文件收到的却是 HTML控制台立刻报Unexpected token 页面白屏。这就是热词里「vue 打包后布局异常」最典型的成因之一不是样式写错了而是资源文件被重写规则吃掉了。条件里的{REQUEST_FILENAME}是 IIS 提供的服务器变量值是请求映射到磁盘后的完整物理路径。matchTypeIsFile配合negatetrue翻译成人话就是「当这个路径不是一个已存在的文件时条件成立」。IsDirectory同理判断是不是一个真实目录。两个条件用logicalGroupingMatchAll组合意思是必须同时满足「不是文件」且「不是目录」才会触发重写。这样真实存在的 JS、CSS、图片、字体文件都能正常返回只有那些不存在的路径也就是 Vue 的路由地址才会交给index.html。这里有个细节值得多说一句IsFile和IsDirectory会真的去访问文件系统。请求量大的时候这确实有一点 IO 开销但 IIS 本身对这些元数据有缓存实测在几万 QPS 级别的静态站点上没有成为瓶颈。真到了需要优化的程度说明你的流量已经该上 CDN 了而不是继续在 IIS 上抠这点开销。3.3 部署到子目录时 base 与重写目标的对应关系这是翻车率最高的一块。假设你的站点不是部署在根目录而是https://example.com/app/下面。这时候有两处必须同时改改一处都不行。第一处是打包配置。Vite 项目改vite.config.jsexport default defineConfig({ base: /app/, // 其他配置 })Vue CLI 项目改vue.config.jsmodule.exports { publicPath: /app/, }这一处的效果是index.html里引用资源的路径会从/assets/xxx.js变成/app/assets/xxx.js。漏了这一步浏览器会去根目录找/assets/xxx.js找不到就 404表现就是白屏加一屏红色报错。第二处是重写规则的目标路径action typeRewrite url/app/index.html /如果你用的是相对写法urlindex.html在根目录部署时没问题在子目录部署时行为取决于 IIS 版本和上下文容易出现解析到上一级的情况。我的习惯是始终写绝对路径把站点前缀写死这样不管将来挪到哪里逻辑都是清晰的。还有一个更隐蔽的坑子目录如果在 IIS 里被配成了「虚拟目录」而不是「应用程序」那么父站点的web.config会向下继承。父站点的重写规则如果写得比较宽比如匹配(.*)且没排除子目录会把子站点的请求也重写到父站点的index.html于是子应用永远打不开。解决办法有两个要么把子目录右键 → 转换为应用程序给它独立的应用程序池和配置边界要么在父规则里加一条条件把子路径排除掉。我推荐前者配置边界清晰排错时不会互相干扰。3.4 完整可抄的 web.config含优先级顺序把上面几件事合到一份文件里同时把规则顺序也排好。规则是从上往下依次匹配的所以特殊规则必须排在通用规则前面?xml version1.0 encodingutf-8? configuration system.webServer rewrite rules !-- 1. 不存在的 assets 资源直接 404避免被兜底成 index.html 掩盖问题 -- rule nameMissing Asset 404 stopProcessingtrue match url^assets/.* / conditions add input{REQUEST_FILENAME} matchTypeIsFile negatetrue / /conditions action typeCustomResponse statusCode404 statusReasonNot Found statusDescriptionStatic asset not found / /rule !-- 2. 接口请求交给后端如果有的话必须排在兜底规则之前 -- !-- 3. 其余全部交给 index.html -- rule nameVue History Fallback stopProcessingtrue match url(.*) / conditions logicalGroupingMatchAll add input{REQUEST_FILENAME} matchTypeIsFile negatetrue / add input{REQUEST_FILENAME} matchTypeIsDirectory negatetrue / add input{REQUEST_URI} pattern^/api/ negatetrue / /conditions action typeRewrite url/index.html / /rule /rules /rewrite defaultDocument files clear / add valueindex.html / /files /defaultDocument httpErrors errorModeDetailedLocalOnly existingResponsePassThrough / /system.webServer /configuration两条容易被忽略的配置值得说。defaultDocument里加index.html并clear是为了确保访问https://example.com/时返回的是你的 SPA 入口而不是 IIS 默认的iisstart.htm之类。httpErrors的existingResponsePassThrough是让应用本身产生的响应状态码原样透传避免 IIS 用自己的错误页覆盖掉前端返回的 404 或者 401调试接口的时候这个非常有用。提示改完web.config不需要重启站点IIS 会自动监听文件变更。但如果你改的是applicationHost.config层面的东西比如解锁配置节那就得iisreset或者重启对应站点。4. 那些刷新 404 之外的问题缓存、压缩、MIME4.1 index.html 被缓存住导致发布不生效重写配好之后另一个高频问题是「明明发了新版本用户看到的还是旧页面」。原因通常是index.html被浏览器缓存了。index.html是所有资源的入口它一变引用的哈希文件名就变了整个版本才会更新。它被缓存住等于整个发布链路断在了第一环。解决办法是在站点配置里对index.html明确禁用缓存location pathindex.html system.webServer staticContent clientCache cacheControlModeDisableCache / /staticContent httpProtocol customHeaders add nameCache-Control valueno-cache, no-store, must-revalidate / add namePragma valueno-cache / add nameExpires value0 / /customHeaders /httpProtocol /system.webServer /locationlocation节点必须放在configuration下面和system.webServer平级不能塞到system.webServer里面去。这点写错了会直接 500.19。三个头部是有历史原因的Cache-Control是现代浏览器认的Pragma和Expires是给老代理和 IE 时代的老客户端兜底的。现在只写第一个也基本够用但既然成本为零一起带上更省心。4.2 带哈希的静态资源为什么可以放心长缓存Vite 和 Vue CLI 打包出来的assets目录文件名里都带内容哈希比如index-a1b2c3d4.js。文件内容一变文件名就变所以这类资源可以放心大胆地设超长缓存期location pathassets system.webServer staticContent clientCache cacheControlModeUseMaxAge cacheControlMaxAge365.00:00:00 / /staticContent /system.webServer /locationcacheControlMaxAge的格式是天.时:分:秒所以365.00:00:00就是 365 天。这个组合的效果是用户第一次访问下载全部资源之后每次访问只需要拉一个几 KB 的index.html其余全部走本地缓存。实测下来首屏加载能从一两秒降到一两百毫秒。有个前提要注意这个策略成立的唯一条件是文件名带哈希。如果你的项目因为某些历史原因关掉了哈希比如build.assetsDir定制成了固定名那就千万别设长缓存否则用户永远拿不到新文件只能靠强刷。判断方法很简单打开dist/assets目录看一眼文件名有没有那一串随机字符。4.3 m3u8、ts、woff2、json 这些类型要手动补IIS 内置的 MIME 类型表是比较老的很多现代前端用到的扩展名它不认识。不认识的表现是文件明明在那儿请求却返回 404准确说是 404.3因为被 MIME 规则拦掉了。热词里出现的vue 播放 m3u8就属于这一类。需要在system.webServer下补一张表staticContent remove fileExtension.json / mimeMap fileExtension.json mimeTypeapplication/json / mimeMap fileExtension.m3u8 mimeTypeapplication/vnd.apple.mpegurl / mimeMap fileExtension.ts mimeTypevideo/mp2t / mimeMap fileExtension.woff mimeTypefont/woff / mimeMap fileExtension.woff2 mimeTypefont/woff2 / mimeMap fileExtension.wasm mimeTypeapplication/wasm / mimeMap fileExtension.webmanifest mimeTypeapplication/manifestjson / mimeMap fileExtension.svg mimeTypeimage/svgxml / /staticContent.json之所以要先remove再add是因为部分 IIS 版本内置的application/json定义和实际需求有出入直接add会因为重复定义报错。remove之后再add无论原来有没有都能保证最终只有一条。.m3u8配成application/vnd.apple.mpegurl、.ts配成video/mp2t这两个是 HLS 播放的通行约定。配错了会怎样某些播放器会因为 MIME 类型不符拒绝解析控制台报一个看着毫不相关的错误。这个我在一个监控回放项目里踩过排查了两个小时才发现是 MIME 的问题。注意staticContent这个配置节在部分 IIS 环境下是被锁定的直接写在web.config里会 500.19。遇到这种情况用管理员命令行执行解锁%windir%\system32\inetsrv\appcmd unlock config /section:system.webServer/staticContent然后再iisreset。4.4 压缩开不开取决于你的服务器预算静态内容压缩能把 JS 和 CSS 的体积压掉七成左右效果非常直观urlCompression doStaticCompressiontrue doDynamicCompressiontrue /但这里有两个前提必须同时满足否则开了也不生效。第一Windows 功能里得装了「静态内容压缩」第二applicationHost.config里的压缩配置允许压缩对应的 MIME 类型默认只压text/*和application/javascript像application/json、image/svgxml这些要手动加进去。CPU 开销方面静态压缩是在首次请求时压一次然后缓存到磁盘后续请求直接读压缩好的产物所以持续开销几乎为零内存和磁盘占用也不大。动态压缩是每个请求现压CPU 开销明显只有在做反向代理转发生成内容时才建议打开。我一般的原则是静态压缩默认开动态压缩看后端压力再定。5. 前后端分离场景让 IIS 顺便当一次反向代理5.1 ARR 装上之后还要把代理开关打开前后端分离的项目前端要调/api/xxx的接口。开发时靠 Vite 的server.proxy或者 Vue CLI 的devServer.proxy转发上线之后这个代理就没了需要 IIS 接手。IIS 做反向代理要装两个东西URL Rewrite前面已经装了和ARRApplication Request Routing。ARR 装完之后默认的代理功能是关闭的很多人卡在这里——规则写得完全正确请求就是不通返回 404 或者 502。开启方式有两种。图形界面是在 IIS 管理器根节点上双击「Application Request Routing Cache」→ 右侧「Server Proxy Settings」→ 勾上「Enable proxy」。命令行更利索%windir%\system32\inetsrv\appcmd set config -section:system.webServer/proxy /enabled:true /commit:apphost注意结尾的/commit:apphost意思是写到applicationHost.config这一层。不加的话只影响当前站点换个站点又得重配一遍。5.2 代理规则为什么必须排在 history 规则前面代理规则长这样放在第 3.4 节那份配置里的位置2处rule nameApi Proxy stopProcessingtrue match url^api/(.*) / action typeRewrite urlhttp://127.0.0.1:8080/{R:1} / serverVariables set nameHTTP_X_FORWARDED_FOR value{REMOTE_ADDR} / set nameHTTP_X_FORWARDED_PROTO valuehttps / set nameHTTP_X_FORWARDED_HOST value{HTTP_HOST} / /serverVariables /rule{R:1}是match里第一个捕获组的内容也就是api/后面的部分。比如请求/api/user/list转发到后端就是http://127.0.0.1:8080/user/list。后端如果本身就是以/api开头的路由那url就写成http://127.0.0.1:8080/api/{R:1}这点取决于后端的实际定义写反了会 404。为什么必须排在 history 兜底规则前面因为兜底规则匹配的是(.*)几乎是全匹配。如果它排在前面/api/user/list会被重写成index.html浏览器拿到一段 HTML 去当 JSON 解析报一个Unexpected token in JSON at position 0的错误。这个报错信息特别有辨识度看到它基本可以直接怀疑重写规则顺序错了。serverVariables那几行是把原始请求信息透传给后端。如果后端需要记录真实客户端 IP没有X-Forwarded-For就只能拿到127.0.0.1。这里有个必须先做的动作服务器变量默认不允许在web.config里设置要先在applicationHost.config里加白名单system.webServer rewrite allowedServerVariables add nameHTTP_X_FORWARDED_FOR / add nameHTTP_X_FORWARDED_PROTO / add nameHTTP_X_FORWARDED_HOST / /allowedServerVariables /rewrite /system.webServer漏了这一步站点会直接 500错误信息里提示某个 server variable 不允许被修改。这个报错挺费解的第一次遇到大概率会往规则语法上想其实是白名单的问题。5.3 502、跨域与真实 IP 丢失的排查方向代理配好之后常见的三个问题我按出现频率排一下。502 Bad Gateway九成是后端没起来或者端口不对。先用curl http://127.0.0.1:8080/在服务器本机测一下通了再回头查 IIS 配置。另外要注意 IIS 站点和127.0.0.1的关系——如果后端监听的是localhost在某些环境下解析到 IPv6 的::1而 IIS 转发到127.0.0.1IPv4就会出现本机 curl 通但 IIS 转发不通的诡异现象。稳妥做法是让后端直接监听0.0.0.0或者明确的 IP别用localhost。跨域报错如果 IIS 已经做了反向代理前端请求的就是同源的/api/xxx理论上不该有跨域。还报跨域的话多半是前端代码里写死了后端的完整地址比如baseURL: http://api.example.com绕过了代理。检查一下 axios 或 fetch 的baseURL配置改成/api这样的相对路径。真实 IP 丢失看第 5.2 节的serverVariables有没有配、白名单有没有加。两层都对了后端才能从请求头里读到X-Forwarded-For。这里有个小细节如果前端还套了一层 CDNX-Forwarded-For会是一个逗号分隔的 IP 链第一个才是真实客户端 IP后端解析时别直接取整个字符串。6. 报错排查链路500.19、0x80005000 与 administration.config6.1 500.19 三种不同成因要分开看500.19 是 IIS 部署里出现频率最高的错误码但它是一类错误不是一种。看到它先看后面的子错误码子错误码含义处理方向0x8007000d配置节无法识别对应模块没装或标签拼写错误0x80070021配置节被锁定用 appcmd unlock config 解锁0x80070005拒绝访问配置文件权限不足0x800700b7重复定义同一节点出现两次比如两次 mimeMap0x8007000d最常见的两个触发场景一是web.config里有rewrite但 URL Rewrite 模块没装二是标签名拼错比如把staticContent写成staticContents。IIS 报错信息里通常会给出行号直接跳到那一行看。0x80070021就是配置节被锁。前面提过staticContent可能被锁实际上httpCompression、customHeaders、handlers这几个也经常被锁。解锁命令的通用写法是appcmd unlock config /section:system.webServer/节点名。要确认某个节当前是锁还是解锁状态可以用appcmd list config /section:system.webServer/节点名查一下输出里会有overrideMode这一项。提示解锁是写到applicationHost.config的属于服务器级改动会影响这台机器上的所有站点。在多人共用的服务器上做这个操作前最好知会一下同事或者至少先做个配置备份见 7.1。6.2 权限报错分层定位目录、应用程序池、配置文件权限问题有个好用的判断方法看错误码里出现的是路径还是配置文件名。错误信息里带具体目录路径比如D:\web\mysite\index.html那是站点目录权限问题解决方案是给目录加上IIS_IUSRS的读取和执行权限icacls D:\web\mysite /grant IIS_IUSRS:(OI)(CI)(RX) /T(OI)(CI)表示权限继承到子文件和子目录/T表示递归应用到现有内容。如果站点需要写文件比如上传目录、日志目录那就单独给那个子目录授权给应用程序池身份icacls D:\web\mysite\uploads /grant IIS AppPool\mysite_pool:(OI)(CI)(M) /T注意这里的账户名格式是IIS AppPool\加上应用程序池名称中间有个空格。写成IISAppPool\或者用池的显示名都会失败。另外这条命令在 PowerShell 里可能需要加引号处理命令行工具icacls的参数用双引号包住比较稳。如果错误信息里出现的是配置文件路径applicationHost.config或administration.config那是另一类问题下一节说。还有一个容易被忽略的点站点目录绝对不要放在用户目录下比如C:\Users\Administrator\Desktop\dist。应用程序池身份对这个路径天生没有权限而且用户目录的继承权限比较复杂加权限也经常加不对。正确做法是把站点放到独立盘符或独立目录比如D:\web\下面路径里不要有中文和空格。6.3 administration.config 报错时的止损与恢复热词里那个「iis报错:执行此操作时出错, 文件名:c:\windows\system32\inetsrv\config\administration.config」是让很多人心态崩掉的一个错误。它的典型表现是打开 IIS 管理器某个功能时弹窗说操作失败指向administration.config这个文件。这个文件保存的是 IIS 管理器的界面配置不是站点的运行配置。所以第一件要放心的事是它坏了不会导致已经跑起来的站点挂掉。它是管理工具的配置影响的是你能不能通过图形界面操作。常见成因有这么几个文件被设成了只读文件的 ACL 权限被改动过比如之前有人为了「解决权限问题」把这整个 config 目录的权限改了文件内容被损坏比如非正常关机导致写入中断还有一种情况是用了非管理员账户登录系统IIS 管理器连不上 ADSI 提供程序报错里带0x80005000。处理顺序我一般是这样先确认是不是权限问题——右键administration.config看属性只读勾要去掉再在安全选项卡里确认SYSTEM和Administrators有完全控制权限。确认是用管理员身份运行 IIS 管理器。错误码0x80005000基本都指向这一类身份验证问题。前两步都没问题就考虑文件损坏。这时候不要手改这个文件用配置备份恢复第 7.1 节的命令或者从同版本的机器上拷一份原始的administration.config覆盖。注意c:\windows\system32\inetsrv\config\这个目录下的文件是整个 IIS 的配置中枢动它之前一定先备份。我见过有人为了「修好权限」直接给整个 config 目录加了Everyone 完全控制结果是配置文件被意外改动站点配置全乱了比原来的问题严重十倍。6.4 上线前必须做的 IIS 配置备份与还原备份这件事最好的时间点是改配置之前而不是出问题之后。IIS 自带备份机制命令很简单:: 创建一个名为 before-vue-deploy 的备份 %windir%\system32\inetsrv\appcmd add backup before-vue-deploy :: 列出所有已有备份 %windir%\system32\inetsrv\appcmd list backup :: 需要回滚时恢复 %windir%\system32\inetsrv\appcmd restore backup before-vue-deploy备份的产物默认落在%windir%\system32\inetsrv\backup下面每个备份是一个以时间戳命名的文件夹里面是配置文件的副本。这个操作耗时通常在一秒以内成本极低但关键时刻能救命。养成习惯每次动站点绑定、应用程序池、applicationHost.config之前先add backup一句。还有一种更细粒度的做法是直接备份整个 config 目录robocopy C:\Windows\System32\inetsrv\config D:\iis-config-backup /MIR /R:1 /W:1/MIR是镜像模式保持两边一致。适合在做大批量变更前留一份完整快照。还原的时候反向执行一次即可但注意还原时最好先把 IIS 服务停掉net stop w3svc避免文件被占用。7. 发布脚本里顺手加的两条命令自动化发布这块只说两个我每次都会加进去的步骤成本很低但省心。第一条是发布完成后清掉dist目录里不该带的东西。本地打包产物里有时候会夹着.map文件source map、.DS_Store、编辑器临时文件。.map文件尤其要注意它包含完整源码暴露出去等于把代码送人。在发布脚本里加一句清理find ./dist -name *.map -type f -delete或者在打包配置里直接关掉 source map 生成Vite 里是build.sourcemap: falseVue CLI 里是productionSourceMap: false。后者更彻底因为文件根本不会生成。第二条是发布后做一次健康检查别等到用户反馈才发现问题。用curl打几个关键路径看状态码curl -o /dev/null -s -w %{http_code}\n https://example.com/ curl -o /dev/null -s -w %{http_code}\n https://example.com/user/list curl -o /dev/null -s -w %{http_code}\n https://example.com/api/health三个都返回 200 才算发布成功。第一条验证默认文档第二条验证重写规则第三条验证反向代理。这三个点覆盖了绝大多数部署故障加起来跑不到两秒。最后再补一个我自己踩过的坑。有一次站点在测试环境好好的上到生产就白屏控制台报一堆资源 404。查了半天发现是生产环境走了一层 CDNCDN 的缓存规则把index.html也缓存了导致新版本发上去用户拿到的还是老的index.html引用的还是老哈希的资源文件而那些老文件已经被新版本覆盖掉了。解决方案是在 CDN 那层也把index.html设成不缓存和 IIS 这边的策略保持一致。这个坑的教训是缓存策略要在整条链路上统一从 IIS 到 CDN 再到浏览器任何一层漏了都可能出问题。排查这类问题的思路也很简单看index.html的响应头里Cache-Control是什么一层层往上找哪一层说「可以缓存」问题就在哪一层。