Skip to content

Node.js HTTP 缓存

HTTP 缓存分两大类:强缓存协商缓存,通过 HTTP 响应头控制,目的是提升性能。

强缓存

不向服务器发请求,直接从浏览器缓存读取。

存储位置

类型位置特点
memory cache浏览器内存快但容量小,刷新页面可见
disk cache硬盘容量大但读取较慢

Expires(HTTP 1.0,已过时)

js
res.setHeader('Expires', new Date(Date.now() + 60000).toUTCString())

缺点:依赖本地时间,客户端时间不准会导致缓存失效。

Cache-Control(HTTP 1.1,主流)

js
// 禁止缓存
res.setHeader('Cache-Control', 'no-cache')

// 允许缓存 60 秒(推荐)
res.setHeader('Cache-Control', 'max-age=60')

max-age 是相对时间(秒),比 Expires 更可靠。

协商缓存

强缓存失效后,浏览器会发请求给服务器验证,服务器判断资源是否变化。

Last-Modified / If-Modified-Since

js
// 服务端:第一次响应带上最后修改时间
res.setHeader('Last-Modified', stats.mtime.toUTCString())

// 浏览器下次自动带 If-Modified-Since
// 服务端对比:
const ifModifiedSince = req.headers['if-modified-since']
if (ifModifiedSince && new Date(ifModifiedSince) >= stats.mtime) {
  res.statusCode = 304     // 未修改,继续用缓存
  return res.end()
}
// 否则返回新内容...(状态码 200)

ETag / If-None-Match(更精确)

js
import crypto from 'node:crypto'

const content = fs.readFileSync(filePath)
const etag = crypto.createHash('md5').update(content).digest('hex')

res.setHeader('ETag', etag)

// 浏览器下次自动带 If-None-Match
// 服务端对比:
if (req.headers['if-none-match'] === etag) {
  res.statusCode = 304    // 未修改
  return res.end()
}

强缓存 vs 协商缓存

强缓存协商缓存
请求服务器不发请求发请求验证
状态码200 (from cache)304 (Not Modified)
头部Cache-Control / ExpiresLast-Modified / ETag
适用场景不常变的静态资源(JS/CSS/图片)可能变化但希望减少传输