AI 修改了网页代码,为什么刷新后还是旧页面?从浏览器缓存到静态资源版本管理
前言
现在使用 AI 编程助手开发网页已经非常方便。
只需要描述需求,AI 就可以修改 HTML、CSS 和 JavaScript,运行测试并提交代码。
但在实际开发过程中,有时会遇到一个奇怪的问题:
AI 明明已经修改了代码,刷新浏览器后,页面却还是旧版本。
例如,我们让 AI 修改按钮颜色。
AI 确认 style.css 已经更新,Git 仓库也有新的提交。
可打开网页,按钮仍然保持原来的颜色。
这时候,很容易误以为 AI 没有正确修改代码,于是要求它再改一次。
但问题可能根本不在源代码,而在浏览器缓存、服务器缓存或者部署流程中。
本文从一个简单的静态网页项目出发,介绍 HTTP 缓存的基本原理,以及如何通过资源版本管理解决网页更新不及时的问题。
一、为什么代码更新了,页面却没有变化?
假设我们有一个最简单的网页项目:
mini-app/
├── index.html
├── style.css
└── script.js
其中,index.html 引用了两个静态资源:
<link rel="stylesheet" href="style.css">
<script src="script.js" defer></script>
第一次访问网页时,浏览器需要下载 HTML、CSS 和 JavaScript。
为了减少重复下载,浏览器可能缓存这些文件。
当用户再次访问时,浏览器会根据缓存策略决定:
-
直接使用本地缓存。
-
向服务器确认文件是否变化。
-
重新下载最新文件。
因此,即使服务器上的 style.css 已经更新,浏览器也可能暂时使用此前缓存的版本。
需要注意的是,网页没有更新不一定都是缓存问题。
还可能是服务部署到了错误目录、代码尚未重新部署,或者访问的根本不是当前项目的服务器。
所以排查时不能只凭现象判断原因。
二、理解 HTTP 缓存的 Cache-Control
HTTP 缓存主要通过响应头控制。
其中一个常见字段是:
Cache-Control: max-age=3600
这表示在符合相关缓存规则的情况下,响应可以在 3600 秒内被认为是新鲜的。
也就是一小时。
如果这段时间内服务器更新了文件,客户端不一定立即重新获取最新内容。
另一个常见配置是:
Cache-Control: no-cache
这里有一个容易误解的地方:
no-cache 并不等于完全不缓存。
它表示缓存内容在复用前需要进行有效性验证。
如果希望响应不被缓存存储,可以使用:
Cache-Control: no-store
三种配置的区别如下:
| 配置 | 主要含义 |
|---|---|
max-age=3600 |
在规定时间内可直接复用新鲜缓存 |
no-cache |
允许存储,但复用前需要验证 |
no-store |
不允许缓存存储该响应 |
对于频繁修改的网页入口文件,no-cache 往往是比较实用的选择。
而对于内容不再变化、文件名包含版本标识的静态资源,则可以考虑较长的缓存时间。
三、为什么强制刷新有时能解决问题?
当网页没有显示最新内容时,开发者经常会尝试:
Windows 浏览器中按 Ctrl + F5,或通过开发者工具禁用缓存后重新加载。
这样做通常可以帮助判断问题是否与浏览器缓存有关。
但需要注意,强制刷新并不能解决所有问题。
例如:
如果服务器部署的仍然是旧文件,那么重新下载多少次,得到的都是旧版本。
如果 CDN 仍然返回过期内容,也需要检查 CDN 对应的缓存与刷新机制。
如果页面注册了 Service Worker,还需要检查它是否拦截请求并返回本地缓存资源。
因此,强制刷新适合作为诊断手段,而不应该成为正式产品的更新方案。
真正可靠的网页部署,需要合理设计缓存策略。
四、最实用的方法:给静态资源增加版本标识
假设我们在网页中引用:
<link rel="stylesheet" href="style.css">
服务器更新样式后,资源地址没有变化。
浏览器可能继续使用此前缓存的内容。
一种简单的处理方式是增加版本参数:
<link rel="stylesheet" href="style.css?v=2">
JavaScript 同样可以这样处理:
<script src="script.js?v=2" defer></script>
当版本变化时,请求 URL 也发生变化,浏览器通常会将其视为不同的缓存资源。
不过,查询参数版本号需要开发者自行维护,而且某些 CDN 缓存策略可能对查询参数做特殊处理。
更推荐用于正式部署的方式是:使用包含内容哈希的文件名。
例如:
style.a81c9f.css
script.72ab41.js
HTML 引用:
<link rel="stylesheet" href="style.a81c9f.css">
<script src="script.72ab41.js" defer></script>
当 CSS 内容发生变化时,构建流程生成新的文件名。
旧资源与新资源拥有不同的地址,浏览器就不会因为 URL 相同而错误复用旧文件。
这里的哈希名称仅用于说明原理,并不是实际计算结果。
五、为什么 HTML 和静态资源应该采用不同缓存策略?
对于普通静态网页,一种常见的部署方案是:
HTML 入口文件:较短缓存或每次复用前验证。
带内容哈希的 CSS、JavaScript:较长时间缓存。
原因很简单。
HTML 负责引用最新版本的静态资源。
如果 HTML 能及时更新,浏览器就能发现新的 CSS 和 JavaScript 地址。
而带哈希文件名的资源,在内容不变的情况下可以长期复用。
例如:
用户打开网页
↓
重新验证 index.html
↓
发现新的资源引用
↓
下载 script.newhash.js
↓
加载最新网页功能
对于已经发布且内容不可变的哈希资源,可以考虑:
Cache-Control: public, max-age=31536000, immutable
这里的缓存时间是一年。
但这种策略有一个重要前提:
文件名对应的内容必须保持不变。
如果服务器在原 URL 下修改文件内容,却继续使用 immutable 长缓存,就可能造成严重的版本更新问题。
因此,长期缓存与内容哈希命名应该配套使用。
六、动手实践:使用 Node.js 查看不同缓存策略
下面使用 Node.js 标准库创建一个简单的 HTTP 服务,演示 HTML 与静态资源采用不同的缓存响应头。
先准备三个文件:
cache-demo/
├── index.html
├── style.v2.css
└── server.js
index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>缓存测试</title>
<link rel="stylesheet" href="/style.v2.css">
</head>
<body>
<h1>网页缓存测试 V2</h1>
<p>观察 Network 面板中的响应头。</p>
</body>
</html>
style.v2.css:
body {
font-family: system-ui, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
h1 {
color: #2563eb;
}
接下来编写 server.js:
const http = require("node:http");
const fs = require("node:fs/promises");
const path = require("node:path");
const files = {
"/": {
name: "index.html",
type: "text/html; charset=utf-8",
cache: "no-cache"
},
"/style.v2.css": {
name: "style.v2.css",
type: "text/css; charset=utf-8",
cache: "public, max-age=31536000, immutable"
}
};
const server = http.createServer(async (req, res) => {
const config = files[req.url];
if (!config) {
res.writeHead(404);
res.end("Not Found");
return;
}
try {
const content = await fs.readFile(
path.join(__dirname, config.name)
);
res.writeHead(200, {
"Content-Type": config.type,
"Cache-Control": config.cache
});
res.end(content);
} catch {
res.writeHead(500);
res.end("Internal Server Error");
}
});
server.listen(3000, "127.0.0.1", () => {
console.log("http://127.0.0.1:3000");
});
运行:
node server.js
然后访问:
http://127.0.0.1:3000
打开浏览器开发者工具的 Network 面板,就可以查看文件的响应头。
也可以在终端执行:
curl -I http://127.0.0.1:3000/
以及:
curl -I http://127.0.0.1:3000/style.v2.css
前一个请求应该返回 no-cache。
后一个请求应该返回较长时间的缓存配置。
需要说明:这个示例主要演示响应头策略。style.v2.css 是人为指定的版本文件名,并非通过内容哈希自动生成;实际项目更新内容时必须同步更换 URL,才能安全采用长期不可变缓存。
七、如何区分浏览器缓存与部署错误?
当网页没有更新时,可以按照下面的顺序排查。
第一步:确认本地代码已经更新。
检查目标文件内容,确保修改确实已经保存。
第二步:确认远程代码正确。
如果通过 GitCode 部署,检查目标分支是否包含最新提交。
但需要注意,GitCode 推送成功不等于部署服务器已经同步最新代码。
第三步:检查服务器实际返回内容。
使用 curl 或浏览器开发者工具,确认服务器返回的是新文件还是旧文件。
第四步:查看响应头。
重点检查 Cache-Control、ETag、Last-Modified 等字段。
第五步:检查浏览器缓存和 CDN。
如果服务器源站已经更新,但用户获取的内容仍然过期,就需要继续检查中间缓存层。
第六步:确认 Service Worker。
如果网站使用过 PWA 或离线缓存机制,需要检查 Service Worker 的缓存逻辑是否仍然返回旧资源。
这种分层排查比反复要求 AI 修改网页代码更有效。
八、ETag 和 304 又是什么?
除了 Cache-Control,HTTP 还提供了用于缓存验证的机制。
其中一个常见字段是 ETag。
例如,服务器响应:
ETag: "version-123"
客户端后续可以携带:
If-None-Match: "version-123"
如果服务器判断资源没有变化,就可以返回:
HTTP/1.1 304 Not Modified
这表示客户端可以继续使用已经保存的响应内容。
如果资源发生变化,服务器则返回新的内容。
ETag 的优势在于:
即使需要向服务器验证资源是否变化,也不一定需要重新传输完整文件。
不过,上面的 Node.js 示例没有实现 ETag 生成和条件请求处理,实际生产环境中通常可以交给成熟的 Web 服务器或部署平台处理。
九、AI 自动部署网页时,怎样避免旧版本问题?
我认为可以在 AI Agent 的开发发布提示词中增加一条明确的验收规则:
完成代码提交与部署后,必须确认部署环境实际返回的 HTML 和静态资源与预期版本一致。不要仅凭 Git Push 成功就认定网页已经更新;如果发现旧版本,优先检查缓存响应头、部署目录、实际分支及 CDN,而不是反复修改源代码。
对于简单的静态网页工具,还可以要求:
-
使用明确的项目目录和部署分支。
-
记录本次发布的 Git 提交 ID。
-
验证网页实际返回的版本。
-
为更新频繁的资源采用合理缓存策略。
-
不把临时开发预览链接当成永久部署地址。
这样能够降低 AI 自动发布过程中“代码已经改了,页面却没有变化”的概率。
十、总结
浏览器缓存是提高网页加载性能的重要机制,但如果缺乏合理的版本管理,也可能让用户长时间看到旧内容。
通过本文的介绍,可以总结出几个关键原则:
-
源代码更新不代表用户立即获得新版本。
-
Cache-Control 决定了缓存存储与复用的基本策略。
-
no-cache不等于no-store。 -
HTML 和带哈希的静态资源适合采用不同的缓存策略。
-
资源版本号或内容哈希可以帮助解决旧文件复用问题。
-
排查网页未更新时,应区分代码、部署、浏览器缓存与 CDN 问题。
对于 AI 编程助手来说,真正可靠的部署过程不能停留在“文件已经修改”“代码已经推送”的阶段。
还需要检查用户实际访问的网页是否已经加载正确版本。
好的自动化部署,不只是把新代码上传成功,更要确保用户真正看到的是新版本。
- 点赞
- 收藏
- 关注作者
评论(0)