consolelog.tools

Service Worker Generator

Generate a real, modern service worker with per-route caching rules (static assets → cache-first, /api/ → network-first, HTML → stale-while-revalidate), an app-shell precache, an offline fallback page, cache versioning with automatic cleanup, and skipWaiting / clients.claim toggles — plus the registration snippet with an update-available flow and an unregister snippet.

About this ToolHow it works, benefits & use cases

Generate a complete, modern service worker with per-route caching rules instead of a single global strategy. You build an ordered route table where each rule maps a URL pattern — a glob like *.css or a path substring like /api/ — to one of five strategies: cache-first, network-first, stale-while-revalidate, network-only or cache-only. Each request is matched top-to-bottom and the first matching rule wins; anything unmatched falls through to a configurable default strategy. You also define an app-shell precache (one URL per line, cached during install), name the cache and give it a numeric version (combined into a name-vN key), and optionally enable an offline fallback page that is served for navigations when both network and cache miss — it is automatically added to the precache. Lifecycle toggles control skipWaiting(), clients.claim() and old-cache cleanup on activate. The output is split across three live tabs: the sw.js itself, a registration snippet (with an optional update-available flow and configurable SW path), and an unregister snippet. Everything updates as you edit, with copy, download and shareable-URL support.

How to Use

  1. 1Set the cache name and version (bump the version to invalidate old caches), then list app-shell URLs to precache, one per line.
  2. 2Add route rules mapping a URL pattern (e.g. *.css or /api/) to a strategy; rules are matched top-to-bottom, first match wins.
  3. 3Pick a default strategy for requests that no rule matches.
  4. 4Optionally enable the offline fallback page and set lifecycle toggles for skipWaiting, clients.claim, cache cleanup and the update flow.
  5. 5Switch between the sw.js, Registration and Unregister tabs, then copy, download or share the generated code.

Key Benefits

  • Per-route caching rules with glob or path-substring patterns, matched first-match-wins
  • Five strategies (cache-first, network-first, stale-while-revalidate, network-only, cache-only) plus a configurable default
  • App-shell precache list cached on install, with the offline page auto-added
  • Offline fallback page served for navigations when both network and cache miss
  • Versioned cache name with automatic cleanup of stale caches on activate
  • Individually toggleable skipWaiting(), clients.claim() and update-available registration flow
  • Three live tabs (sw.js, registration, unregister) with copy, download and shareable-URL output

Common Use Cases

  • Adding offline support and an app shell to a Progressive Web App
  • Caching hashed static assets cache-first while keeping /api/ network-first in one worker
  • Serving HTML stale-while-revalidate for instant loads that refresh in the background
  • Showing a friendly offline.html for navigations when the user loses connectivity
  • Producing a dependency-free starter sw.js plus its registration code to adapt before reaching for Workbox

Save at your scope root — updates live as you edit

/**
 * Service Worker — generated by consolelog.tools
 * Strategy: per-route caching with a network-first default.
 *
 * Save this file as '/offline.html' sibling, sw.js at the
 * scope root you want it to control, then register it (see the registration
 * snippet). Requires HTTPS (localhost is exempt).
 */
const CACHE_NAME = 'my-app-v1';
const PRECACHE_URLS = [
  "/",
  "/index.html",
  "/styles/main.css",
  "/scripts/app.js",
  "/manifest.webmanifest",
  "/offline.html"
];
const OFFLINE_URL = '/offline.html';

// ── Install: precache the app shell ──────────────────────────────────────────
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)),
  );
  // Activate this worker as soon as it finishes installing.
  self.skipWaiting();
});

// ── Activate: drop stale caches from previous versions ───────────────────────
self.addEventListener('activate', (event) => {
  event.waitUntil(
    (async () => {
      const keys = await caches.keys();
      await Promise.all(
        keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)),
      );
      // Take control of open clients without requiring a reload.
      await self.clients.claim();
    })(),
  );
});

// Only responses worth caching: ok, basic/cors, and not partial (206).
function isCacheable(response) {
  return (
    response &&
    response.status === 200 &&
    (response.type === 'basic' || response.type === 'cors')
  );
}

// Compile a route pattern to a matcher. Globs (with *) become RegExps;
// everything else is a substring test against the pathname (or full URL when
// the pattern includes a scheme).
function compileMatcher(pattern) {
  if (pattern.includes('*')) {
    const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
    const re = new RegExp('^' + escaped + '$');
    return (url) => re.test(url.pathname) || re.test(url.href);
  }
  if (pattern.includes('://')) {
    return (url) => url.href.includes(pattern);
  }
  return (url) => url.pathname.includes(pattern);
}

async function staleWhileRevalidate(request) {
  const cache = await caches.open(CACHE_NAME);
  const cached = await cache.match(request);
  const network = fetch(request)
    .then((response) => {
      if (isCacheable(response)) cache.put(request, response.clone());
      return response;
    })
    .catch(async () => {
    if (request.mode === 'navigate') {
      const fallback = await caches.match(OFFLINE_URL);
      if (fallback) return fallback;
    }
      return cached || Response.error();
    });
  return cached || network;
}

async function networkFirst(request) {
  const cache = await caches.open(CACHE_NAME);
  try {
    const response = await fetch(request);
    if (isCacheable(response)) cache.put(request, response.clone());
    return response;
  } catch (error) {
    const cached = await cache.match(request);
    if (cached) return cached;
    if (request.mode === 'navigate') {
      const fallback = await caches.match(OFFLINE_URL);
      if (fallback) return fallback;
    }
    throw error;
  }
}

async function cacheFirst(request) {
  const cache = await caches.open(CACHE_NAME);
  const cached = await cache.match(request);
  if (cached) return cached;
  try {
    const response = await fetch(request);
    if (isCacheable(response)) cache.put(request, response.clone());
    return response;
  } catch (error) {
    if (request.mode === 'navigate') {
      const fallback = await caches.match(OFFLINE_URL);
      if (fallback) return fallback;
    }
    throw error;
  }
}

// Ordered route table — the first matching rule handles the request.
const ROUTES = [
  { pattern: '/', handler: staleWhileRevalidate },
  { pattern: '/api/', handler: networkFirst },
  { pattern: '*.css', handler: cacheFirst },
  { pattern: '*.js', handler: cacheFirst },
  { pattern: '*.{png,jpg,jpeg,svg,webp,gif,ico}', handler: cacheFirst },
].map((route) => ({ match: compileMatcher(route.pattern), handler: route.handler }));

async function defaultHandler(request) {
  return networkFirst(request);
}

// ── Fetch: dispatch each request to its matching strategy ────────────────────
self.addEventListener('fetch', (event) => {
  const { request } = event;
  // Never intercept non-GET (POST/PUT/etc.) — let them hit the network.
  if (request.method !== 'GET') return;

  const url = new URL(request.url);
  const route = ROUTES.find((r) => r.match(url));
  event.respondWith((route ? route.handler : defaultHandler)(request));
});

Cache

Versioned as name-vN at runtime

Bump to invalidate old caches

Precache (app shell)

URLs cached during install — one per line. These are the files needed to render your shell offline.

5 URLs will be precached.

Route rules (5)

Each request is matched against rules top-to-bottom; the first match wins. Use * globs (e.g. *.css) or a path substring (e.g. /api/).

API calls and dynamic content that needs to be fresh

Offline fallback

Auto-added to the precache so it's available offline

Lifecycle & registration

Detects a waiting worker and reloads once it takes control

Determines the SW scope — keep it at the root to control the whole origin

Caching strategies & gotchas

  • Cache-first — serve from cache, fall back to network. Best for hashed/static assets (CSS, JS, images, fonts).
  • Network-first — try the network, fall back to cache. Best for /api/ and dynamic data that must stay fresh.
  • Stale-while-revalidate — serve cache instantly and refresh in the background. A good default for HTML/navigations.
  • Network-only / cache-only — never cache / never hit the network. Use sparingly for sensitive or fully-offline routes.

Gotchas: service workers require HTTPS (localhost is exempt). Scope is limited to the directory the SW is served from, so host sw.js at the site root to control everything. Bump the version on every deploy so the activate step purges stale caches. The generated SW only intercepts GET requests and skips opaque/partial responses.

Was this tool helpful?

Share Your Experience

Help others discover this tool!

Related tools