Service worker PWA + Amplify Deployment issues - reactjs

We are currently hosting a react app with amplify and have recently enabled the service worker via workbox to become PWA compatible. Our amplify app uses Cloudflare + S3 to serve our built react app. After enabling the service worker, when pushing new code and triggering a build, we noticed that if a user was on the site and refreshed their app, it would get a 404 error
https://ourwebsite.com/static/js/main.3j3k232.chunk.js 404 not found
This error would persist until the user would do a hard refresh on the browser. Our service worker previously was set to createHandlerBoundToURL() but we opted for NetworkFirst() instead because we were curious about solving the N+1 update problem. This was after the createHandlerBoundToURL() version of the service worker was deployed already.
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { NetworkFirst, NetworkOnly, StaleWhileRevalidate } from 'workbox-strategies';
declare const self: ServiceWorkerGlobalScope;
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST);
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }: { request: Request; url: URL }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
}
// If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
}
// If this looks like a URL for a resource, because it contains
// a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
}
// Return true to signal that we want to use the handler.
return true;
},
// createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') // initially had this enabled
new NetworkFirst()
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) =>
url.origin === self.location.origin && url.pathname.endsWith('.png'),
// Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
To reproduce this, we would only have to push new code and trigger a build, after it was completed the user would just need to refresh and they could be met with a 404 blank screen. We are using this PWA in a TWA android application.
Any help would be appreciated! We suspect it could also be an issue with CloudFront CDN as we have read that CDNs can cause issues with service workers and their caches.

Related

Script load failed service worker on PWA

On my PWA application I have successfully registered a service worker. Some users on Safari are experiencing this error Script https://app.com/service-worker.js load failed Note that I was still able to view the service worker in the dev tools despite the error (it was activated status).
It is not clear what this error means. It is not impacting their experience but I am afraid it might cause issues later on.
Here is my service-worker.js - Note we cannot have any caching in our application. We enabled the service worker just so we can have add to home screen capability on Android.
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core';
import { precacheAndRoute } from 'workbox-precaching';
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST.filter(() => false));
self.addEventListener('install', (event) => {
self.skipWaiting();
event.waitUntil(caches.open('app').then((cache) => cache.addAll([])));
});
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches
.keys()
.then((cacheNames) => Promise.all(cacheNames.map((cacheName) => caches.delete(cacheName)))),
);
});

How to exclude index.html from service-worker.js in CRA

I created a new project using CRA's npx create-react-app my-app --template cra-template-pwa command. When I build and test, the service worker worked, but index.html is also cached, so the change is not applied. I am wondering how can I prevent the precahce of index.html.
Is there a way to prevent precache in service-worker.js or serviceWorkerRegistration.js created by CRA? I tried to build using workbox-cli, but it fails because self.__WB_MANIFEST does not exist.
service-worker.js
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from "workbox-core";
import { ExpirationPlugin } from "workbox-expiration";
import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching";
import { registerRoute } from "workbox-routing";
import { StaleWhileRevalidate } from "workbox-strategies";
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST);
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp("/[^/?]+\\.[^/]+$");
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }) => {
console.log(url);
// If this isn't a navigation, skip.
if (request.mode !== "navigate") {
return false;
} // If this is a URL that starts with /_, skip.
if (url.pathname.startsWith("/_")) {
return false;
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
} // Return true to signal that we want to use the handler.
if (url.pathname.endsWith("/index.html")) {
return false;
}
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + "/index.html")
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) =>
url.origin === self.location.origin && url.pathname.endsWith(".png"), // Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: "images",
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
registerRoute(
({ url }) =>
url.origin === self.location.origin && url.pathname.endsWith(".html"),
new StaleWhileRevalidate({
cacheName: "html",
plugins: [new ExpirationPlugin({ maxEntries: 0 })],
})
);
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
// Any other custom service worker logic can go here.
self.addEventListener("push", function (e) {
const data = e.data.json();
console.log(data);
self.registration.showNotification(data.title, {
body: data.message,
requireInteraction: true,
icon: "/favicon.ico",
badge: "/favicon.ico",
});
});

Create react app PWA manifest.json not being cached

I'm building a React app using create-react-app and I used the following command to make it from the beginning a PWA.
npx create-react-app my-app --template cra-template-pwa
After building my app, going to the browser developer tools, and look into the cache, I noticed all my images and files are cached but not my manifest.json. I can see my manifest.json being in the app since I can look at it in the dev tools and I can install the app on my computer. The problem comes when I go offline, the manifest.json is not found because it's not cached.
How can I cache my manifest.json?
Why my manifest.json isn't cached as default?
This is the error I see in my console when I serve the app offline:
GET http://localhost:5000/manifest.json net::ERR_INTERNET_DISCONNECTED
200
Service Worker generated by CRA;
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST);
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
} // If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
} // Return true to signal that we want to use the handler.
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});```

Precached content does not load from cache

In my React SSR application I have implemented service worker(via Workbox).
It's working fine. Every time when I am changing some piece of code, rebuilding again, running the server, going to the browser, I am seeing that my cache was updated succesfully.
But one thing I cant understand. When I am deleting some asset(js or css) from my local server and trying to do some action in the browser(which invokes that asset) I am getting a chunk error, which says that the file is not available.
The main question is if that asset is already is in cache storage it should not be loaded from that cache or I have missed something?
The components I have used is
Node/express(for server)
#loadable/components(for code splitting), combined with webpack
Google workbox plugin
// my sw.js file
import { skipWaiting } from 'workbox-core';
import { precacheAndRoute } from 'workbox-precaching';
declare const self: Window & ServiceWorkerGlobalScope;
precacheAndRoute(self.__WB_MANIFEST);
skipWaiting();
// my workbox setup
const serviceWorkerRegistration = async (): Promise<void> => {
const { Workbox } = await import('workbox-window');
const wb = new Workbox('./service-worker.js');
wb.addEventListener('activated', (event: any) => {
if (event.isExternal) {
window.location.reload();
}
});
wb.register();
};
This is due to your usage of skipWaiting() inside of your service worker. When the waiting service worker activates, it will delete all of the outdated precached entries that are no longer associated with the new service worker deployment.
There is more background information in this two closely related answers, as well as a presentation:
What are the downsides to using skipWaiting and clientsClaim with Workbox?
Workbox: the danger of self.skipWaiting()
Paying Attention while Loading Lazily

How to use injectManifest in Workbox with Parcel-bundler?

I am building a service worker for my PWA with Workbox.
service-worker.js
import { registerRoute } from 'workbox-routing';
import { NetworkFirst } from 'workbox-strategies';
import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import * as googleAnalytics from 'workbox-google-analytics';
import { precacheAndRoute } from 'workbox-precaching';
googleAnalytics.initialize();
registerRoute(
({ request }) => request.destination === 'script',
new NetworkFirst()
);
registerRoute(
// Cache style resources, i.e. CSS files.
({ request }) => request.destination === 'style',
// Use cache but update in the background.
new StaleWhileRevalidate({
// Use a custom cache name.
cacheName: 'css-cache',
})
);
registerRoute(
// Cache image files.
({ request }) => request.destination === 'image',
// Use the cache if it's available.
new CacheFirst({
// Use a custom cache name.
cacheName: 'image-cache',
plugins: [
new ExpirationPlugin({
// Cache for a maximum of a week.
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
})
);
precacheAndRoute(self.__WB_MANIFEST);
if (process.env.NODE_ENV !== 'production') {
console.log('This is a dev-only log message!');
}
Is there a way that Parcel injects only the latest version of files in the service worker after each build?
My Attempts
I tried using workbox-build in a node script but each time I ran npm run build, it added all the versions of the file present in the dist/ folder.
service-worker.js (when I used a Node script)
// code skipped for brevity
precacheAndRoute([
{"revision":"78416d1f083fa1a898d17e20d741a462","url":"Share.332aebe4.js"},{"revision":"413f2ed8c0dcd51e3c01855ac307f6d2","url":"Share.77f580e5.js"},{"revision":"c95daa634502ca4a38e1822e7460688e","url":"style.0f60499b.css"},{"revision":"7f4b6e94999540101a2a7e5b4226080e","url":"style.78032849.css"}])
They were different versions of the same file but I want only the latest version
I tried using parcel-plugin-sw-cache but couldn't figure out how to use it due to insufficient documentation.
parcel-plugin-sw-cache works when you change
precacheAndRoute(self.__WB_MANIFEST);
to
precacheAndRoute([]);
Also, when you're using injectManifest method, you need to provide "swSrc": "service-worker.js" in package.json (according to documentation)

Resources