Service Worker 是 HTML 离线运行的唯一可靠基础,必须在 HTTPS 或 localhost 下注册激活、拦截 fetch 事件并用 Cache API 显式缓存资源;manifest.json 已被淘汰,file:// 协议不支持 Service Worker。

前端资源离线缓存不是加个 Cache-Control 就完事
浏览器本地缓存(HTTP 缓存)和前端构建产物的离线能力(如 Service Worker、manifest.json)是两套逻辑。Gin 本身不生成或管理 Service Worker,它只负责把 index.html 和静态文件(/static/js/app.js 等)正确吐出去。如果前端用了 PWA 或自建离线包,Gin 必须确保这些文件能被稳定访问,否则离线包加载失败、缓存失效、白屏。
常见错误现象:Service Worker 注册成功,但 self.skipWaiting() 后 fetch 时返回 404;或者 workbox.precacheAndRoute([]) 列表里路径对了,Gin 却返回 index.html 而不是对应 JS 文件——说明 r.Static() 挂载错位或 NoRoute 拦截太早。
-
r.Static("/static", "./dist/static")必须在r.NoRoute()之前注册,否则所有/static/xxx请求会被兜底成index.html - 前端构建输出的
manifest.json、sw.js也要被r.Static()显式挂载,例如:r.Static("/sw.js", "./dist/sw.js")或r.StaticFile("/manifest.json", "./dist/manifest.json") - 避免用
r.StaticFS("/static", http.Dir("./dist/static"))替代r.Static()—— 前者不自动设置Content-Type,某些 MIME 类型(如.webp、.woff2)会 fallback 成text/plain,导致浏览器拒绝加载
如何让 index.html 在离线时仍可被 Service Worker 正确读取
Service Worker 的 cache.addAll() 或 Workbox 的 precache 都依赖首次加载时能拿到干净的 index.html。但 Gin 默认的 NoRoute 返回 c.File("./dist/index.html") 是动态响应,不带 ETag 或强缓存头,容易被浏览器判定为“不可缓存”,导致离线时 SW 找不到该文件。
解决办法不是关掉缓存,而是让 index.html 像其他静态资源一样走 StaticFile 流程:
立即学习“go语言免费学习笔记(深入)”;
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
- 用
r.StaticFile("/index.html", "./dist/index.html")替代r.GET("/", ...)或NoRoute中的c.File() - 确保
./dist/index.html文件存在且路径相对于main.go正确;可用os.Stat("./dist/index.html")启动时校验 - 若需支持多级路由(如
/user/profile),仍保留NoRoute,但必须把它放在StaticFile之后,且内部只返回index.html,不调用c.File()(避免重复设置 header)——改用c.Header("Cache-Control", "public, max-age=31536000")+c.Header("Content-Type", "text/html; charset=utf-8")+c.String(200, string(htmlBytes))
Static() 路径参数写错会导致整个离线链路断裂
错误示例:r.Static("/static", "./static"),而真实路径是 ./dist/static。后果不只是 JS 404,还会让 Service Worker 的 precache 列表全部 miss,进而触发 fallback 策略失败,最终用户刷新页面就白屏。
验证方式非常直接:
- 启动 Gin 服务后,手动 curl
http://localhost:8080/static/js/app.js,看是否返回原始文件内容(不是 HTML) - 检查响应头是否有
Content-Type: application/javascript;没有就说明 MIME 推断失败,大概率是路径不对或文件不存在 - Windows 下注意路径分隔符:Go 的
filepath.Abs()返回,但r.Static()内部用的是path.Clean(),所以传入"./dist\static"反而可能出错,统一用"./dist/static"即可
生产环境必须关闭 Cache-Control: no-cache 并加固 MIME
Gin 默认开发模式下,Static() 返回的资源带 Cache-Control: no-cache,这会阻止 Service Worker 缓存它们。离线包一旦没缓存到关键 JS/CSS,就彻底不可用。
上线前必须显式覆盖:
- 在
r.Static()之后加中间件,或直接用r.StaticFS()自定义http.FileSystem实现;更简单的是用r.Static()+ 全局中间件: router.Use(func(c *gin.Context) { if strings.HasPrefix(c.Request.URL.Path, "/static/") { c.Header("Cache-Control", "public, max-age=31536000") } c.Next() })- MIME 不全会导致字体、图片等资源加载失败:Gin 内置 MIME 表不包含
.woff2、.avif等新格式,需手动注册:mime.AddExtensionType(".woff2", "font/woff2")
index.html 或某个 .js 文件悄悄返回了 404 或错误 Content-Type——而这个错误在 DevTools Network 面板里一闪而过,没人盯着看。

















