How to add a basic service worker with Workbox in WordPress in WordPress

Contents

Overview

This article is a complete, detailed tutorial on how to add a basic service worker to a WordPress site using Workbox. It covers concepts, multiple practical methods (build-time generation, CDN import, plugin/mu-plugin techniques, and rewrite approaches), concrete code examples you can paste into your theme/plugin, build scripts, troubleshooting, and production considerations. Follow the sections in order or jump to the area that matches your environment.

Why add a service worker and why use Workbox?

  • Service worker purpose: service workers run in the browser and let you intercept network requests, provide offline responses, apply caching strategies, show offline fallbacks, and power background features.
  • Workbox advantages: Workbox is a set of libraries and build tools from Google that simplify creating robust service workers with common caching strategies, precaching, runtime caching, background sync, and more. It reduces boilerplate and helps avoid common pitfalls.
  • WordPress specifics: WordPress themes and plugins live under directories like /wp-content/ so placing a service worker there will limit its scope. For the SW to control the entire site, it is usually best to serve the service worker from the site root (e.g., /sw.js) or ensure its scope covers the intended paths.

Prerequisites and constraints

  • Site served over HTTPS (or localhost during development). Service workers require secure context.
  • Ability to add files to your theme/plugin or write to the WordPress root (ABSPATH). If you cant write to root, see the rewrite or PHP-serving approach below.
  • Node.js npm if you plan to use Workbox CLI, build tools, or webpack.
  • Familiarity with WordPress functions.php and registering/enqueuing scripts is helpful.

High-level approaches

  1. Build-time generation and place sw.js in the site root (recommended for full-site control): Use Workbox CLI, workbox-build, or webpack plugin to generate sw.js and write it to ABSPATH sw.js.
  2. Use Workbox CDN from a served sw file: Create a sw.js that imports Workbox from the CDN using importScripts and place it at the root or ensure scope via rewrite.
  3. Serve sw.js dynamically via PHP rewrite: Add an .htaccess rewrite or route that returns JavaScript from a plugin file when /sw.js is requested.
  4. Write the file to the root via a mu-plugin or plugin activation hook: If ABSPATH is writable, generate or write sw.js programmatically.

Important scope and placement notes

  • Scope: The scope of a service worker is determined by its URL. A service worker at /sw.js controls the entire origin. A service worker at /wp-content/themes/mytheme/sw.js would control only that directory and below by default.
  • Registration path: When registering in JavaScript, the first argument to navigator.serviceWorker.register() dictates the service worker file. It must be accessible and served with the correct MIME type.
  • MIME type: Ensure the response for sw.js has Content-Type: application/javascript.

Method A — Simple CDN-imported sw.js placed in site root

This approach is quick for prototyping: create a file named sw.js at the WordPress root (ABSPATH) and import Workbox from the CDN. Then register the SW in the frontend via a small JS file enqueued in WordPress.

1) sw.js (root)

Example using Workbox CDN, precaching, runtime caching, and immediate activation.

importScripts(https://storage.googleapis.com/workbox-cdn/releases/6.6.2/workbox-sw.js)

if (workbox) {
  workbox.core.setCacheNameDetails({
    prefix: mywp,
    suffix: v1,
    precache: precache,
    runtime: runtime
  })

  // Ensure the SW takes control immediately on update
  workbox.core.skipWaiting()
  workbox.core.clientsClaim()

  // Precache manifest will be injected by build tools use an empty array for manual example
  workbox.precaching.precacheAndRoute(self.__WB_MANIFEST  [])

  // Runtime route for CSS and JS (StaleWhileRevalidate)
  workbox.routing.registerRoute(
    ({request}) => request.destination === script  request.destination === style,
    new workbox.strategies.StaleWhileRevalidate({
      cacheName: mywp-runtime-assets
    })
  )

  // Runtime route for images (CacheFirst with maxEntries and maxAge)
  workbox.routing.registerRoute(
    ({request}) => request.destination === image,
    new workbox.strategies.CacheFirst({
      cacheName: mywp-images,
      plugins: [
        new workbox.expiration.ExpirationPlugin({
          maxEntries: 50,
          maxAgeSeconds: 30  24  60  60 // 30 Days
        })
      ]
    })
  )

  // Offline fallback for navigation requests (simple HTML fallback)
  workbox.routing.registerRoute(
    ({request}) => request.mode === navigate,
    async ({event}) => {
      try {
        return await workbox.strategies.NetworkFirst({
          cacheName: mywp-pages
        }).handle({event})
      } catch (err) {
        return caches.match(/offline.html)
      }
    }
  )

} else {
  console.error(Workbox failed to load)
}

2) sw-register.js (enqueue this in footer)

Small script that registers the service worker from the client side. Put this in your theme and enqueue it using functions.php (example below).

if (serviceWorker in navigator) {
  window.addEventListener(load, function() {
    navigator.serviceWorker.register(/sw.js, {scope: /})
      .then(function(reg) {
        console.log(SW registered, reg)
      }).catch(function(err) {
        console.error(SW registration failed:, err)
      })
  })
}

3) Enqueue the registration script from functions.php

add_action(wp_enqueue_scripts, function() {
  wp_enqueue_script(
    sw-register,
    get_stylesheet_directory_uri() . /sw-register.js,
    array(),
    null,
    true
  )
})

Notes

  • If your host prevents writing to ABSPATH, see the rewrite method below.
  • Update the Workbox CDN version to the latest stable when implementing.

Method B — Build-time generation with Workbox CLI or workbox-build (recommended for production)

Use Workbox tools to generate a precache manifest that lists your assets, then create a sw.js that includes that manifest (inject manifest) or generate a full SW file to place at the site root.

Install Workbox

npm install --save-dev workbox-cli
# or for programmatic usage
npm install --save-dev workbox-build

Option 1: Workbox CLI config (workbox-config.js)

module.exports = {
  globDirectory: ./,
  globPatterns: [
    wp-content/themes/mytheme//.css,
    wp-content/themes/mytheme//.js,
    wp-content/themes/mytheme//.png,
    wp-content/themes/mytheme//.jpg,
    wp-content/themes/mytheme//.svg,
    index.php,
    offline.html
  ],
  swDest: ./sw.js,
  maximumFileSizeToCacheInBytes: 5  1024  1024
}

Run the CLI:

npx workbox generateSW workbox-config.js

That creates sw.js at your repository root. Deploy sw.js to your WordPress site root so the scope is correct (for example, upload to public_html/sw.js or write it during your deploy process).

Option 2: workbox-build in a Node build script (customizable)

const {generateSW} = require(workbox-build)

generateSW({
  globDirectory: ./,
  globPatterns: [
    wp-content/themes/mytheme//.{js,css,png,jpg,svg},
    index.php,
    offline.html
  ],
  swDest: ./sw.js,
  runtimeCaching: [{
    urlPattern: //wp-json//,
    handler: NetworkFirst,
    options: {
      cacheName: wp-api-cache,
      expiration: {
        maxEntries: 50,
        maxAgeSeconds: 5  60
      }
    }
  }]
}).then(({count, size}) => {
  console.log(Generated sw.js, precached {count} files, {size} bytes)
})

Where to place the generated sw.js on your WordPress server

  • Place sw.js in the WordPress site root (ABSPATH). Many deploy pipelines can copy it there as part of the build/deploy.
  • If you cannot place files at root, consider the rewrite or dynamic delivery method below.

Method C — Serve sw.js dynamically via PHP .htaccess rewrite

If you cannot write to the site root but can add files in plugin directories and can edit .htaccess, you can rewrite requests for /sw.js to a PHP script that emits JavaScript. Make sure the PHP script sets the Content-Type header to application/javascript.

.htaccess example (for Apache)

# Place this above the WordPress front-controller rules
RewriteRule ^sw.js /wp-content/plugins/my-sw-plugin/sw.php [L]

sw.php in your plugin


Important

  • Even though the file is served via PHP, the requested URL is /sw.js so the SW scope can be root.
  • Be careful with output buffering or plugins that add content to every request — the response must be pure JavaScript and valid JS.

Method D — Write sw.js to the root from a mu-plugin or plugin activation

If PHP has filesystem access to ABSPATH (the WordPress root), a plugin or mu-plugin can write sw.js during activation or on-demand. Ensure you only do this if you trust write permissions and update the file when you update the SW logic.


Security note

  • Only write files this way if you control the server and can ensure correct permissions and security. Avoid arbitrary file writes on shared-hosting without proper checks.

Registering the service worker (client-side) — best practices

  • Register the service worker from a script enqueued in the footer so that registration happens after page load.
  • Use navigator.serviceWorker.register(/sw.js, {scope: /}) to ensure scope if the file is at root. If using a different path, set scope accordingly but remember scope cannot exceed the SW file path.
  • Use workbox-window for a richer lifecycle and update handling (recommended).

Example registration using workbox-window (enqueue workbox-window from CDN)

import { Workbox } from https://storage.googleapis.com/workbox-cdn/releases/6.6.2/workbox-window.prod.mjs

if (serviceWorker in navigator) {
  const wb = new Workbox(/sw.js, { scope: / })

  wb.addEventListener(installed, (event) => {
    if (event.isUpdate) {
      // Notify user or auto-skip waiting
      console.log(New content available, prompt user to refresh.)
    } else {
      console.log(Content cached for offline use.)
    }
  })

  wb.register().catch(err => console.error(Workbox registration failed:, err))
}

Workbox strategies and examples

  • precacheAndRoute() — precaches a list of files with hashed revisions and serves them from the cache. Use for theme assets and core pages you want available offline.
  • NetworkFirst — best for HTML pages and API calls where fresh content is desired but fallback to cache if offline.
  • StaleWhileRevalidate — serve cached version immediately and update the cache in the background. Good for CSS/JS assets.
  • CacheFirst — serve cached resources first useful for images and static assets with long TTLs.

Example runtime caching routes (Workbox v6 syntax)

// inside sw.js after importing workbox-sw.js
workbox.routing.registerRoute(
  ({request}) => request.destination === image,
  new workbox.strategies.CacheFirst({
    cacheName: images-cache,
    plugins: [
      new workbox.expiration.ExpirationPlugin({
        maxEntries: 100,
        maxAgeSeconds: 60  60  24  30
      })
    ]
  })
)

workbox.routing.registerRoute(
  ({url}) => url.pathname.startsWith(/wp-json/),
  new workbox.strategies.NetworkFirst({
    cacheName: api-cache,
    networkTimeoutSeconds: 3
  })
)

Offline fallback example

Include an offline.html page in your precache list and return it for navigation failures.

// sw.js excerpt
workbox.precaching.precacheAndRoute([
  { url: /offline.html, revision: 1 },
  // ... other files
])

workbox.routing.registerRoute(
  ({request}) => request.mode === navigate,
  async ({event}) => {
    try {
      return await workbox.strategies.NetworkFirst({
        cacheName: pages,
      }).handle({event})
    } catch (err) {
      return caches.match(/offline.html)
    }
  }
)

Using Workbox with webpack (theme or plugin build)

If your theme uses webpack to build assets, use the WorkboxWebpackPlugin. Two common patterns:

  • GenerateSW — Workbox generates a complete sw.js for you with precache manifest and runtimeCachings.
  • InjectManifest — You author a custom sw-source.js (with import workbox libraries) and Workbox injects the precache manifest into it. This offers more control for custom logic.

Example webpack plugin usage (GenerateSW)

const { GenerateSW } = require(workbox-webpack-plugin)

module.exports = {
  // ... other webpack config ...
  plugins: [
    new GenerateSW({
      clientsClaim: true,
      skipWaiting: true,
      exclude: [/.map/, /asset-manifest.json/],
      runtimeCaching: [{
        urlPattern: /.(?:pngjpgjpegsvggif)/,
        handler: CacheFirst,
        options: {
          cacheName: images,
          expiration: { maxEntries: 60, maxAgeSeconds: 30  24  60  60 }
        }
      }]
    })
  ]
}

Cache versioning and invalidation

  • Workbox injects revisioned filenames into the precache manifest so cache busting occurs when filenames or revisions change.
  • If you hand-write caches, include a cache name version string and update it when you deploy, then use a service worker activate event to delete old caches.
  • Be careful with long cache TTLs — ensure you have a strategy to update or expire caches when content changes.

Helpful command examples

# Generate a service worker using Workbox CLI
npx workbox generateSW workbox-config.js

# Run a Node script that uses workbox-build to generate sw.js
node scripts/generate-sw.js

# Run webpack build which includes GenerateSW
npm run build

Troubleshooting

  1. Service worker not registered / 404: Verify sw.js is accessible at the path you register. Use the browser devtools Application tab > Service Workers and check registration attempts and network response.
  2. Wrong MIME type: Ensure the response has Content-Type: application/javascript browsers may reject service workers served with text/html.
  3. Scope limited unexpectedly: If sw.js is in /wp-content/…, it cannot control URLs above its directory. Place sw.js at root or rewrite to root.
  4. Mixed content / HTTP: Service workers require HTTPS (except localhost). Use HTTPS in staging and production.
  5. File overwritten by WordPress or plugins: If a plugin or theme deploy overwrites sw.js, include SW generation in your deploy pipeline or plugin activation logic.
  6. Caching old files after deployment: Use skipWaiting() and clientsClaim() patterns plus update notifications via workbox-window to prompt users to refresh.

Testing and verification

  • Open DevTools → Application → Service Workers to confirm the service worker is installed, activated, and controlling pages.
  • Use the Offline checkbox in the Network panel to test offline behavior and fallbacks.
  • Inspect the Cache Storage pane to view cached entries created by Workbox or your runtime caches.
  • Use Lighthouse to validate PWA and service worker behaviors Workbox-generated SWs typically improve PWA score for offline-first capabilities.

Security and production considerations

  • A service worker is powerful — it can intercept all fetch requests within scope. Ensure your SW code is secure and does not expose sensitive data.
  • Avoid serving dynamic HTML pages directly through the SW that you havent sanitized. Use caches as intended and maintain correct CORS headers for cross-origin requests.
  • Monitor and log SW lifecycle events during rollout. Consider a staged rollout for large sites to catch unexpected behaviors.

Full minimal example summary (files and where to put them)

File Location (recommended) Purpose
sw.js WordPress root (ABSPATH) -> /sw.js Service worker file (Workbox import or generated content) — controls the site
sw-register.js Theme JS, enqueued via functions.php Registers the service worker on the client
workbox-config.js / build script Theme or project repo (dev tools) Used during build to generate sw.js with precache manifest

Example: put-it-together minimal workflow

  1. Create a build script that outputs sw.js to the project root using workbox-build or Workbox CLI.
  2. During deploy, copy sw.js to the site root (e.g., public_html/sw.js or ABSPATH/sw.js).
  3. Add sw-register.js to your theme and enqueue it via functions.php so it runs in the footer.
  4. Test in the browser, toggle offline in DevTools, verify cached content and fallback behavior.

Final notes (concise)

Workbox simplifies adding resilient caching strategies and offline support to WordPress sites. The key WordPress-specific requirement is ensuring the service worker is served at the desired scope (usually site root). Use Workbox CLI or webpack plugin for production to generate a precache manifest, and register the worker client-side from an enqueued script. If you cannot place files at root, use a rewrite that serves a JS response at /sw.js or write the file during plugin activation if the server permits.

Implement carefully, test thoroughly, and add update and cache invalidation logic for a smooth user experience.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *