Web 应用缓存与部署优化指南
目录
HTTP 缓存机制
强缓存
浏览器直接使用本地缓存,不与服务器通信。
控制头部:
http
Cache-Control: max-age=31536000
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: must-revalidate
Expires: Wed, 12 Jun 2026 12:00:00 GMT
特点:
- 命中强缓存时不发送请求
- 通过 DevTools 可见
200 (from disk cache)
或200 (from memory cache)
- 适用于带版本号的静态资源
协商缓存
浏览器与服务器协商验证资源是否更新。
控制头部:
http
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 12 Jun 2024 12:00:00 GMT
If-Modified-Since: Wed, 12 Jun 2024 12:00:00 GMT
特点:
- 每次都发送请求验证
- 资源未变时返回 304 状态码
- 适用于 HTML 等经常变化的资源
浏览器缓存体系
缓存位置
- Service Worker Cache
- Memory Cache
- Disk Cache
- Push Cache (HTTP/2)
缓存优先级
- Service Worker 的 Cache Storage
- Memory Cache(内存缓存)
- Disk Cache(磁盘缓存)
- 网络请求
Cache Storage API
基本概念
Cache Storage API 是现代浏览器提供的可编程缓存接口。
使用场景:
- Service Worker 中
- Window 上下文(普通网页)
- Worker 上下文
权限与安全
-
访问限制:
- 仅能访问同源的 Cache Storage
- 无法访问浏览器的 HTTP 缓存
- 无法访问其他存储机制
-
控制台访问:
- 可以在 DevTools 中查看和操作
- 仅限当前网站的缓存
- 遵循同源策略
基本用法
javascript
// 打开缓存
const cache = await caches.open('v1');
// 存储响应
await cache.put('/api/data', response);
// 读取缓存
const cached = await cache.match('/api/data');
// 删除缓存
await cache.delete('/api/data');
// 清理所有缓存
const keys = await caches.keys();
await Promise.all(keys.map(key => caches.delete(key)));
构建与部署优化
1. Webpack 配置优化
javascript
module.exports = {
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
clean: true // 清理 dist 目录
},
optimization: {
moduleIds: 'deterministic', // 确保模块 ID 稳定
runtimeChunk: 'single', // 分离 runtime 代码
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
}
}
}
}
2. 服务器配置
nginx
# HTML - 协商缓存
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache";
etag on;
}
# 静态资源 - 长期缓存
location /static/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# 动态 chunks - 长期缓存
location /chunks/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
3. 版本控制系统
javascript
// webpack 注入版本信息
new webpack.DefinePlugin({
'process.env.APP_VERSION': JSON.stringify(require('./package.json').version),
'process.env.BUILD_TIME': JSON.stringify(new Date().toISOString())
})
class VersionManager {
async checkUpdate() {
const response = await fetch('/version.json', {
headers: { 'Cache-Control': 'no-cache' }
});
const { version, buildTime } = await response.json();
if (version !== process.env.APP_VERSION ||
buildTime !== process.env.BUILD_TIME) {
await this.handleUpdate();
}
}
async handleUpdate() {
await this.clearCaches();
if (confirm('发现新版本,是否刷新?')) {
window.location.reload(true);
}
}
}
4. 部署流程
bash
#!/bin/bash
VERSION=$(date +%Y%m%d%H%M%S)
DEPLOY_DIR="/usr/share/nginx/html"
# 1. 新建版本目录
mkdir -p ${DEPLOY_DIR}/${VERSION}
# 2. 部署新文件
cp -r dist/* ${DEPLOY_DIR}/${VERSION}/
# 3. 切换版本
ln -sfn ${DEPLOY_DIR}/${VERSION} ${DEPLOY_DIR}/current
# 4. 保留最近版本
ls -dt ${DEPLOY_DIR}/20* | tail -n +4 | xargs rm -rf
环境特定策略
开发环境
nginx
# 开发环境配置
location / {
add_header Cache-Control "no-store";
add_header Pragma no-cache;
}
特点:
- 禁用缓存
- 启用热更新
- 频繁版本检查
测试环境
nginx
# 测试环境配置
location / {
add_header Cache-Control "no-cache";
etag on;
if_modified_since exact;
}
特点:
- 短期缓存
- 版本检测
- 强制刷新功能
生产环境
策略:
- HTML:协商缓存
- JS/CSS:contenthash + 长期缓存
- 图片/字体:hash + 长期缓存
- API:按数据特性决定
问题诊断与解决方案
1. 缓存不一致
症状:
- 新旧代码混合
- 功能异常
- 样式错乱
解决方案:
- 确保资源使用 contenthash
- 实施"先静态资源,后 HTML"的部署顺序
- 实现版本检测机制
2. CDN 缓存问题
症状:
- 不同地区用户体验不一致
- 更新不同步
解决方案:
- 使用版本化 URL
- 配置合适的缓存刷新策略
- 实施灰度发布
3. 测试环境缓存
症状:
- 测试结果不准确
- 需要频繁强制刷新
解决方案:
- 配置 no-store 头部
- 使用版本检测
- 提供一键刷新功能
最佳实践总结
1. 资源分类策略
- HTML:协商缓存
- JS/CSS:contenthash + 长期缓存
- 图片/字体:hash + 长期缓存
- API:按数据时效性决定
2. 构建优化
- 使用 contenthash
- 合理代码分割
- 提取公共依赖
3. 部署流程
- 版本化部署
- 支持快速回滚
- 自动化发布
4. 监控维护
- 监控缓存命中率
- 观察版本更新效果
- 及时处理缓存问题
记住:缓存策略不仅是技术选择,更是产品需求、用户体验和运维效率的平衡。好的缓存策略应该:
- 保证资源更新的及时性
- 最大化缓存利用率
- 支持快速问题诊断
- 便于运维和回滚