页面突然只剩 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 与静态资源对不上的中间状态。

发布顺序也要调整

保留旧资源只能解决一部分问题,发布顺序同样重要。我现在采用下面的流程:

  1. 上传新版本的 CSS、JavaScript、字体和图片。
  2. 检查新静态资源是否能够通过公网地址访问。
  3. 发布引用这些资源的新 HTML。
  4. 等待缓存周期结束后,再清理过期版本。

新资源必须先于 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,旧资源等缓存过期后再清理。改动不复杂,却消除了一个很隐蔽的发布窗口。

用户什么时候刷新页面,我们控制不了。但只要旧页面依赖的文件还在,它就不至于突然"裸奔"。

相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95273 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大3 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师3 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学3 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端