登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go 静态文件更新了浏览器还是旧版本:Cache-Control、ETag 和文件名指纹怎么配

来源:17golang原创

时间:2026-07-18 18:53:11 251浏览 收藏

前端刚发完版,运营刷新页面点进去,按钮样式还是上一个版本的。服务器日志没报错,Go服务也确实重启过。这种情况大多不是用户浏览器出问题,而是入口HTML和带版本标识的JS、CSS被统一套了同一条缓存规则:本该长期存本地的资源缓存时间太短,每次都要重下浪费带宽;本该每次校验更新的入口页反而被锁死在本地,拖了好几天都拿不到新内容。

不用让用户强制清缓存,只要按资源类型分别配置Go服务端的缓存响应头,入口HTML走协商校验,带内容指纹的JS、CSS开长期强缓存,就能解决大部分旧版静态文件残留的问题。
要点速览
  • index.html 负责指向所有最新的静态资源,正常要让浏览器每次都找服务端确认有没有更新。
  • 文件名带内容指纹的 /assets/app.3c1f.js 可以设置长期缓存,因为内容一旦改了就会生成全新路径,不会覆盖旧文件。
  • ETag 和 Last-Modified 专门处理同一路径下资源是否变更的协商逻辑,没办法代替文件名指纹的作用。
  • 发版之后别光看页面显示正常,打开浏览器Network面板确认入口校验状态、资源路径变化、响应状态码才稳妥。

浏览器为什么还留着旧版JS和CSS

浏览器请求静态文件的时候,会先读本地的缓存规则。如果当前资源还在缓存有效期里,直接拿本地副本用,完全不发请求;如果设置了需要校验,才会在请求头里带 If-None-Match 或者 If-Modified-Since 找服务端核对。服务端确认内容没改就返回304,浏览器继续用本地旧文件;内容真的变了才会返回新的资源内容。

问题基本都出在入口页身上。index.html 里的 script src 还指向旧的资源路径,哪怕新JS早就传到服务器上了,浏览器也不可能主动猜到新的文件名是什么。反过来如果为了怕出问题,所有大体积资源每次发版都强制全量重下,虽然不会有旧版本问题,但是首屏加载速度和服务器带宽都会白白多出没必要的开销。

资源类型推荐响应头发版时要注意的规则
index.htmlno-cache每次发请求都要重新校验,拿到最新的资源引用地址
/assets/app.3c1f.jspublic, max-age=31536000, immutable路径不变就直接复用本地缓存,内容改动直接生成全新路径
/assets/logo.png短缓存或者加指纹路径根据资源会不会后续被覆盖调整对应策略
/api/profile按业务需求自定义不要和静态文件的缓存规则混在一起

把入口HTML和带指纹的资源分开处理

日常开发里非常好用的一个约定是:构建工具给JS、CSS自动生成内容指纹,比如 app.3c1f.js 这种;每次构建生成的入口HTML,永远只引用当次构建产出的全新资源路径。入口页直接开 no-cache,意思是本地可以存这个HTML文件,但是下次要用的时候必须先找服务端确认有没有更新。带指纹的资源直接给最长时间的缓存,因为新内容不会覆盖已有的旧文件。

不要直接用同名的 app.js 覆盖线上旧文件,等着浏览器刚好愿意拉新资源。中间经过的CDN、反向代理、用户本地的缓存过期时间完全不一样,最后排查的时候会出现一部分用户页面正常、一部分用户还看到旧样式的奇怪情况,很难定位。

Go 静态资源发布中 index.html 重新核验并指向带内容指纹 assets 文件的真实工程证据场景

Go 实现最简静态资源处理器

Go标准库的 http.FileServer 本身就可以直接用来挂载静态文件夹。外层多包一层自定义逻辑,就能按不同的路径设置对应的缓存策略;剩下的条件请求校验逻辑完全交给底层的标准文件服务处理就好。这里的核心不是硬写一个固定的缓存秒数,而是保证路径的可变性和缓存时长完全对应。

func staticHandler(root string) http.Handler {
	files := http.FileServer(http.Dir(root))
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if strings.HasPrefix(r.URL.Path, "/assets/") {
			w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
		} else {
			w.Header().Set("Cache-Control", "no-cache")
		}
		files.ServeHTTP(w, r)
	})
}

这套写法适合所有资源路径都已经加了指纹的项目。如果 /assets/ 文件夹下面还有会被同名覆盖的文件,不要直接套一年的长缓存,要么给这些文件也加上内容指纹,要么单独给它们设短一点的缓存时间。不要上来就给所有资源开immutable属性,文件命名的规则没定好,长缓存只会把问题藏得更深,发版之后半天才暴露出来。

ETag、Last-Modified 和 304 分别解决什么问题

浏览器需要回源校验的时候,ETag用来传递资源的版本标识,Last-Modified用来传递资源最后修改的时间。Go标准库的 ServeContent 已经自动处理了条件请求逻辑;如果请求带了格式正确的ETag,它也会自动根据 If-None-Match 头做校验,确认资源没改动就返回304,不用再把整个资源内容重复返回给浏览器。

但是304只是在同一条路径上做“资源有没有改动”的确认。前端发版要解决的核心问题是“入口页指向哪一套资源”,这个场景必须靠文件名指纹才能实现。把两者的作用区分开之后策略就很清晰了:HTML靠协商缓存保证每次都拿到最新的资源引用,带指纹的资源靠路径变更彻底避开旧缓存的干扰。

浏览器 Network 证据中 Go 服务返回 index.html 重新核验、ETag 条件请求和指纹资源缓存命中的流程

发版之后用Network面板做三项校验

  1. 先勾选浏览器的保留网络日志选项,刷新页面,确认 index.html 没有被错误设置超长缓存。
  2. 查看当前HTML源码里引用的JS、CSS地址,确认已经换成了新的指纹路径,不是还在引用上一版的旧资源地址。
  3. 再刷新一次页面,确认没有改动的指纹资源可以直接从本地缓存读取;之后再发一个新版本,确认全新的指纹路径能正常发起请求拿到新资源。

如果线上还有用户看到旧页面,先收集这三部分信息:入口页的响应头、当前页面源码里的资源URL、受影响用户拿到的资源响应头。只说“清缓存就好了”解决不了根本问题,下一次发版同样的问题还会再次出现。

相关问题

index.html 也设置一年长缓存行不行?

不建议。它决定了浏览器要加载哪一套静态资源,长缓存会让旧的资源引用在用户本地留太久。设置成每次回源校验的方式会稳妥很多。

只开ETag不加文件名指纹可以吗?

确实可以减少同路径下资源的重复传输,但没办法像全新路径那样把不同构建版本的资源完全隔离开。针对主业务JS、CSS这类核心资源,用指纹路径做发版边界会更可控。

304是异常状态吗?

它代表服务端确认当前用户本地存的资源副本还是有效的,浏览器不用再重新下载完整的响应体。排查问题的时候重点看304返回的是不是对应资源,不要看到304就直接当成错误处理。

接了CDN会不会让这套缓存规则失效?

CDN本身也会做一层缓存,所以要保证源站、CDN、浏览器三个环节遵守同一套路径和响应头规则。发版前后都要顺着完整访问链路验证一遍实际效果。

收尾:让缓存策略和文件命名规则同步落地

Go的静态文件服务本身逻辑不复杂,难点是把浏览器缓存当成完整发版链路里的一部分。入口页保持可校验状态,静态资源用内容指纹加长期缓存,发版后再用Network面板核对实际响应效果,旧版本残留的问题就能从随机出现的用户反馈,变成可以复现、可以验证的标准化流程。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>