43

I built an app using create react which by default includes a service worker. I want the app to be run anytime someone enters the given url except when they go to /blog/, which is serving a set of static content. I use react router in the app to catch different urls.

I have nginx setup to serve /blog/ and it works fine if someone visits /blog/ without visiting the react app first. However because the service worker has a scope of ./, anytime someone visits any url other than /blog/, the app loads the service worker. From that point on, the service worker bypasses a connection to the server and /blog/ loads the react app instead of the static contents.

Is there a way to have the service worker load on all urls except /blog/?

pppery
  • 3,731
  • 22
  • 33
  • 46
user3577166
  • 532
  • 1
  • 4
  • 8
  • 1
    could you post the relevant code inside of the `fetch` event listener in your service worker? – Schrodinger's cat Aug 13 '17 at 19:58
  • There are a couple of different solutions in github comments. Basically you need to edit the field `navigateFallbackWhitelist` in `sw-precache` config. [Solution 1](https://github.com/facebook/create-react-app/issues/3171#issuecomment-386262079) and [solution 2 (using react-app-rewired)](https://github.com/facebook/create-react-app/issues/2237#issuecomment-419646171) – apaatsio Sep 08 '18 at 14:44

9 Answers9

39

So, considering, you have not posted any code relevant to the service worker, you might consider adding a simple if conditional inside the code block for fetch

This code block should already be there inside your service worker. Just add the conditionals.

self.addEventListener( 'fetch', function ( event ) {

  if ( event.request.url.match( '^.*(\/blog\/).*$' ) ) {
    return false;
  }

  // OR
  if ( event.request.url.indexOf( '/blog/' ) !== -1 ) {
    return false;
  }

  // **** rest of your service worker code ****
}

Note: You can either use the regex or the prototype method indexOf. per your whim.

The above would direct your service worker, to just do nothing when the url matches /blog/.

Dmitriy Popov
  • 2,150
  • 3
  • 25
  • 34
Schrodinger's cat
  • 1,074
  • 10
  • 17
  • Hey, sorry for the delayed reply, but yeah, this works great. Thanks for your help. – user3577166 Aug 14 '17 at 17:06
  • 1
    Hello, where do I actually put this code block in my create-react-app? Do I have to eject? – Woppi Feb 27 '18 at 09:48
  • 1
    @Woppi Check our this github repo: https://github.com/bbhlondon/cra-append-sw. It allows you to append sw functionality. – creimers Jun 07 '18 at 06:49
  • 3
    Any idea how this works with the new service worker implementation that doesn't use `addEventListener('fetch', ...)`? – F Lekschas Oct 16 '18 at 03:19
  • 2
    Can somebody be more specific where this change is supposed to be? What is "your service woker"? Do you mean registerServiceWorker.js? However, I don't see self.addEventLister(... there. – newman Oct 31 '18 at 03:27
  • 1
    Have not had a chance to look into a newer implementation of an sw if any, yet. Can you list out the life cycle if the implementation has changed? – Schrodinger's cat Oct 31 '18 at 03:42
  • 1
    the file is `service-worker.js` as of right now and can be found in the `build` folder generated by cra after the build command runs. – Tope Dec 15 '18 at 00:15
  • Is anyone getting `The service worker navigation preload request was canceled before 'preloadResponse' settled. If you intend to use 'preloadResponse', use waitUntil() or respondWith() to wait for the promise to settle.` error on doing the above? – pnk6 Mar 16 '21 at 14:17
14

Another way to blacklist URLs, i.e., exclude them from being served from cache, when you're using Workbox can be achieved with workbox.routing.registerNavigationRoute:

workbox.routing.registerNavigationRoute("/index.html", {
  blacklist: [/^\/api/,/^\/admin/],
});

The example above demonstrates this for a SPA where all routes are cached and mapped into index.html except for any URL starting with /api or /admin.

Edric
  • 24,639
  • 13
  • 81
  • 91
F Lekschas
  • 12,481
  • 10
  • 60
  • 72
  • I did this for create-react-app: https://stackoverflow.com/questions/49776235/how-do-i-exclude-urls-from-service-worker-scope-in-create-react-app-without-ha/63344095#63344095 – Wayne Bloss Aug 10 '20 at 16:39
7

here's whats working for us in the latest CRA version:

// serviceWorker.js

window.addEventListener('load', () => {
  if (isAdminRoute()) {
    console.info('unregistering service worker for admin route')
    unregister()
    console.info('reloading')
    window.location.reload()
    return false
  }

we exclude all routes under /admin from the server worker, since we are using a different app for our admin area. you can change it of course for anything you like, here's our function in the bottom of the file:

function isAdminRoute() {
  return window.location.pathname.startsWith('/admin')
}
Shining Love Star
  • 5,734
  • 5
  • 39
  • 49
  • Thanks, it works great. I made a modification though so I can add more URLs dynamically. `function isNotPwa() { const blacklist = ['store'].filter( r => window.location.pathname.startsWith(`/${r}`)) return blacklist.length > 0 }` – Doyin Olarewaju Jan 20 '20 at 01:12
  • 4
    Wouldn't this unregister the SW for also the non-admin routes? So what happens if you are browsing both areas at the same time? Then sometimes you might have a SW registered and sometimes not, I assume. – Dejan Jun 03 '20 at 13:14
  • 1
    As @Dejan said, this is more of a foot-gun than any sort of help. It registers/unregisters the ENTIRE SW at page load time. This will not solve the OP's problem, wherein they want to move between the two types of pages without forcing and entire page reload. – Zane Claes Sep 28 '20 at 22:56
3

Here's how you do it in 2021:

import {NavigationRoute, registerRoute} from 'workbox-routing';

const navigationRoute = new NavigationRoute(handler, {
  allowlist: [
    new RegExp('/blog/'),
  ],
  denylist: [
    new RegExp('/blog/restricted/'),
  ],
});
registerRoute(navigationRoute);

How to Register a Navigation Route

Dave Anderson
  • 11,836
  • 3
  • 58
  • 79
Max Flex
  • 1,116
  • 10
  • 16
1

If you are using or willing to use customize-cra, the solution is quite straight-forward.

Put this in your config-overrides.js:

const { adjustWorkbox, override } = require("customize-cra");

module.exports = override(
  adjustWorkbox(wb => 
    Object.assign(wb, {
      navigateFallbackWhitelist: [
        ...(wb.navigateFallbackWhitelist || []),
        /^\/blog(\/.*)?/,
      ],
     })
   )
);

Note that in the newest workbox documentation, the option is called navigateFallbackAllowlist instead of navigateFallbackWhitelist. So, depending on the version of CRA/workbox you use, you might need to change the option name.

The regexp /^/blog(/.*)?/ matches /blog, /blog/, /blog/abc123 etc.

apaatsio
  • 3,073
  • 1
  • 22
  • 18
0

Try using the sw-precache library to overwrite the current service-worker.js file that is running the cache strategy. The most important part is setting up the config file (i will paste the one I used with create-react-app below).

  1. Install yarn sw-precache
  2. Create and specify the config file which indicates which URLs to not cache
  3. modify the build script command to make sure sw-precache runs and overwrites the default service-worker.js file in the build output directory

I named my config file sw-precache-config.js is and specified it in build script command in package.json. Contents of the file are below. The part to pay particular attention to is the runtimeCaching key/option. "build": "NODE_ENV=development react-scripts build && sw-precache --config=sw-precache-config.js"

CONFIG FILE: sw-precache-config.js

module.exports = {
    staticFileGlobs: [
        'build/*.html',
        'build/manifest.json',
        'build/static/**/!(*map*)',
    ],
    staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    swFilePath: './build/service-worker.js',
    stripPrefix: 'build/',
    runtimeCaching: [
        {
            urlPattern: /dont_cache_me1/,
            handler: 'networkOnly'
        }, {
            urlPattern: /dont_cache_me2/,
            handler: 'networkOnly'
        }
    ]
}
Tope
  • 938
  • 2
  • 11
  • 15
0

Update (new working solution) In the last major release of Create React App (version 4.x.x), you can easily implement your custom worker-service.js without bleeding. customize worker-service

Starting with Create React App 4, you have full control over customizing the logic in this service worker, by creating your own src/service-worker.js file, or customizing the one added by the cra-template-pwa (or cra-template-pwa-typescript) template. You can use additional modules from the Workbox project, add in a push notification library, or remove some of the default caching logic.

You have to upgrade your react script to version 4 if you are currently using older versions.

Ali Titan
  • 51
  • 1
  • 4
0

Working solution for CRA v4

Add the following code to the file service-worker.js inside the anonymous function in registerRoute-method.

// If this is a backend URL, skip
if (url.pathname.startsWith("/backend")) {
    return false;
}
mleister
  • 1,697
  • 2
  • 20
  • 46
0

To simplify things, we can add an array list of items to exclude, and add a search into the fetch event listener.

Include and Exclude methods below for completeness.

var offlineInclude = [
    '',                // index.html
    'sitecss.css',
    'js/sitejs.js'
];

var offlineExclude = [
    '/networkimages/bigimg.png',   //exclude a file
    '/networkimages/smallimg.png',
    '/admin/'                      //exclude a directory
];

self.addEventListener("install", function(event) {
  console.log('WORKER: install event in progress.');
  event.waitUntil(
    caches
      .open(version + 'fundamentals')
      .then(function(cache) {
        return cache.addAll(offlineInclude);
      })
      .then(function() {
        console.log('WORKER: install completed');
      })
  );
});

self.addEventListener("fetch", function(event) {
  console.log('WORKER: fetch event in progress.');

  if (event.request.method !== 'GET') {
    console.log('WORKER: fetch event ignored.', event.request.method, event.request.url);
    return;
  }

  for (let i = 0; i < offlineExclude.length; i++)
  {
    if (event.request.url.indexOf(offlineExclude[i]) !== -1)
    {
      console.log('WORKER: fetch event ignored. URL in exclude list.', event.request.url);
      return false;
    }
  }
TJS101
  • 490
  • 1
  • 7
  • 19