文章目录
- [页面突然只剩 DOM?一次静态资源版本错配排查](#页面突然只剩 DOM?一次静态资源版本错配排查)
页面突然只剩 DOM?一次静态资源版本错配排查
前几天,我给项目发布了一个新版本。部署过程没有报错,自己访问也很正常,原以为这次上线已经结束。
没过多久,有用户发来一张截图:页面里的文字和按钮都还在,但布局、颜色和交互全没了。整个网站像是浏览器关闭了 CSS,只剩下一堆原始的 DOM 元素。

故障示意图:CSS 和 JavaScript 静态资源请求返回 404,页面因此只剩下未经样式修饰的 DOM 元素。
页面还在,样式为什么没了
遇到这种情况,我的第一反应是打开浏览器开发者工具。
在 Network 面板中过滤 CSS 和 JS 请求,很快就能看到几个红色的 404。控制台里通常也会出现类似错误:
text
Failed to load resource: the server responded with a status of 404
ChunkLoadError: Loading chunk xxx failed
页面的 HTML 已经成功返回,所以文字、按钮和输入框还在。负责样式和交互的静态资源没有加载成功,页面自然就只剩下最原始的样子。
真正的问题出在版本发布与缓存之间。
一次版本错配是怎么发生的
前端项目构建后,文件名通常会带上内容哈希:
text
app.a81f3c.css
vendor.71bd29.js
main.c8e602.js
只要文件内容发生变化,哈希也会跟着变化。下一次构建可能得到另一组文件:
text
app.591de8.css
vendor.71bd29.js
main.e320a1.js
这套机制本身没有问题。哈希能让浏览器长期缓存静态资源,又能在文件发生变化时请求新地址。
麻烦出在旧版本被过早删除。
用户访问网站后,HTML 可能缓存在浏览器、本地代理或 CDN 节点中。发布新版本时,如果部署脚本直接清空旧目录,再上传本次构建产物,旧文件 app.a81f3c.css 就不存在了。
这时,不同用户拿到的内容可能并不一致:
text
浏览器或 CDN 中的旧 HTML
↓
请求 app.a81f3c.css
↓
服务器只保留 app.591de8.css
↓
返回 404
↓
页面只剩 DOM 元素
换句话说,缓存没有失效并不可怕。真正导致故障的是:旧 HTML 仍然可以访问,它依赖的旧静态资源却已经被删除。
这也解释了为什么问题经常很难复现。刚发布版本的开发者可能拿到了最新 HTML,页面一切正常;某些用户命中了旧缓存,看到的却是一片没有样式的页面。
我的处理方法:让新旧版本共存
我的解决思路是保留一段时间内的静态资源版本,而不是每次发布都覆盖同一个目录。
静态资源可以放在 OSS 的版本目录中,也可以保存在前端服务器的挂载卷里。例如:
text
/releases
├── 2026-08-12
│ └── assets
│ ├── app.a81f3c.css
│ └── main.c8e602.js
└── 2026-08-13
└── assets
├── app.591de8.css
└── main.e320a1.js
发布后,新旧资源都能通过原来的 URL 访问:
text
https://static.example.com/releases/2026-08-12/assets/app.a81f3c.css
https://static.example.com/releases/2026-08-13/assets/app.591de8.css
用户命中旧 HTML 时,继续加载旧版本资源;缓存过期并重新获取 HTML 后,再自然切换到新版本。整个过程不需要强制用户刷新,也不会出现 HTML 与静态资源对不上的中间状态。
发布顺序也要调整
保留旧资源只能解决一部分问题,发布顺序同样重要。我现在采用下面的流程:
- 上传新版本的 CSS、JavaScript、字体和图片。
- 检查新静态资源是否能够通过公网地址访问。
- 发布引用这些资源的新 HTML。
- 等待缓存周期结束后,再清理过期版本。
新资源必须先于 HTML 上线。否则用户可能已经拿到新 HTML,服务器上却还没有对应的 CSS 和 JavaScript,结果依然是 404。
旧版本也不需要永久保存。保留时间可以按最长缓存时间计算,再增加一点缓冲。例如 CDN 最长缓存 7 天,可以将旧资源保留 10 至 14 天。具体时间取决于项目的发布频率、回滚要求和存储成本。
缓存策略需要分开设置
HTML 和带哈希的静态资源不适合使用同一套缓存策略。
带哈希的文件内容不会发生变化,可以长期缓存:
nginx
location ~* \.(css|js|png|jpg|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}
HTML 决定当前页面引用哪个版本的资源,缓存时间应该短一些,也可以要求客户端重新验证:
nginx
location = /index.html {
add_header Cache-Control "no-cache";
}
如果项目是单页应用,还要检查路由回退后的 HTML 是否带有正确的缓存响应头。项目使用 Service Worker 时,也要确认它有没有继续返回更早的 HTML。
怎样快速确认是同一个问题
下次再遇到"页面只有文字,没有样式",可以直接检查下面几项:
- Network 面板中的 CSS、JavaScript 是否返回
404。 - 请求地址里的哈希文件是否存在于 OSS 或服务器。
- 当前 HTML 引用的是哪个发布版本。
- CDN、浏览器或 Service Worker 是否仍在缓存旧 HTML。
如果旧 HTML 请求的资源已经被部署脚本删除,原因基本就找到了。
写在最后
这次问题表面上像是 CSS 突然失效,实际是发布系统没有照顾仍在缓存中的旧页面。
我后来把部署方式改成了版本化存储:先上传新资源,再发布 HTML,旧资源等缓存过期后再清理。改动不复杂,却消除了一个很隐蔽的发布窗口。
用户什么时候刷新页面,我们控制不了。但只要旧页面依赖的文件还在,它就不至于突然"裸奔"。