IIS部署Node.js+Express+Vite-React应用时静态资源加载404问题
我最近碰到了和你一模一样的情况:用Vite+React写前端,Node.js+Express搭后端,本地跑完全正常,但部署到IIS作为默认网站的子站点(路径localhost/chat)时,index.html能正常加载,可所有.js、.css静态资源全报404。折腾了好一阵终于搞定,把问题根源和解决办法分享给你:
我的问题场景和你完全一致
- 前端用Vite+React,已经执行
npm run build生成frontend/dist生产包 - 后端用Express,通过
express.static()托管frontend/dist静态文件 - 部署到IIS子站点
chat,用了iisnode和URL Rewrite插件 - 现象:
localhost/chat能返回index.html,但静态资源(比如http://localhost/assets/index-qBCe9vKe.js)全是404
问题到底出在哪?
- Vite默认绝对路径踩坑:Vite打包后默认生成的静态资源引用是根路径绝对地址(比如
/assets/xxx.js),但部署在子站点chat下时,浏览器会去localhost/assets/xxx.js找资源,可实际资源明明在localhost/chat/assets/xxx.js,这是最核心的问题。 - IIS重写规则顺序乱了:原来的规则没有优先匹配静态文件,导致静态资源被错误转发到server.js处理,自然找不到。
- 子站点上下文没对齐:Express和IIS的路径映射没有适配子站点的上下文路径,导致资源寻址错位。
分步解决办法
1. 先改Vite的基础路径配置
在Vite项目的vite.config.js里设置base为你的子站点路径(结尾必须带斜杠),这样打包后的静态资源引用会自动加上子站点前缀:
// vite.config.js import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], base: '/chat/' // 替换成你的IIS子站点路径,比如我的是/chat/ })
改完后重新执行npm run build,生成新的frontend/dist包。
2. 优化Express的静态资源托管(可选但更稳妥)
你的server.js这段代码本身没问题,但可以加上子站点上下文兼容,后续改子站点路径也更灵活:
// server.js 中静态资源和兜底路由部分 const subPath = '/chat'; // 子站点路径 app.use(subPath, express.static(path.join(__dirname, "/frontend/dist"))); // 兜底路由也要适配子站点 app.get(`${subPath}/*`, (req, res) => { res.sendFile(path.join(__dirname, "/frontend", "dist", "index.html")); })
如果IIS重写规则配置正确,这一步可以跳过,但加上会更鲁棒。
3. 修正IIS的web.config重写规则
原来的规则顺序和匹配逻辑完全反了,应该优先让静态文件直接返回,再处理API请求,最后处理React路由兜底。修改后的web.config如下:
<configuration> <system.webServer> <handlers> <add name="iisnode" path="server.js" verb="*" modules="iisnode" /> </handlers> <rewrite> <rules> <!-- 1. 优先匹配静态资源,直接返回,不转发到server.js --> <rule name="Serve Static Assets" stopProcessing="true"> <match url="^assets/(.*)$" /> <action type="None" /> </rule> <!-- 2. 处理所有API请求,转发到server.js --> <rule name="Node.js API Routes"> <match url="^api/(.*)$" /> <action type="Rewrite" url="server.js" /> </rule> <!-- 3. React路由兜底:非文件非API的请求,返回index.html --> <rule name="React Router Fallback" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> </conditions> <action type="Rewrite" url="/chat/frontend/dist/index.html" /> </rule> </rules> </rewrite> <security> <requestFiltering> <hiddenSegments> <add segment="node_modules" /> <add segment="iisnode" /> </hiddenSegments> </requestFiltering> </security> </system.webServer> </configuration>
⚠️ 重点注意:React Router Fallback规则里的action url要填子站点下index.html的完整路径,比如我的子站点chat下index.html在frontend/dist,所以路径是/chat/frontend/dist/index.html。
4. 最后检查IIS子站点配置
- 确保子站点的物理路径直接指向你的应用根目录(就是包含
server.js和frontend文件夹的那个目录) - 给IIS_IUSRS和IUSR用户分配该目录的读取权限
- 重启IIS子站点或者整个IIS服务
验证效果
重新部署修改后的代码和静态包,访问localhost/chat,打开浏览器控制台你会发现,静态资源的请求路径变成了localhost/chat/assets/xxx.js,不再是根路径,404问题直接解决!
备注:内容来源于stack exchange,提问作者Yash Gawli

