304 not modified不是错误,而是服务端确认客户端缓存有效后跳过响应体传输的协商缓存机制;它依赖首次响应携带last-modified或etag及cache-control,由服务器比对条件请求头后直接终止处理流程,仅返回状态码和极简头部,浏览器随即复用本地缓存。

304 Not Modified 不是错误,而是服务端告诉浏览器“你本地存的那份资源还新鲜,直接用,别重下”。它没有响应体,正是因为这个决定本身——省掉传输、跳过读取、不构造内容——就是加速的核心逻辑。
304 的本质是“跳过”而非“返回”
当浏览器发起带 If-Modified-Since 或 If-None-Match 的请求时,Nginx(或其它服务器)并不加载文件内容,也不组装 HTML/CSS/JS 数据。它只做两件事:
- 检查目标文件的修改时间是否 ≤ 请求头中的时间戳(Last-Modified 场景)
- 或比对 ETag 值是否完全一致(ETag 场景)
一旦条件成立,Nginx 立即终止后续处理流程:不打开文件、不读磁盘、不序列化内容、不计算 Content-Length。响应仅含状态行和极简头部(如 Date、Server),响应体严格为 0 字节。
为什么没 Body 却能正常渲染?
浏览器收到 304 后,不会等待新内容,而是立刻启用本地缓存副本。这个过程发生在内存中,无需网络 I/O 或解码等待:
- HTML 文件缓存后,DOM 构建可立即复用已解析树
- CSS 文件缓存后,样式规则直接注入渲染引擎
- JS 文件缓存后,脚本字节码保持可执行状态,跳过下载与编译前置步骤
真正耗时的环节——网络传输、磁盘读取、文本解析、语法分析——全部被绕过。实测显示,单次 304 可节省约 50–300 KB 传输量,同时减少 80–200 ms 渲染延迟。
触发 304 的硬性前提
不是所有请求都能走到 304。必须同时满足:
- 首次响应携带 Last-Modified 或 ETag(缺一不可)
- 响应头中设置有效的 Cache-Control: max-age=… 或 Expires
- 缓存过期后,浏览器自动带上对应条件头发起验证请求
- 服务端未主动清除或覆盖 Last-Modified/ETag(例如 Nginx 中误写 add_header Last-Modified "")
若 HTML 文件改了但仍是 304,大概率是文件系统 mtime 未更新,或 ETag 没随内容重新生成。
前端无法“制造”304,但可以保护链路
304 完全由服务端决策,前端唯一能做的,是避免破坏协商机制:
- 开发调试时慎用 DevTools 的 “Disable cache”,它会禁用所有条件请求
- 不要在 fetch 请求中手动删除 If-None-Match —— 浏览器加的,删了就变 200
- SPA 应用中,HTML 通常只加载一次;真正依赖 304 的是 .js、.css、图片等静态资源











