Contents
Introduction
This tutorial shows how to turn a WordPress site into a Progressive Web App (PWA) by adding a web app manifest, registering a service worker, and implementing offline mode. It covers manual implementation (recommended for full control) and notes about plugin alternatives. Every code example is included so you can copy/paste into your theme or server root.
Prerequisites
- Site served over HTTPS (required for service workers and many PWA features).
- Ability to edit theme files (child theme recommended) or server root to add files (manifest.json, service-worker.js, offline page).
- WordPress 4.7 recommended familiarity with functions.php, enqueuing scripts, and FTP or hosting file manager.
- Back up your site before making changes.
Overview of the approach
- Create a web app manifest (manifest.json) with icons, name, start URL and scope.
- Register and install a service worker (service-worker.js) that precaches assets and provides runtime caching strategies.
- Add an offline fallback page to display when the network is unavailable.
- Enqueue the manifest and add service worker registration in your theme, avoiding logged-in admin users.
- Test and iterate using Chrome DevTools and Lighthouse.
What youll create
- /manifest.json — web app manifest
- /service-worker.js — service worker registered at root to scope to the entire site
- /offline.html (or offline.php) — offline fallback page cached by the service worker
- functions.php snippets — enqueues and registration script
Create the Web App Manifest
Save a file named manifest.json at your site root or in the theme root and reference it from the site head. The manifest tells the browser how your PWA appears when installed.
{ name: My WordPress Site, short_name: MySite, start_url: /?utm_source=homescreen, scope: /, display: standalone, background_color: #ffffff, theme_color: #2B6CB0, orientation: portrait, icons: [ { src: /wp-content/uploads/icons/icon-192x192.png, sizes: 192x192, type: image/png }, { src: /wp-content/uploads/icons/icon-512x512.png, sizes: 512x512, type: image/png } ] }
Manifest best practices
- Provide at least a 192×192 and 512×512 PNG icon for most platforms.
- Set start_url and scope carefully. If you want the entire site covered, use / for scope and place the manifest at the root or set start_url to /.
- Include theme_color and background_color for a native-like experience and splash screen control.
Add a link to the manifest and common meta tags via your theme. Use functions.php to add them conditionally (for example, skip for logged-in admin users).
Create an offline fallback page
An offline page provides a friendly message and links when the user is offline. Save it as /offline.html at the site root or create a WordPress template in your theme and serve it at /offline.html.
ltmeta charset=utf-8gt ltmeta name=viewport content=width=device-width,initial-scale=1gt lttitlegtOfflinelt/titlegt ltstylegtbody{font-family: system-ui, Arialdisplay:flexalign-items:centerjustify-content:centerheight:100vhmargin:0text-align:center}h1{font-size:1.4rem}p{color:#666}lt/stylegt ltmaingt lth1gtYou are offlinelt/h1gt ltpgtThis content is not available while offline. Please check your connection and try again.lt/pgt ltpgtlta href=/gtReturn to Homelt/agtlt/pgt lt/maingt
Create the service worker (basic cache offline)
Place service-worker.js at the site root so its scope can cover the entire site. The example below uses a simple precache and runtime caching for the REST API and images. Adjust caches and versioning for your setup.
const CACHE_NAME = my-site-cache-v1 const PRECACHE_URLS = [ /, // update if you use start_url with query params /offline.html, /wp-includes/css/style.css, // example, add your main css // add other key assets you want precached ] self.addEventListener(install, event =gt { self.skipWaiting() event.waitUntil( caches.open(CACHE_NAME).then(cache =gt { return cache.addAll(PRECACHE_URLS) }) ) }) self.addEventListener(activate, event =gt { event.waitUntil( (async () =gt { // cleanup old caches const keys = await caches.keys() await Promise.all( keys.map(key =gt { if (key !== CACHE_NAME) { return caches.delete(key) } }) ) // take control immediately await self.clients.claim() })() ) }) self.addEventListener(fetch, event =gt { const request = event.request const url = new URL(request.url) // Handle navigation requests: app shell offline fallback if (request.mode === navigate) { event.respondWith( fetch(request) .then(response =gt { // store navigation responses in cache optionally return response }) .catch(() =gt { return caches.match(/offline.html) }) ) return } // Cache-first for static assets (CSS, JS, images) if (request.destination === style request.destination === script request.destination === image request.destination === font) { event.respondWith( caches.match(request).then(resp =gt { return resp fetch(request).then(fetchResp =gt { return caches.open(CACHE_NAME).then(cache =gt { // put a clone in the cache cache.put(request, fetchResp.clone()) return fetchResp }) }).catch(() =gt { // fallback for images: optional placeholder if (request.destination === image) { return caches.match(/wp-content/uploads/icons/icon-192x192.png) } }) }) ) return } // Network-first for REST API so users get fresh data when online if (url.pathname.startsWith(/wp-json/)) { event.respondWith( fetch(request).then(networkResp =gt { // optionally cache a clone for offline view return caches.open(CACHE_NAME).then(cache =gt { cache.put(request, networkResp.clone()) return networkResp }) }).catch(() =gt { return caches.match(request) }) ) return } // Default behavior: try network, fall back to cache event.respondWith( fetch(request).catch(() =gt caches.match(request)) ) })
Notes on the service worker
- Place the service-worker.js file at your site root (example: https://example.com/service-worker.js) to get scope / so it can cache the entire site. If placed in a subdirectory, scope is limited to that directory.
- Use a cache name with a version. Bump the version (v2) when you update the cache list to force refresh of cached assets.
- Use self.skipWaiting() and self.clients.claim() to make the new service worker take control immediately (careful: this may affect active sessions).
- Be mindful of cache size and expiration. Add logic to purge old cache entries if needed.
Register the service worker in WordPress
Add a registration script to your theme footer or enqueue it so the browser knows to register the service worker. Do not register for admin/back-end pages or for users who are logged in if you plan to avoid caching that content.
ltscriptgt if (serviceWorker in navigator) { window.addEventListener(load, function() { navigator.serviceWorker.register(/service-worker.js) .then(function(reg) { console.log(Service worker registered., reg) }) .catch(function(err) { console.warn(Service worker registration failed:, err) }) }) } lt/scriptgt
Advanced: Use Workbox for easier, robust caching
Workbox provides higher-level tools and plugins to simplify precaching and runtime caching rules. You can use Workbox via CDN import inside your service worker or build pipeline. The example below demonstrates using Workbox (CDN) for common caching strategies.
// service-worker.js using Workbox importScripts(https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js) if (workbox) { workbox.setConfig({debug: false}) // Precaching (an array populated by build tools is typical) workbox.precaching.precacheAndRoute([ {url: /, revision: v1}, {url: /offline.html, revision: v1}, // add other entries or generate via build step ]) // Runtime caching for images - cache-first workbox.routing.registerRoute( ({request}) =gt request.destination === image, new workbox.strategies.CacheFirst({ cacheName: images-cache, plugins: [ new workbox.expiration.ExpirationPlugin({maxEntries: 60, maxAgeSeconds: 30 24 60 60}) ] }) ) // Network-first for API workbox.routing.registerRoute( ({url}) =gt url.pathname.startsWith(/wp-json/), new workbox.strategies.NetworkFirst({ cacheName: api-cache, networkTimeoutSeconds: 3 }) ) // Fallback to offline page for navigation const FALLBACK_HTML_URL = /offline.html workbox.routing.registerRoute( ({request}) =gt request.mode === navigate, new workbox.strategies.NetworkFirst({ cacheName: pages-cache }) ) } else { // Workbox failed to load console.error(Workbox failed to load) }
Handling logged-in users, search, forms and REST API
- Avoid caching sensitive pages (wp-admin, user dashboard, checkout pages) — conditionally skip service worker registration for logged-in users or add logic in fetch handler to bypass caching for URLs containing cookies or nonce-related requests.
- For forms and POST requests, always forward to network service workers should not respond to POST requests with cached GET responses.
- If your theme uses dynamic REST API driven content, use network-first caching for /wp-json/ endpoints so users receive fresh content when online and cached content when offline.
Testing and debugging
- Open Chrome DevTools gt Application gt Service Workers to view registration, scope, and update the worker.
- Use Application gt Clear storage gt Clear site data while testing to reset caches.
- Use the Network tab to set Offline or Slow 3G and test offline behavior and fallbacks.
- Run Lighthouse (Chrome DevTools Audits / Lighthouse) to measure PWA score and see required changes (manifest present, service worker registered, works offline, start_url reachable, icons included).
- Test on an Android device: open site in Chrome, then use the browser menu gt Add to Home screen to see installation and splash behavior.
Service worker update strategy
When you update the service worker code or the precache list, bump the CACHE_NAME or revision so clients pick up the new worker. Typical pattern:
// change cache name when assets change const CACHE_NAME = my-site-cache-v2 // increment v1 -> v2 // rest of service-worker.js logic remains
After deployment, the browser downloads the new service worker and installs it. If you use skipWaiting / clients.claim you can force immediate activation, but this may interrupt open pages. Otherwise, the new worker takes control on the next page load.
Common pitfalls and troubleshooting
- Service worker scope: file location determines scope. Place at root for site-wide scope.
- HTTPS required: service workers wont register on plain HTTP (except localhost).
- MIME types and path issues: ensure manifest.json is reachable at the specified path and served with application/json or proper headers.
- Manifest or icon 404s: check console for manifest errors and correct icon paths and sizes.
- Caching wrong responses: be careful caching HTML for logged-in users test with different user states.
- Large caches: implement cache expiration to avoid filling storage quotas.
Plugin alternatives (if you prefer a plugin)
- PWA (by PWA Plugin): Easy setup, includes manifest and service worker generation. Quick to start, less control than manual approach.
- PWA for WP AMP: More features like offline content caching, custom offline pages, push notification integrations (some features may require paid addons).
- Super Progressive Web Apps: Simple UI to configure manifest and service worker. Good for quick deployment.
Plugins are useful for rapid setup but review generated service worker code and caching strategies. Manual approach offers full control and better long-term maintainability.
Security, privacy and SEO considerations
- Do not cache private or personal data. Avoid caching responses that contain user-specific info or cookies unless you handle authentication-specific flows carefully.
- Ensure REST API endpoints that expose sensitive information are not unintentionally served from cache to other users.
- Search engines index the site as usual an offline page does not replace your live content for crawlers — ensure the start_url and content are reachable by crawlers when online.
Checklist before going live
- HTTPS enabled and verified (Lets Encrypt or hosting provider).
- manifest.json placed and accessible at the declared URL icons available and valid sizes.
- service-worker.js placed at the root (or appropriate scope) and successfully registered in browser.
- Offline fallback page created and included in precache.
- Logged-in user handling tested and configured (skip registration or special behavior).
- Tested with Chrome DevTools: offline behavior works, Lighthouse PWA checks pass or show clear actionable items.
- Monitoring in place: use analytics or logs to check installs and offline usage (respecting privacy laws).
Useful debugging tips
- Open the console to see service worker registration logs printed from the registration script.
- In DevTools Application gt Service Workers, you can Update the service worker and Skip waiting.
- Inspect caches in Application gt Cache Storage to view cached assets and their contents.
- Use fetch logging inside service-worker.js during development to understand which paths are matched.
Final implementation example — minimal end-to-end summary
Files to add at the site root:
- /manifest.json (see manifest example above)
- /service-worker.js (see service worker example above)
- /offline.html (offline fallback page)
functions.php additions:
ltscriptgt if (serviceWorker in navigator) { window.addEventListener(load, function() { navigator.serviceWorker.register(/service-worker.js) }) } lt/scriptgt
Conclusion
Implementing a PWA on WordPress provides offline support, improved mobile UX, and the ability for users to install your site. Start with a manifest and a simple service worker to cache key assets and an offline page, then iterate: add runtime caching strategies, use Workbox for advanced needs, and test thoroughly. Keep security and user privacy in mind and avoid caching sensitive user data.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |