SSF0SSF0
首页
前端
  • Node
  • Go
  • Python
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
  • 开源项目
Timeline
Github
标签
分类
首页
前端
  • Node
  • Go
  • Python
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
  • 开源项目
Timeline
Github
标签
分类
  • HTML

    • html1
    • html2
  • CSS

    • Flex 布局常见问题与解决方案
  • JavaScript

    • 数据类型及引用问题
    • 处理 Blob 类型文件下载问题总结
    • localStorage 与 sessionStorage 区别
    • JavaScript 中的 script 标签问题详解
    • JavaScript 中的 this 指向问题详解
    • SessionStorage踩坑记录
    • 动态加载 JS 脚本方法对比
    • 浏览器页面关闭场景下的数据上报
    • HTTP流式接口详解与封装实践
    • JavaScript 中 this 绑定与解构:为什么 Pinia Store 可以解构,但 Class 实例方法不行?
  • es6

    • Promise
    • es6 模块导出方式全解析
    • reduce
    • forEach 中使用 async/await 的异步陷阱
  • Vue2

    • created VS mounted 发起异步请求
    • vue2-2
  • Vue3

    • Vite + Vue3 修改 element-plus 源码
    • Vue v-if 与 v-show
    • Vue3 ref 获取组件
    • Vue3 路由传参
    • 父子组件与组件里 Hooks 加载顺序
    • 第三方组件传入参数TS提示
    • Vue3 Props 在 Hooks 中的响应性处理
    • Vue Router 的两种历史模式及部署配置详解
    • 在Vue 3项目中顺利集成Tailwind CSS的完整指南
    • Vue Scoped CSS 深度解析
    • Vue 3 Teleport 组件完全指南:从原理到实战
    • Vant List 组件中双向绑定 loading 的注意事项
    • Vue 3 + Vite 中的路径别名与静态资源处理深度解析
    • 获取图片高度
    • 深入理解 Pinia:storeToRefs vs toRefs - 避开 TypeScript 类型推断的坑
  • webSocket

    • WebSocket 鉴权入门(实战版):握手票据 / 首帧鉴权 + 连接级 cid
  • Electron

    • 快速构建 electron + vue3 项目
  • TS

    • TS 泛型
    • 记录模板使用断言的问题
    • type 与 interface
  • WebPack

    • Webpack 介绍
  • Vite

    • Vite CLI 常见命令
    • vite 与 webpack 比较
    • Vite base 配置与 public 静态资源路径问题
  • 客户端数据库

    • IndexedDB 索引陷阱:字段重命名后索引返回旧数据
    • IndexedDB 事务陷阱与最佳实践:TransactionInactiveError 完全解析
  • UniApp

    • uni-app + Monorepo + 微信小程序热更新失效问题排查与结论总结
    • Monorepo + uni-app 开发中监听 / 热更新机制差异与解决方案
    • uni-app 运行与编译原理梳理
    • 为什么 uni-app 在 Monorepo 中必须做「环境区分」,而 Web 项目却不用?
  • 项目工程

    • 前端代码风格
    • Vue3 项目规范
    • npm 镜像问题
    • 包管理工具
    • 使用 engines 限制开发环境依赖
    • 打包与shell交互指定模式
    • Monorepo示例
    • PC 项目兼容 uni-app:基于 pnpm Monorepo 的工程化重构实践
    • Vue3 + Vite + pnpm Monorepo 迁移问题指南
    • 前端工程化:深入理解 npm 依赖管理机制
    • pnpm 修改依赖源码打包报错
    • 代码片段
  • 移动端

    • 浏览器跳转外部应用时请求状态码问题
  • 记录一下小程序
  • 控制浏览器正确保存网站账号密码的技巧
  • 信息泄露
  • 使用Gulp实现前端项目哈希化压缩打包
  • 前端缓存策略与文件哈希化的关联解析
  • 浏览器刷新方式与 Vue 生命周期、缓存行为解析
  • 浏览器消息铃声实现记录

前端缓存策略与文件哈希化的关联解析

前端缓存的层级结构

前端缓存不是单一概念,而是多层级的缓存体系:

用户 → 浏览器缓存 → CDN缓存 → 反向代理缓存 → Web服务器 → 应用服务器

每一层都可能缓存文件,任何一层的配置错误都会导致更新问题。

缓存破坏(Cache Busting)的必要性

传统缓存的困境

<!-- 没有版本控制的引用 -->
<script src="js/app.js"></script>
<link rel="stylesheet" href="css/style.css" />

问题场景:

  1. 用户第一次访问:

    • 浏览器下载 app.js

    • 服务器设置:Cache-Control: max-age=31536000 (缓存 1 年)

    • 浏览器保存到本地缓存

  2. 开发者更新代码:

    • 服务器上的 app.js 内容已经改变

    • 但文件名还是 app.js

  3. 用户再次访问:

    • 浏览器看到还是 js/app.js

    • 浏览器使用缓存的旧文件

    • 用户看不到更新,甚至可能出现功能异常 😱

哈希化的解决方案

<!-- 哈希化后的引用 -->
<script src="js/app-a1b2c3d4.js"></script>
<link rel="stylesheet" href="css/style-e5f6g7h8.css" />

工作原理:

  • 文件内容不变 → 哈希值不变 → 文件名不变 → 使用缓存 ✅

  • 文件内容改变 → 哈希值改变 → 文件名改变 → 重新下载 ✅

实际例子:

// 原始文件内容
console.log("Hello World");
// ↓ 构建后
// app-a1b2c3.js

// 修改文件内容
console.log("Hello Vue");
// ↓ 重新构建后
// app-d4e5f6.js  (新哈希值)

不同类型文件的缓存策略

HTML 文件缓存策略

方案 1:不缓存(推荐)

location ~* \.html$ {
    add_header Cache-Control "no-cache, no-store, must-revalidate";
    add_header Pragma "no-cache";
    add_header Expires "0";
}

方案 2:短期缓存

location ~* \.html$ {
    add_header Cache-Control "max-age=300";  # 5分钟
}

原因:HTML 是"资源引用目录",必须能及时更新。

静态资源缓存策略

# 带哈希的静态资源 - 长期缓存
location ~* -[a-f0-9]{8,}\.(js|css|png|jpg|gif)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
    expires 1y;
}

原因:哈希值保证了文件唯一性,可以安全地长期缓存。

多层缓存的挑战

缓存层级详解

1. 浏览器缓存 (本地存储)

   - 内存缓存 (Memory Cache)

   - 磁盘缓存 (Disk Cache)

2. 代理缓存 (中间层)

   - 公司代理服务器

   - ISP缓存

3. CDN缓存 (全球分布)

   - 边缘节点缓存

   - 源站缓存

4. 服务器缓存 (源站)

   - Nginx缓存

   - 应用服务器缓存

实际案例分析

从真实的请求头分析缓存问题:

# 问题请求示例
Request: XXX.2af4e15d.html
Response Headers:
  age: 330006                     # CDN已缓存91小时
  x-cache: HIT TCP_MEM_HIT       # 命中CDN缓存
  x-swift-cachetime: 2589109     # CDN设置缓存30天

# 浏览器明确不要缓存
Request Headers:
  cache-control: no-cache        # 浏览器请求不缓存
  pragma: no-cache               # 兼容性不缓存头

问题根源:CDN 忽略了浏览器的 no-cache 请求,强制返回缓存内容。

前后端分离项目的 Nginx 配置

完整的 Nginx 配置示例

server {
    listen 80;
    server_name your-domain.com;
    root /var/www/html/dist;
    index index.html;

    # 入口HTML - 不缓存
    location = / {
        try_files /index.html =404;
        add_header Cache-Control "no-cache, no-store, must-revalidate";
        add_header Pragma "no-cache";
        add_header Expires "0";
    }

    # 带哈希的HTML文件 - 可以长期缓存
    location ~* -[a-f0-9]{8,}\.html$ {
        add_header Cache-Control "public, max-age=31536000, immutable";
        expires 1y;
    }

    # 带哈希的JS/CSS - 长期缓存
    location ~* -[a-f0-9]{8,}\.(js|css)$ {
        add_header Cache-Control "public, max-age=31536000, immutable";
        expires 1y;
    }

    # 带哈希的图片 - 长期缓存
    location ~* -[a-f0-9]{8,}\.(png|jpg|jpeg|gif|svg)$ {
        add_header Cache-Control "public, max-age=31536000, immutable";
        expires 1y;
    }

    # 其他HTML文件 - 短期缓存
    location ~* \.html$ {
        add_header Cache-Control "no-cache";
    }

    # API 请求转发到后端
    location /api/ {
        proxy_pass http://backend-server:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

CDN 配置建议

# CDN缓存规则配置
cache_rules:
  - pattern: "*.html"
    cache_time: 0 # HTML不缓存

  - pattern: "*-[a-f0-9]*.js"
    cache_time: 31536000 # 哈希化JS长期缓存

  - pattern: "*-[a-f0-9]*.css"
    cache_time: 31536000 # 哈希化CSS长期缓存

  - pattern: "*-[a-f0-9]*.(png|jpg|gif)"
    cache_time: 31536000 # 哈希化图片长期缓存

常见问题和解决方案

问题 1:更新后用户还是看到旧版本

排查步骤:

# 1. 检查文件是否真的更新了
ls -la dist/js/app-*.js

# 2. 检查HTML引用是否更新了
grep "app-" dist/*.html

# 3. 检查服务器缓存配置
curl -I https://your-site.com/index.html

# 4. 强制刷新浏览器缓存
Ctrl + F5 (Windows) / Cmd + Shift + R (Mac)

问题 2:CDN 缓存导致的更新延迟

解决方案:

  1. CDN 控制台手动刷新

  2. API 自动刷新:

// 构建完成后自动刷新CDN
function refreshCDN() {
  // 调用CDN API刷新缓存
  execSync('curl -X POST "https://cdn-api.com/refresh" -d "path=/*"');
}

问题 3:哈希值不一致

常见原因:

  • 构建顺序错误(先哈希再压缩)

  • 多次构建导致清单文件混乱

  • 并行构建时的竞态条件

解决方案:

// 确保正确的构建顺序
gulp.series(
  clean,                    // 先清理
  gulp.parallel(...),       // 再并行处理
  htmlRevise,               // 最后替换引用
);

最佳实践总结

1. 文件分类处理

// 需要哈希化的文件
✅ JS/CSS/图片等静态资源

✅ 业务逻辑相关的HTML

// 不建议哈希化的文件
❌ 入口HTML文件

❌ robots.txt, sitemap.xml

❌ 第三方库文件

2. 部署策略

# 部署顺序很重要
1. 先部署静态资源 (JS/CSS/图片)

2. 再部署HTML文件

3. 最后刷新CDN缓存

3. 验证方法

// 在JS文件中添加版本标识
console.log('构建时间:', '2025-01-15 10:30:00');
console.log('Git提交:', 'abc123def456');

// 在HTML中添加meta标签
<meta name="build-time" content="2025-01-15 10:30:00">
<meta name="git-commit" content="abc123def456">

4. 监控和调试

// 添加构建信息到文件中
function addBuildInfo() {
  const buildInfo = {
    buildTime: new Date().toISOString(),
    gitCommit: execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim(),
    branch: execSync("git rev-parse --abbrev-ref HEAD", {
      encoding: "utf-8",
    }).trim(),
  };

  return gulp
    .src("dist/**/*.js")
    .pipe(replace("/* BUILD_INFO */", `/* ${JSON.stringify(buildInfo)} */`))
    .pipe(gulp.dest("dist/"));
}

核心原理总结

哈希化缓存策略的本质:

  1. 静态资源:哈希化 + 长期缓存 = 性能最优

  2. HTML 文件:不缓存或短期缓存 = 更新及时

  3. CDN 配置:区分处理不同类型文件

  4. 部署流程:先静态资源,后 HTML,最后刷新缓存

最终目标:在保证性能的同时,确保用户总是能看到最新版本的代码。

这种策略已经成为现代前端项目的标准做法,被广泛应用于各种规模的 Web 应用中。无论是 Vue、React 还是原生 HTML 项目,都可以采用这种构建和缓存策略来优化用户体验。

实战经验

调试技巧

  1. 开发者工具 Network 面板:

    • 查看 Status:200(新请求) vs 304(缓存)

    • 查看 Size 列:实际大小 vs (from cache)

  2. 添加时间戳验证:

// 在关键JS文件中添加
console.log("JS加载时间:", new Date().toISOString());
console.log("文件哈希:", window.location.pathname.match(/-([a-f0-9]+)\./)?.[1]);
  1. 服务器日志分析:
# 查看Nginx访问日志
tail -f /var/log/nginx/access.log | grep "\.js"

# 查看CDN命中率
grep "HIT\|MISS" /var/log/nginx/access.log

性能优化建议

  1. 合理设置缓存时间:

    • HTML: 0-5 分钟

    • 哈希化资源: 1 年

    • API 接口: 根据业务需求

  2. 使用 immutable 指令:

# 告诉浏览器文件永远不会改变
add_header Cache-Control "public, max-age=31536000, immutable";
  1. 预加载关键资源:
<link rel="preload" href="js/app-a1b2c3.js" as="script" />
<link rel="preload" href="css/style-d4e5f6.css" as="style" />

通过理解和正确实施这些缓存策略,可以显著提升前端应用的性能和用户体验。

最后更新时间:
贡献者: 何风顺
上一页
使用Gulp实现前端项目哈希化压缩打包
下一页
浏览器刷新方式与 Vue 生命周期、缓存行为解析