前端 | Workbox的缓存策略

2021 11 2, Tue

我的博客加上workbox以后更新一直不大对头。

前请提要:我重新用Gatsby写了个博客的架子

问题

目标

Gatsby有非常多的插件,每一个都集成了某些服务或者资源。比如说数据源、渲染工具、图片剪裁工具、缓存。其中我希望用gatsby-plugin-offline这个插件把大部分博客的资源缓存到浏览器里面,这样不用每跳转一次都要等待网络加载图片这些,甚至离线情况下我也可以从手机打开我的博客。

尝试

于是我在Gatsby配置的结尾加了这样一个配置:


module.exports = {
  siteMetadata: metadata,
  plugins: [
    `gatsby-plugin-sharp`,
    `gatsby-plugin-sass`,
    // ...
    
    `gatsby-plugin-offline`,
  ],
};

结果出事儿了。

出现问题

我部署以后刷新了博客的页面,看到图片都正常加载了而且很快,本来觉得问题解决了,挺好挺好。没有想到后来再更新博客时,在对象存储中上传了站点,在CDN上重新预取了数据,在浏览器里面清理了缓存。

可是我的首页没有更新。

又访问了具体的文章页面,也都可以访问,网络请求中service worker发起的真正的网络请求也成功了,但是首页就是不更新列表。

更具体来说,博客在刷新以后有时首页彻底不更新,有时更新以后又展示了老的页面。

猜测问题

Gatsby的原理是把React页面通过SSR渲染以后写到各个html中,同时把Query的数据存到json里面。每当我们访问页面时,从无到有打开页面我们会加载SSR渲染好的页面,加载JS以后重新把vdom对应到dom上。在页面内跳转时通过React Router加载需要的模块、拉取page-data.json、渲染需要更新的部分。

因为页面没有更新,或者偶尔会更新以后回到老的页面。非常像浏览器获得了新的HTML,在浏览器处理page-data的过程中用了老的page-data.json,所以又回到了老的列表。

尝试解决

为了解决问题,瞎折腾了很多,最后猜测和缓存策略有关系。

尝试过的解决办法:只缓存一部分的资源

可以解决,但是不是最佳方案,我还是希望我的博客可以全站缓存下来。

读到Google文档关于Service Worker的部分中讲页面缓存的几种策略

大体来讲,缓存有几种策略

  • Cache Only:只从缓存获取内容
  • Network Only:只从网络获取内容
  • Cache First:先读取缓存,没有再从网络获取
  • Network First:先从网络获取回复,没有再读取缓存
  • Stale while Revalidate:同时发起网络请求并尝试用缓存中的内容直接回复,网络请求成功时更新缓存

除此之外还可以利用Push API实现这两种方案

  • 通过Push API告知Service Worker更新缓存
  • 通过Background Sync在某个时候或定时更新缓存

重读gatsby-plugin-offline

重新检查offline插件,默认配置如下:

const options = {
  importWorkboxFrom: `local`,
  globDirectory: rootDir,
  globPatterns,
  modifyURLPrefix: {
    // If `pathPrefix` is configured by user, we should replace
    // the default prefix with `pathPrefix`.
    "/": `${pathPrefix}/`,
  },
  cacheId: `gatsby-plugin-offline`,
  // Don't cache-bust JS or CSS files, and anything in the static directory,
  // since these files have unique URLs and their contents will never change
  dontCacheBustURLsMatching: /(\.js$|\.css$|static\/)/,
  runtimeCaching: [
    {
      // Use cacheFirst since these don't need to be revalidated (same RegExp
      // and same reason as above)
      urlPattern: /(\.js$|\.css$|static\/)/,
      handler: `CacheFirst`,
    },
    {
      // page-data.json files, static query results and app-data.json
      // are not content hashed
      urlPattern: /^https?:.*\/page-data\/.*\.json/,
      handler: `StaleWhileRevalidate`,
    },
    {
      // Add runtime caching of various other page resources
      urlPattern:
        /^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/,
      handler: `StaleWhileRevalidate`,
    },
    {
      // Google Fonts CSS (doesn't end in .css so we need to specify it)
      urlPattern: /^https?:\/\/fonts\.googleapis\.com\/css/,
      handler: `StaleWhileRevalidate`,
    },
  ],
  skipWaiting: true,
  clientsClaim: true,
}

看到page-data.json还有一些静态资源都是用StaleWhileRevalidate这个策略,加载的同时更新缓存,有那么一点像是后端更新缓存后又遇到缓存脏数据的情况。

解决


module.exports = {
  siteMetadata: metadata,
  plugins: [
    `gatsby-plugin-sharp`,
    `gatsby-plugin-sass`,
    // ...
    {
      resolve: `gatsby-plugin-offline`,
      options: {
        workboxConfig: {
          runtimeCaching: [
            {
              // Use cacheFirst since these don't need to be revalidated (same RegExp
              // and same reason as above)
              urlPattern: /(\.js$|\.css$|static\/)/,
              handler: `StaleWhileRevalidate`,
            },
            {
              // page-data.json files, static query results and app-data.json
              // are not content hashed
              urlPattern: /^https?:.*\/(page|app)-data\/.*\.json/,
              handler: `NetworkFirst`, // !!!!!!!!!!!!!! <------------- CHANGED HRER
            },
            {
              // Add runtime caching of various other page resources
              urlPattern:
                /^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css|pprof|pdf)$/,
              handler: `StaleWhileRevalidate`,
            },
            {
              // Google Fonts CSS (doesn't end in .css so we need to specify it)
              urlPattern: /^https?:\/\/fonts\.googleapis\.com\/css/,
              handler: `StaleWhileRevalidate`,
            },
          ],
        },
      },
    },
  ],
};

既然是更新缓存的问题,那我们就让网页优先用网络上的page data就好,反正数据量也并不大。

测试缓存仍然正常工作

  • 写好这篇博客。
  • 部署到对象存储,刷新CDN。
  • 打开浏览器开发者工具。
  • 刷新,检查网络请求
  • Network Throttling选择No Network
  • 刷新,确认离线可以访问网站

完成!(希望我部署以后没问题)

之后可能继续了解的内容

虽然不是专门搞前端的,但是还是很有兴趣。

  • Service Worker和网页通信
  • Workbox告知页面缓存更新
  • Push API
  • Background Sync