Reputation: 18520
I've been using Google's workbox library for a little while now, on a very basic level. Most of it works great, but for some reason the start_url
in my manifest.json
isn't being cached on initial page load (reported by Lighthouse, confirmed in dev tools). If you browse around a bit, or soft-refresh the page a few times, it does seem to cache, it just doesn't do it initially.
I assume I'm doing something wrong, but it's not clear to me what that is. Demo URL and source code below.
import { skipWaiting, clientsClaim } from "workbox-core";
import { ExpirationPlugin } from "workbox-expiration";
import { precacheAndRoute } from "workbox-precaching";
import { setCatchHandler, registerRoute } from "workbox-routing";
import { CacheFirst, NetworkFirst } from "workbox-strategies";
skipWaiting();
clientsClaim();
/**
* Cache WordPress content
*/
registerRoute(
({ url }) => {
return (url.pathname && !url.pathname.match(/^\/(wp-admin|wp-content|wp-includes|wp-json|wp-login)/) && url.pathname.match(/\/$/));
},
new NetworkFirst({
cacheName: "new_site-content-cache",
plugins: [
new ExpirationPlugin({
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
})
);
/**
* Cache CSS files
*/
registerRoute(
({ url }) => {
return (url.pathname && url.pathname.match(/\.css$/) && !url.pathname.match(/wp-admin|wp-includes|wp-json/));
},
new CacheFirst({
cacheName: "new_site-css-cache",
plugins: [
new ExpirationPlugin({
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
})
);
/**
* Cache JS files
*/
registerRoute(
({ url }) => {
return (url.pathname && url.pathname.match(/\.js$/) && !url.pathname.match(/wp-admin|wp-includes|wp-json/) && !url.pathname.match(/redirection/));
},
new CacheFirst({
cacheName: "new_site-js-cache",
plugins: [
new ExpirationPlugin({
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
})
);
/**
* Cache image files
*/
registerRoute(
({ url }) => {
return (url.pathname && url.pathname.match(/\.gif|jpeg|jpg|png|svg|webp$/) && !url.pathname.match(/wp-admin|wp-includes|wp-json/));
},
new CacheFirst({
cacheName: "new_site-image-cache",
plugins: [
new ExpirationPlugin({
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
})
);
/**
* Cache font files
*/
registerRoute(
({ url }) => {
return (url.pathname && url.pathname.match(/\.otf|ttf|woff|woff2$/) && !url.pathname.match(/wp-admin|wp-includes|wp-json/));
},
new CacheFirst({
cacheName: "new_site-font-cache",
plugins: [
new ExpirationPlugin({
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
})
);
/**
* Return "offline" page when visiting pages offline that haven't been cached
*/
setCatchHandler(() => {
return caches.match("/offline/");
});
/**
* Precache "offline" page
*/
precacheAndRoute([
{
url: "/offline/",
revision: __VERSION__,
},
]);
UPDATE 1: Okay, so I found this issue which I think is the problem. It seems that the initial response isn't cached because the service worker is installed after the request, so it has nothing to cache. I'm still not seeing a way to fix this, but will post another update if I make any progress.
UPDATE 2: It seems like using skipWaiting()
and clientsClaim()
is supposed to be the solution to this, but after adding those, it's still not working as expected. It's strange, Lighthouse reports "Current page responds with a 200 when offline," but also "start_url does not respond with a 200 when offlineThe start_url did respond, but not via a service worker." That doesn't make sense because it's the same page... maybe I'm doing something wrong with trying to route /
?
UPDATE 3: Tried changing the last check in my first registerRoute
to /\/?$/
, made no difference. Also tried changing NetworkFirst
to CacheFirst
and that actually works fine... but I need it to be NetworkFirst
, so that's not really a viable option to get around this issue. I also tried precaching /
, but that again made no difference.
UPDATE 4: I suspect that this "warm the runtime cache" recipe may be close to what I need, but I haven't been able to successfully implement... if I hard code the start_url
, it at least caches the HTML, but misses all other files. It seems like this method of gathering all resources may be useful, but location.href
is just returning the service worker URL, and even if it weren't, I'm not sure how I'd split everything out in to their own caches like I'm currently doing.
Upvotes: 4
Views: 1023
Reputation: 117
I had the same problem and found that the expression generated by workbox wizard is faulty. So I changed it and it now pre-cahcing all the site resources on the first load.
globPatterns: [
'**/*.{html, json, svg, png, gif, txt, css, js}'
],
I changed this to be like the following and it worked fine with me
globPatterns: [
'**/*.js',
'**/*.html',
'**/*.css',
'**/*.json',
'**/*.svg',
'**/*.png',
'**/*.gif',
'**/*.txt',
],
You can check the generated sw.js to find the different resources inserted in the actual sw.js file to know which resources were cached.
Upvotes: 0
Reputation: 2815
Just as the method of gathering all resources says, the page and resources are not cached because they are loaded before the service worker was installed.
Why not use workbox-build
to inject essential precache manifest at build time? Assets will be precached at service worker install time.
Quick example, add following line into service-worker.js
:
precacheAndRoute(self.__WB_MANIFEST);
And run the following js script after build:
const {injectManifest} = require('workbox-build');
const swSrc = 'src/sw.js';
const swDest = 'build/sw.js';
injectManifest({
swSrc,
swDest,
globDirectory: 'build',
globPatterns: ['*.@(js|css)']
}).then(({count, size}) => {
console.log(`Generated ${swDest}, which will precache ${count} files, totaling ${size} bytes.`);
});
You can even set swSrc
and swDest
to be the same, to inject manifest in place.
I think it's not neccesary to split assets out in to different caches, but it's possible using getManifest
mode, glob one type of assets at a time, and manually inject them into certain point in service-worker.js
Upvotes: 1