必须用staticfs配合自定义htmlfilesystem实现fallback逻辑,因static无法拦截404;需在open方法中判断无扩展名路径才fallback至index.html,避免吞掉api或资源请求。

直接用 Gin 服务静态文件就能跑单页应用(SPA),不用非得套 Nginx —— 但必须配对 try_files 等效逻辑,否则刷新 404。
为什么 StaticFS + HTMLFileSystem 是刚需
Gin 的 Static 方法只映射路径前缀,不处理前端路由 fallback。比如访问 /dashboard 时,浏览器发请求到后端,但该路径下没有真实文件,Gin 默认返回 404。
解决办法是用 StaticFS 配合自定义 http.FileSystem,让所有未命中文件的请求都 fallback 到 index.html:
-
http.Dir("./dist", false)创建只读文件系统,第二个参数设为false禁用目录列表(安全) - 包装一层
HTMLFileSystem,重写Open方法:当os.IsNotExist(err)时,返回index.html的文件句柄 - 别用
Static,它无法拦截 404;StaticFS才能接管整个文件查找流程
gin.Engine.ServeHTTP 不能绕过,但可以劫持
你不能在中间件里简单地 c.Request.URL.Path = "/index.html" 然后 c.Next() —— 这会破坏路由匹配,且 StaticFS 已经走完文件查找逻辑。
正确做法是在 HTMLFileSystem.Open 里做判断:
func (fs HTMLFileSystem) Open(name string) (http.File, error) {
f, err := fs.fs.Open(name)
if os.IsNotExist(err) {
// 只对非静态资源路径 fallback
if strings.HasPrefix(name, "/") && !strings.Contains(name, ".") {
return fs.fs.Open("index.html")
}
return nil, err
}
return f, err
}
- 检查
name是否无扩展名(典型 SPA 路由特征),再 fallback,避免把/favicon.ico也塞进index.html - 不要无条件 fallback 所有 404,否则图片、API 请求也会被吞掉
- 注意
index.html必须在文件系统根目录(即./dist/index.html),路径要和http.Dir初始化位置一致
开发环境要不要开 gzip?
本地部署一般不开。Gin 默认不启用响应压缩,gzip 是 Nginx 层的事;自己手写压缩中间件容易出错,还增加 CPU 开销。
- 构建时用 Webpack/Vite 开启
build.rollupOptions.output.gzip或插件生成.gz文件更稳妥 - 如果真要 Gin 压缩,得引入第三方库如
github.com/gin-contrib/gzip,但本地调试没必要 - 注意:gzip 中间件对
text/html默认不压缩,需显式加gzip.MIME("text/html")
最易被忽略的是 fallback 路径判断粒度 —— 不是“所有 404 都给 index.html”,而是“仅对客户端路由路径 fallback”。漏掉这个,API 接口或资源请求就会静默返回 HTML,调试时很难定位。











