Cache Strategies

Network Only

Do not store anything in the cache and fetch everything from the network.

// sw.js

self.addEventListener("fetch", event => {
  event.respondWith(fetch(event.request));
});

This is essentially the same behaviour as not using a Service Worker.
Not very usefull is it?

Pre-Cache Only

Only store files in the cache during the install event and serve only those.

// sw.js

self.addEventListener("fetch", event => {
  event.respondWith(caches.match(event.request));
});

This is only usefull if you can completely cache your application.

Network with Cache Fallback

You first try to fetch from the network and if that fails you fall back to the cache.

// sw.js

self.addEventListener("fetch", event => {
  event.respondWith(
    fetch(event.request)
      // save it to the cache
      .then(res => {
        return caches.open("dynamic").then(cache => {
          cache.put(event.request.url, res.clone());
          return res;
        });
      })
      // if network fails, serve from cache
      .catch(err => {
        return caches.match(event.request);
      })
  );
});

You don't have the advantage of a fast cache response.
Network fails aren't necessarily instantly. If you are offline it will fail fast, but when you have a poor connection it can take quite a while.

Cache then Network with Dynamic Caching and Offline Support

If a resource is available in the cache we serve it instantly.
In the meantime the Service Worker will also fetch it from the network, save it to the cache and update the website.

// app.js
const url = "https://whatever.com/json";
const networkDataAvailable = false;

// fetch it from the network
fetch(url)
  .then(response => {
    if (response) {
      return response.json();
    }
  })
  .then(data => {
    // do something with the data
    networkDataAvailable = true;
    console.log("Fetched from web", data);
  });

// and also grab it from the cache if available
caches
  .match(url)
  .then(response => {
    if (response) {
      return response.json();
    }
  })
  .then(data => {
    // do something with the data
    // if the network was faster, ignore the cache
    if (!networkDataAvailable) {
      console.log("Fetched from cache", data);
    }
  });
// sw.js

const DYNAMIC_CACHE = "dynamic-v2";
const STATIC_CACHE = "static-v3";
const STATIC_FILES = ["/", "/index.html", "/offline.html", "/app.js", "/app.css", "/image.png"];
const url = "https://whatever.com/json";

/// if it retrieved from our JavaScript file cache
//  the network responses for this dynamic content
self.addEventListener("fetch", event => {
  if (event.request.url.indexOf(url) > -1) {
    event.respondWith(
      caches.open(DYNAMIC_CACHE).then(cache => {
        return fetch(event.request).then(response => {
          cache.put(event.request, response.clone());
          return response;
        });
      })
    );
  }
  // is it a file from my static cache?
  else if (STATIC_FILES.includes(event.request.url)) {
    event.respondWith(caches.match(event.request));
  } else {
    event.respondWith(
      // is it available in the static or dynamic cache?
      caches.match(event.request).then(response => {
        if (response) {
          return response;
        } else {
          return fetch(event.request)
            .then(res => {
              return caches.open(DYNAMIC_CACHE).then(cache => {
                cache.put(event.request.url, res.clone());
                return res;
              });
            })
            .catch(err => {
              // check if it is a HMTL file, no use for other files
              if (event.request.headers.get("accept").includes("text/html")) {
                return caches.open(STATIC_CACHE).then(cache => {
                  return cache.match("/offline.html");
                });
              }
              // add more fallbacks, like a default image
            });
        }
      })
    );
  }
});

Network can be faster than cache.
Essentially you can overwrite fresh data with old cached data.

If the network times out it doesn't matter, because we already served it from the cache.
If it is succesful we will overwrite whatever we fetched from the cache.

Cache then Network with Dynamic Caching

You first try to fetch from the cache and if that fails you fall back to the network.

// sw.js

self.addEventListener("fetch", event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      if (response) {
        return response;
      } else {
        return fetch(event.request)
          .then(res => {
            return caches.open("dynamic").then(cache => {
              cache.put(event.request.url, res.clone());
              return res;
            });
          })
          .catch(err => {
            return caches.open("static").then(cache => {
              return cache.match("/offline.html");
            });
          });
      }
    })
  );
});

If a resource is available in the cache we serve it instantly.
In the meantime the Service Worker will also fetch it from the network, save it to the cache and update the website.

Don't cache JSON responses

You don't want to cache JSON responses in the cache storage. It's more appropriate for file cahing like images, JavaScript, HTML and CSS.