HTTP 强缓存与协商缓存:概念、实现及开发落地全解
一、核心概念与执行流程
HTTP 缓存的核心目标是减少不必要的网络请求、提升资源加载速度、降低服务器压力,强缓存和协商缓存是实现 HTTP 缓存的两大核心方式,其中强缓存优先级高于协商缓存,整体执行流程为:
- 浏览器发起资源请求,先检查本地是否有该资源的缓存副本及缓存相关响应头;
- 优先判断强缓存:缓存未过期则直接读取本地缓存(状态码
200 OK (from memory/disk cache)),不与服务器通信; - 强缓存失效则进入协商缓存:浏览器携带缓存标识向服务器发起请求,由服务器判断缓存有效性;
- 协商缓存结果:缓存可用返回
304 Not Modified(不返回资源内容,使用本地缓存);缓存不可用返回200 OK(返回最新资源,更新本地缓存)。
1. 强缓存
浏览器通过本地缓存直接判断资源是否“新鲜”,无需与服务器通信,是性能最优的缓存方式。
- 核心实现头:旧标准
Expires(绝对GMT时间,依赖本地时间,存在篡改漏洞);新标准Cache-Control(相对秒数,不依赖本地时间,主流方案),二者共存时Cache-Control优先级更高。 Cache-Control常用指令:max-age=xxx:核心指令,资源从接收开始的xxx秒内有效;public/private:前者允许浏览器+代理服务器(CDN)缓存,后者仅允许浏览器缓存;no-cache:跳过强缓存,直接进入协商缓存(非“不缓存”);no-store:完全不缓存,每次请求均从服务器获取最新内容;s-maxage:专用于代理服务器,优先级高于max-age。
2. 协商缓存
强缓存失效后,浏览器携带“资源标识”与服务器协商缓存有效性,需少量网络通信(仅传输标识),无大资源传输开销。
核心为两组“响应头+请求头”标识组合,二者共存时**ETag+If-None-Match优先级更高**,服务器优先对比该组标识,辅助对比Last-Modified+If-Modified-Since。
| 组合方式 | 核心依据 | 工作流程 | 优缺点 |
|---|---|---|---|
Last-Modified+If-Modified-Since(旧标准) | 资源最后修改时间 | 服务器返回Last-Modified(资源最后修改GMT时间)→ 浏览器请求携带If-Modified-Since(缓存的修改时间)→ 服务器对比时间判断是否修改 | 优点:实现简单,服务器开销低;缺点:精度仅秒级、存在伪修改(时间变内容不变)、依赖时区时间 |
ETag+If-None-Match(新标准,主流) | 资源内容哈希值 | 服务器返回ETag(资源内容的唯一哈希值)→ 浏览器请求携带If-None-Match(缓存的哈希值)→ 服务器重新计算哈希对比是否一致 | 优点:精度无限制、无伪修改问题、不依赖时间;缺点:服务器需实时计算哈希,增加少量CPU负载 |
3. 核心区别
| 对比维度 | 强缓存 | 协商缓存 |
|---|---|---|
| 网络请求 | 无(直接读本地缓存) | 有(仅传输标识,无大资源) |
| 状态码 | 200 OK (from memory/disk cache) | 304 Not Modified/200 OK |
| 核心依赖 | 单一响应头(Expires/Cache-Control) | 响应头+请求头(标识组合) |
| 性能开销 | 极低(无网络/服务器开销) | 较低(少量网络,服务器轻量计算) |
| 灵活性 | 低(过期时间固定) | 高(服务器动态判断) |
二、实际开发落地实现
实际开发中缓存配置以“静态资源”为核心,动态接口按需配置,且遵循**“强缓存+协商缓存+资源指纹”组合原则**(三者缺一不可),整体落地分为前端构建预处理和服务器/CDN缓存头配置两步。
核心配置原则
- 静态资源(JS/CSS/图片/字体等):强缓存+协商缓存+资源指纹,兼顾性能与缓存更新有效性;
- HTML入口文件:禁用强缓存,仅开启协商缓存,确保能加载最新的哈希静态资源;
- 动态接口:实时数据(用户信息/支付结果)配置
no-store(完全不缓存);非实时数据(文章列表)配置no-cache(仅协商缓存)。
步骤1:前端构建工具 - 生成资源指纹(关键前置)
资源指纹即给静态资源文件名添加内容哈希(如app.abc123.js),资源内容不变则哈希不变,浏览器持续使用强缓存;内容修改则哈希变化,浏览器认为是新资源,直接发起新请求绕过旧缓存,彻底解决“强缓存过期前资源更新无法获取”的问题。
1. Vite(主流,内置支持,配置简单)
执行npm run build自动为静态资源添加内容哈希,可在vite.config.js自定义哈希格式和资源分类:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
assetsInlineLimit: 4096, // 小于4kb资源内联,减少请求
rollupOptions: {
output: {
// 自定义哈希格式:8位哈希,按目录分类
entryFileNames: 'js/[name].[hash:8].js',
chunkFileNames: 'js/[name].[hash:8].js',
assetFileNames: '[ext]/[name].[hash:8].[ext]'
}
}
}
})
2. Webpack 5(传统项目,需插件配合)
通过output配置和MiniCssExtractPlugin插件实现,优先使用[contenthash](基于文件内容计算,优于打包批次的[hash]和代码块的[chunkhash]):
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
path: path.resolve(__dirname, 'dist'),
clean: true // 打包前清空dist
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
],
module: {
rules: [
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
{
test: /\.(png|jpg|gif|svg|woff2)$/,
type: 'asset/resource',
generator: { filename: 'assets/[name].[contenthash:8].[ext]' }
}
]
}
}
步骤2:服务器/CDN - 配置缓存响应头
前端打包后,通过服务器或CDN配置缓存响应头,是实现强缓存和协商缓存的核心步骤,以下为3种主流部署方式的配置方案。
1. Nginx配置(生产环境主流,Web服务器/反向代理)
Nginx自动为静态资源开启ETag和Last-Modified,无需手动配置协商缓存,仅需通过add_header配置强缓存规则,核心配置片段:
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html; # 指向前端dist目录
# 静态资源:强缓存(30天)+ 自动协商缓存
location ~* \.(js|css|png|jpg|gif|svg|woff2|ico)$ {
add_header Cache-Control "public, max-age=2592000"; # 30天=2592000秒
expires 30d; # 兼容旧浏览器Expires
etag on; # 开启ETag(默认开启)
}
# HTML入口:禁用强缓存,仅协商缓存
location ~* \.html$ {
add_header Cache-Control "public, no-cache";
if_modified_since on; # 开启Last-Modified(默认开启)
}
# 动态接口:完全不缓存,转发到后端
location /api/ {
proxy_pass http://backend:8080;
add_header Cache-Control "no-store";
add_header Pragma "no-cache";
add_header Expires "0";
}
}
2. Node.js Express配置(Node后端项目)
通过express.static内置中间件实现,默认开启ETag和Last-Modified,可通过配置项自定义强缓存规则:
const express = require('express')
const path = require('path')
const app = express()
// 配置前端dist目录为静态资源目录
const staticPath = path.resolve(__dirname, 'dist')
app.use(express.static(staticPath, {
maxAge: 30 * 24 * 60 * 60 * 1000, // 强缓存30天,单位毫秒
etag: true, // 开启ETag
lastModified: true, // 开启Last-Modified
// 单独配置HTML:禁用强缓存
setHeaders: (res, filePath) => {
if (path.extname(filePath) === '.html') {
res.setHeader('Cache-Control', 'public, no-cache')
}
}
}))
// 动态接口:完全不缓存
app.get('/api/user', (req, res) => {
res.setHeader('Cache-Control', 'no-store')
res.json({ name: '张三', age: 25 })
})
app.listen(3000, () => console.log('服务运行在http://localhost:3000'))
3. CDN配置(生产环境必备,简化服务器压力)
静态资源通常部署到CDN(阿里云/腾讯云CDN),缓存配置通过控制台可视化操作,无需修改服务器代码,核心步骤(以阿里云CDN为例):
- 进入CDN控制台「域名管理」,选择绑定的前端域名,进入「缓存配置」→「缓存规则配置」;
- 静态资源规则:匹配后缀
js,css,png,jpg,缓存过期时间设为30天,缓存策略选「遵循源站」; - HTML规则:匹配后缀
html,缓存过期时间设为0秒(禁用强缓存); - 高级配置:进入「高级缓存配置」,开启「ETag功能」和「Last-Modified功能」(默认开启);
- 保存配置,等待5-10分钟生效即可。
生产环境最佳实践总结
- 静态资源(JS/CSS/图片):前端构建加内容哈希 + 服务器/CDN配置Cache-Control: public, max-age=2592000 + 开启ETag协商缓存,实现“首次访问后30天内本地缓存,资源更新则哈希变化获取新资源,强缓存过期后协商缓存快速验证”;
- HTML入口文件:配置Cache-Control: public, no-cache,仅开启协商缓存,确保每次能获取最新入口,加载最新哈希静态资源;
- 动态接口:按实时性区分,实时数据(支付/用户信息)配
no-store,非实时数据(文章/榜单)配no-cache; - 部署选型:生产环境优先使用CDN配置缓存,既简化操作,又能通过就近节点提升资源加载速度,大幅减轻源站服务器压力。