前端 | Workbox的缓存策略
我的博客加上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