Skip to content

Workbox — Google's Service Worker Library for Production PWAs

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Workbox. We cover key concepts, practical examples, and best practices to help you master this topic.

Workbox is Google's JavaScript library for adding offline support to web applications, simplifying service worker creation with pre-built caching strategies, runtime tools, and build integration.

What You'll Learn

By the end of this tutorial, you will understand what Workbox offers, how to set it up using both the CLI and manual approach, and how to configure its pre-built strategies and plugins.

Why It Matters

Writing service workers manually is error-prone. Cache management, versioning, and Strategy implementation require careful attention to edge cases. Workbox handles these details for you, letting you configure strategies declaratively instead of writing event handlers from scratch.

Real-World Use

A large e-commerce site migrated from a manually written service worker to Workbox. The manual worker had 800 lines of caching logic with 12 bugs. The Workbox version: 50 lines of configuration. Bug reports dropped to zero, and the team gained confidence to add offline support for checkout.

Workbox Overview

Workbox Architecture
    ┌──────────────────────────────────────────────────────┐
    │                    Build Process                     │
    │  workbox-build / workbox-webpack-plugin               │
    │  Generates service worker with precache manifest     │
    └──────────────────────┬───────────────────────────────┘
                           ↓
    ┌──────────────────────────────────────────────────────┐
    │              Generated Service Worker                │
    ├────────────────────┬────────────────────────────────┤
    │  Precache          │  Runtime Caching                │
    │  (install event)   │  (fetch event strategies)       │
    ├────────────────────┼────────────────────────────────┤
    │  - Inject manifest │  - StaleWhileRevalidate         │
    │  - Cache first     │  - NetworkFirst                 │
    │  - Versioned       │  - CacheFirst                   │
    │    assets          │  - NetworkOnly                  │
    └────────────────────┴────────────────────────────────┘

Think of Workbox like a power drill versus a manual screwdriver. You can drive a screw by hand (manual service worker) — it works, but it is slow and tiring. A power drill (Workbox) does the same job faster, more consistently, and with less effort.

Setting Up Workbox with Webpack

// webpack.config.js
const { InjectManifest } = require('workbox-webpack-plugin');

module.exports = {
    // ... other config
    plugins: [
        new InjectManifest({
            swSrc: './src/sw.js',
            swDest: 'sw.js',
            maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5MB
        })
    ]
};

Manual Workbox Setup

// sw.js with Workbox
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import {
    StaleWhileRevalidate,
    NetworkFirst,
    CacheFirst,
    NetworkOnly
} from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

// Precache all assets generated by build
precacheAndRoute(self.__WB_MANIFEST);

// Cache Google Fonts with CacheFirst
registerRoute(
    ({ url }) => url.origin === 'https://fonts.googleapis.com' ||
                url.origin === 'https://fonts.gstatic.com',
    new CacheFirst({
        cacheName: 'google-fonts',
        plugins: [
            new CacheableResponsePlugin({ statuses: [0, 200] }),
            new ExpirationPlugin({
                maxEntries: 20,
                maxAgeSeconds: 60 * 60 * 24 * 365 // 1 year
            })
        ]
    })
);

// Cache images with CacheFirst and expiration
registerRoute(
    ({ request }) => request.destination === 'image',
    new CacheFirst({
        cacheName: 'images',
        plugins: [
            new ExpirationPlugin({
                maxEntries: 60,
                maxAgeSeconds: 60 * 60 * 24 * 30 // 30 days
            })
        ]
    })
);

// Cache API responses with StaleWhileRevalidate
registerRoute(
    ({ url }) => url.pathname.startsWith('/api/'),
    new StaleWhileRevalidate({
        cacheName: 'api-cache',
        plugins: [
            new ExpirationPlugin({
                maxEntries: 100,
                maxAgeSeconds: 60 * 60 // 1 hour
            }),
            new CacheableResponsePlugin({ statuses: [0, 200] })
        ]
    })
);

// Network first for navigation
registerRoute(
    ({ request }) => request.mode === 'navigate',
    new NetworkFirst({
        cacheName: 'pages',
        plugins: [
            new ExpirationPlugin({
                maxEntries: 50,
                maxAgeSeconds: 60 * 60 * 24 // 1 day
            })
        ]
    })
);

// Analytics: network only
registerRoute(
    ({ url }) => url.origin === 'https://analytics.example.com',
    new NetworkOnly()
);

Workbox Precaching

Workbox precaching generates a manifest of all build assets and injects it into the service worker:

// The manifest is automatically generated
// self.__WB_MANIFEST is replaced during build with:
// [{url: '/index.html', revision: 'abc123'}, ...]

// precacheAndRoute handles install, activate, and fetch
// for all listed assets
precacheAndRoute(self.__WB_MANIFEST, {
    // Ignore certain URL parameters
    ignoreURLParametersMatching: [/utm_/],
    // Clean outdated precaches automatically
    cleanURLs: true
});

Runtime Caching Plugins

Workbox plugins add advanced behavior without writing custom logic:

import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { BackgroundSyncPlugin } from 'workbox-background-sync';

// Background sync for failed POST requests
registerRoute(
    ({ url }) => url.pathname.startsWith('/api/feedback'),
    new NetworkOnly({
        plugins: [
            new BackgroundSyncPlugin('feedback-queue', {
                maxRetentionTime: 24 * 60 // Retry for 24 hours
            })
        ]
    }),
    'POST'
);

// Cache with size and age limits
registerRoute(
    ({ request }) => request.destination === 'image',
    new CacheFirst({
        cacheName: 'optimized-images',
        plugins: [
            new CacheableResponsePlugin({ statuses: [0, 200] }),
            new ExpirationPlugin({
                maxEntries: 100,
                maxAgeSeconds: 60 * 60 * 24 * 30,
                // Purge excess entries when cache exceeds max
                purgeOnQuotaError: true
            })
        ]
    })
);

Workbox with Vite

// vite.config.js
import { VitePWA } from 'vite-plugin-pwa';

export default {
    plugins: [
        VitePWA({
            registerType: 'autoUpdate',
            workbox: {
                globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
                runtimeCaching: [
                    {
                        urlPattern: /^https:\/\/api\.example\.com\/.*/i,
                        handler: 'StaleWhileRevalidate',
                        options: {
                            cacheName: 'api-cache',
                            expiration: {
                                maxEntries: 50,
                                maxAgeSeconds: 60 * 60
                            }
                        }
                    }
                ]
            }
        })
    ]
};

Testing Workbox

// In browser console
navigator.serviceWorker.register('/sw.js').then(reg => {
    console.log('Workbox SW registered:', reg.scope);
    console.log('Using Workbox version:', self.workbox ? '4.x+' : 'unknown');
});

// Check Workbox cache storage
caches.open('workbox-precache-v2').then(cache => {
    cache.keys().then(keys => {
        console.log('Workbox precached files:', keys.length);
        keys.forEach(req => console.log(' -', req.url));
    });
});

Common Mistakes

  1. Not generating the precache manifest. Workbox requires self.__WB_MANIFEST to be replaced during build. Running the service worker directly without build will fail.
  2. Confusing Workbox strategies with manual caching. You do not need both. Either use Workbox entirely or manual strategies. Mixing them causes unpredictable behavior.
  3. Forgetting importScripts. Workbox modules must be imported. In module-based service workers, use ES module imports. In classic scripts, use importScripts().
  4. Missing plugins for cache management. Without ExpirationPlugin, caches grow unbounded. Always set maxEntries or maxAgeSeconds.
  5. Not handling opaque responses. Cross-origin responses without CORS headers are opaque (status 0). CacheableResponsePlugin with {statuses: [0, 200]} handles these.

Practice Questions

  1. What does the InjectManifest plugin do in a Workbox setup?
  2. How does Workbox simplify the install, activate, and fetch event handling?
  3. What is the purpose of the ExpirationPlugin in Workbox?
  4. How do you configure different caching strategies for different resource types?
  5. Why do you still need a build step when using Workbox?

Challenge: Set up Workbox in a simple Vite project with: precaching for all build assets, CacheFirst for images with 30-day expiration, StaleWhileRevalidate for API calls with 100 entry limit, and NetworkFirst for navigation with 1-day expiration. Verify using DevTools.

FAQ

Do I need to use a build tool with Workbox?

Yes, Workbox requires a build step to generate the precache manifest (self.__WB_MANIFEST). Use workbox-webpack-plugin, workbox-build, or vite-plugin-pwa.

Can Workbox be used without a framework?

Yes. Workbox works with any web application. Use workbox-build CLI to generate the service worker or use the workbox-window module to register it.

Is Workbox still maintained?

Yes, Workbox is maintained by the Chrome team. Version 7 is current and receives regular updates. It is the recommended approach for adding service workers.

Does Workbox handle service worker updates?

Yes. Workbox includes update handling through workbox-window's 'updatefound' and 'waiting' events. The generating service worker also calls skipWaiting() when configured.

Can I add custom logic alongside Workbox?

Yes. Workbox handles precaching and runtime caching, but you can add custom event listeners for push notifications, background sync, and message events alongside Workbox.

Mini Project

Create a PWA using Workbox with Vite. Configure: precaching for all static assets, CacheFirst for images (max 50, 7-day expiry), StaleWhileRevalidate for API calls (max 100, 1-hour expiry), NetworkFirst for navigation, and BackgroundSync for form submissions. Verify all strategies work using DevTools.

What's Next

You now know Workbox. Next, learn about precaching strategies in depth and how to choose what to pre-cache for optimal performance.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro