304状态码表示客户端缓存有效,服务器跳过响应体传输;调优关键在于让动态接口可缓存、可验证、可更新,而非增大响应体积。

304状态码不是“响应体积过小”导致的,而是服务器明确告诉客户端“你缓存的版本仍有效,不用重传内容”。它本身不表示错误,也不反映响应体大小——反而说明响应体被跳过了。你遇到的“接口返回304”现象,本质是动态接口未正确参与条件请求协商,或客户端反复用旧缓存发起请求,而服务端被动返回304。调优重点不在“增大响应”,而在让动态资源**可缓存、可验证、可更新**。
确认是否真需支持304:先判断场景合理性
动态接口(如用户实时订单列表、个性化推荐结果)通常不适合长期缓存,强行加ETag或Last-Modified反而引入一致性风险。只有满足以下条件之一,才建议启用304机制:
- 数据变更频率低(例如:城市配置表、商品类目树,每天最多更新1次)
- 客户端高频轮询同一接口(如每5秒查一次状态),且服务端计算成本高
- 移动端弱网环境下,需显著降低重复传输量
为动态接口注入缓存标识:生成可靠ETag或Last-Modified
静态文件由Web服务器自动设置,但动态接口需后端主动提供。关键不是“返回更大内容”,而是返回能代表当前数据状态的指纹:
-
优先用ETag:基于响应主体内容做哈希(如MD5或SHA256),或组合关键字段+更新时间戳(
"user_123_v2_20260526") -
慎用Last-Modified:仅适用于有明确、稳定更新时间的数据源(如数据库记录的
updated_at字段),避免用now()导致每次都不一致 - 务必在首次200响应中带上
ETag或Last-Modified头,否则浏览器不会在下次请求中携带If-None-Match或If-Modified-Since
服务端高效验证并短路响应
收到带条件头的请求后,不要走完整业务逻辑再比对——那样失去304意义。应在入口层快速决策:
- 用Redis缓存ETag值,或预查数据库中关键字段的最新哈希/时间戳
- 若客户端ETag匹配,立即返回
304 Not Modified,不执行后续查询、渲染、序列化 - NestJS示例:在Guard或Interceptor中拦截,比对成功则
res.status(304).end() - OpenResty/Lua方案更极致:在Nginx层解析
If-None-Match,查共享字典命中即返回304,完全不触达后端
控制缓存生命周期:避免“假304”和陈旧数据
光有304不够,必须配合Cache-Control让客户端知道该缓存多久:
- 对低频变动数据,设
Cache-Control: public, max-age=300(5分钟),既允许CDN/浏览器缓存,又限制过期时间 - 禁用
no-cache(它强制每次发条件请求,但不解决根本问题);避免no-store(彻底禁用缓存) - 如需强制刷新,前端可在URL后加时间戳参数(
?t=1748277540)或版本号,绕过缓存直接取新数据

















