动态接口返回304是缓存机制与业务逻辑错配所致,应禁用etag/last-modified、配置no-store或no-cache,并在nginx层关闭协商,小体积响应更需警惕假304干扰。

动态接口返回304状态码,本质不是“错误”,而是客户端带着 If-Modified-Since 或 If-None-Match 来验证缓存,服务器确认资源未变后合规返回的响应。但对动态接口(如 /api/user/info)来说,内容本应实时变化,却频繁返回304,说明缓存协商机制与业务逻辑错配——这是典型的“不该缓存却缓存了”问题。调优关键在于**切断无效验证链路**,同时保留合理缓存收益。
明确动态接口是否该参与协商缓存
304只在客户端发起条件请求时才可能出现。而条件请求的前提,是上一次响应中带了 Last-Modified 或 ETag。所以第一步必须确认:
- 后端代码是否主动设置了
ETag或Last-Modified?比如 Express 中用了res.set('ETag', ...)或 Koa 中调用了ctx.set('Last-Modified', ...) - Nginx 等反向代理是否在静态文件逻辑里“误伤”了接口?例如 location 匹配过于宽泛(如
location ~ \.js$却覆盖了/api/v1/data.js这类伪接口路径) - CDN(如 Cloudflare)是否对
/api/路径做了默认缓存策略?即使源站没设头,CDN 也可能自动添加ETag并开启协商
禁用不适用的缓存标识与验证
对纯动态、高时效性接口,应避免让浏览器或中间件发起条件请求。操作上分三层控制:
-
服务端响应头强制禁用协商:返回
Cache-Control: no-store(彻底不缓存)或no-cache, max-age=0(允许缓存但每次强制验证)。注意:no-cache不等于“不验证”,它仍会触发 304;真正要杜绝 304,必须配合移除ETag和Last-Modified -
后端代码清除冗余头:在 Express 中可加中间件统一清理:
app.use((req, res, next) => {<br> res.removeHeader('ETag');<br> res.removeHeader('Last-Modified');<br> next();<br>}); -
Nginx 层拦截条件头:在对应
location /api/块中加入:if_modified_since off;<br>etag off;
这会让 Nginx 忽略客户端发来的If-Modified-Since和If-None-Match,直接跳过 304 判断逻辑
小体积响应更需警惕“假304”干扰调试
接口响应体只有几百字节时,304 的带宽节省意义极小,反而容易掩盖真实问题:
- 前端 Axios 或 Fetch 拿到 304 后,可能静默复用旧响应体,导致 UI 显示陈旧数据,开发者却看不到网络错误
- 日志中大量 304 掩盖了真正的 200 成功率下降、超时或 5xx 异常
- 监控系统若只统计非 2xx/3xx 状态码,会漏掉因缓存导致的数据不一致故障
建议在开发与测试环境,对所有 /api/ 接口强制加 Cache-Control: no-store,确保每次请求都走通链路;生产环境再按需分级(如读接口可缓存 10 秒,写接口一律禁用)。
替代方案:用时间戳或版本号实现可控缓存
若部分动态接口确有缓存价值(如配置中心、地区列表),不建议依赖 ETag 或修改时间,而应改用语义化缓存控制:
- 后端在响应中返回
X-Data-Version: v2026052601和Cache-Control: max-age=300 - 前端下次请求时,在 URL 后拼
?v=v2026052601或带headers: { 'If-None-Match': 'v2026052601' } - 服务端比对版本号决定是否返回 304 —— 这样逻辑清晰、可追踪,且不受文件系统 mtime 或哈希计算开销影响











