A custom service worker, next-pwa

August 4th, 2021

This article will show you how to add a custom service worker to your next-pwa app.

For this I have used the base configuration in the next-pwa examples. Then I have added a small snip-it to download my markdown files and cache them.

Getting Started

For this we are going to be adding on a folder and a file, then it should just work.

Obviously, there is a lot of work going on under the hood of next-pwa, but they have made it as simple as possible for people to add this functionality to your apps.

Adding the service worker

In your root directory add a the folder and file /worker/index.js

and there it is. Anything in here will be added to the automatically generated sw.js file that sits in your public folder.

Here is how my file is defined for reference:

import { skipWaiting, clientsClaim } from 'workbox-core'
import { ExpirationPlugin } from 'workbox-expiration'
import { NetworkOnly, NetworkFirst, CacheFirst, StaleWhileRevalidate } from 'workbox-strategies'
import { registerRoute, setDefaultHandler, setCatchHandler } from 'workbox-routing'
import { matchPrecache, cleanupOutdatedCaches } from 'workbox-precaching'

skipWaiting()
clientsClaim()

cleanupOutdatedCaches()
registerRoute(
  '/',
  new NetworkFirst({
    cacheName: 'start-url',
    plugins: [new ExpirationPlugin({ maxEntries: 1, maxAgeSeconds: 86400, purgeOnQuotaError: !0 })]
  }),
  'GET'
)
registerRoute(
  /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i,
  new CacheFirst({
    cacheName: 'google-fonts',
    plugins: [new ExpirationPlugin({ maxEntries: 4, maxAgeSeconds: 31536e3, purgeOnQuotaError: !0 })]
  }),
  'GET'
)
registerRoute(
  /\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,
  new StaleWhileRevalidate({
    cacheName: 'static-font-assets',
    plugins: [new ExpirationPlugin({ maxEntries: 4, maxAgeSeconds: 604800, purgeOnQuotaError: !0 })]
  }),
  'GET'
)
// disable image cache, so we could observe the placeholder image when offline
registerRoute(
  /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
  new NetworkOnly({
    cacheName: 'static-image-assets',
    plugins: [new ExpirationPlugin({ maxEntries: 64, maxAgeSeconds: 86400, purgeOnQuotaError: !0 })]
  }),
  'GET'
)
registerRoute(
  /\.(?:js)$/i,
  new StaleWhileRevalidate({
    cacheName: 'static-js-assets',
    plugins: [new ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400, purgeOnQuotaError: !0 })]
  }),
  'GET'
)
registerRoute(
  /\.(?:css|less)$/i,
  new StaleWhileRevalidate({
    cacheName: 'static-style-assets',
    plugins: [new ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400, purgeOnQuotaError: !0 })]
  }),
  'GET'
)
registerRoute(
  /\.(?:json|xml|csv)$/i,
  new NetworkFirst({
    cacheName: 'static-data-assets',
    plugins: [new ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400, purgeOnQuotaError: !0 })]
  }),
  'GET'
)
registerRoute(
  /\/api\/.*$/i,
  new NetworkFirst({
    cacheName: 'apis',
    networkTimeoutSeconds: 10,
    plugins: [new ExpirationPlugin({ maxEntries: 16, maxAgeSeconds: 86400, purgeOnQuotaError: !0 })]
  }),
  'GET'
)
registerRoute(
    /\.(?:md)$/i,
    new NetworkFirst({
      cacheName: 'blog-content',
      networkTimeoutSeconds: 10,
      plugins: [new ExpirationPlugin({ maxEntries: 16, maxAgeSeconds: 86400, purgeOnQuotaError: !0 })]
    }),
    'GET'
  )
registerRoute(
  /.*/i,
  new NetworkFirst({
    cacheName: 'others',
    networkTimeoutSeconds: 10,
    plugins: [new ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400, purgeOnQuotaError: !0 })]
  }),
  'GET'
)

// following lines gives you control of the offline fallback strategies
// https://developers.google.com/web/tools/workbox/guides/advanced-recipes#comprehensive_fallbacks

// Use a stale-while-revalidate strategy for all other requests.
setDefaultHandler(new StaleWhileRevalidate())

// This "catch" handler is triggered when any of the other routes fail to
// generate a response.
setCatchHandler(({ event }) => {
  // The FALLBACK_URL entries must be added to the cache ahead of time, either
  // via runtime or precaching. If they are precached, then call
  // `matchPrecache(FALLBACK_URL)` (from the `workbox-precaching` package)
  // to get the response from the correct cache.
  //
  // Use event, request, and url to figure out how to respond.
  // One approach would be to use request.destination, see
  // https://medium.com/dev-channel/service-worker-caching-strategies-based-on-request-types-57411dd7652c
  switch (event.request.destination) {
    case 'document':
      // If using precached URLs:
      return matchPrecache('/fallback');
    //   return caches.match('/fallback')
      break
    case 'image':
      // If using precached URLs:
      return matchPrecache('/static/images/fallback.png');
      // return caches.match('/static/images/fallback.png')
      break
    case 'font':
    // If using precached URLs:
    // return matchPrecache(FALLBACK_FONT_URL);
    //return caches.match('/static/fonts/fallback.otf')
    //break
    default:
      // If we don't have a fallback, just return an error response.
      return Response.error()
  }
})

This works for me as I use the pre-cached urls from next-pwa. But this may require some more configuration to work for your app.

Here is what I added

The part I added to the file to cache my markdown files. Which I am not sure if I have to do, as they are statically generated but it works so, I will show you how.

registerRoute(
    /\.(?:md)$/i,
    new NetworkFirst({
      cacheName: 'blog-content',
      networkTimeoutSeconds: 10,
      plugins: [new ExpirationPlugin({ maxEntries: 16, maxAgeSeconds: 86400, purgeOnQuotaError: !0 })]
    }),
    'GET'
  )
  • registerRoute defines a root for the service-worker to look for when caching.
  • /.(?:md)$/i this is an regular expression, if you have not used these before they allow you define a path that can include multiple files. So this looks for all files with the .md extension.
  • NetworkFirst the files from before will download once you have a network connection established.
  • cache-name kind of self-explanatory, what do you what the cache to be called
  • networkTimeoutSeconds how long before you want to cancel the request to get the files. (for larger files you may want to extend this, mine are all just text.)
  • plugins This uses a plugin from this import import { ExpirationPlugin } from 'workbox-expiration'
  • ExpirationPlugin This defines the amount for entries you want to cache and then will take a certain amount of time in ms to set an age for these items to expire. (Change this if you have more you want to cache or for them to be cached for a different amount of time)
  • GET this is the type of request the service worker will use to make the request.

For more information on plugins go to this link, for the workbox plugins page

Conclusion

next-pwa make it really easy to add this, and I have only scratched the surface of what service-workers are able to do.

But hopefully this will help you add your own service worker and let you cache the items you need to make your app as productive as possible.

Thank you for reading.

Back to Home