How Service Worker Registration Works for PWAs in Create React App
Service worker registration in Create React App is opt-in and handled by the serviceWorkerRegistration.js helper, which registers the Workbox-generated worker in production only when you call register() instead of unregister() in src/index.js.
Create React App (CRA) provides built-in support for Progressive Web Apps through an opt-in service worker architecture. Understanding how service worker registration works is essential for enabling offline capabilities and update management in your React applications. This guide examines the registration flow, key source files, and Workbox integration based on the actual implementation in the facebook/create-react-app repository.
The Opt-In Architecture of CRA PWA Support
CRA deliberately makes PWA functionality optional to avoid caching complications during development. By default, new projects created with the standard template call serviceWorkerRegistration.unregister() in src/index.js, which ensures no service worker is active.
To enable PWA features, you must either use the cra-template-pwa (or cra-template-pwa-typescript) template during project creation, or manually switch the function call from unregister() to register(). This design ensures that service workers only activate in production builds and never interfere with the development server.
Key Files in the Service Worker Registration Flow
src/service-worker.js
This file contains the actual Workbox service worker script that controls caching strategies. During the build process, the self.__WB_MANIFEST placeholder is replaced with a precache manifest generated by Workbox. You customize caching behavior by editing this file to add runtime caching routes or adjust precache settings.
src/serviceWorkerRegistration.js
This helper module exports two primary functions: register() and unregister(). It handles browser capability detection, production environment validation, and lifecycle event monitoring. The file bridges your application code with the browser's Service Worker API, providing callbacks for update detection and successful installation.
src/index.js
The application entry point imports the registration helper and decides whether to enable PWA functionality. By default, it imports serviceWorkerRegistration and calls unregister(). Changing this single line to register() activates the service worker in production builds.
How the Registration Process Works
When you call serviceWorkerRegistration.register() in src/index.js, the helper executes a specific validation and registration sequence:
-
Environment and Support Checks – The function first verifies
process.env.NODE_ENV === 'production'and'serviceWorker' in navigator. If either check fails, registration aborts silently to prevent development caching or browser incompatibility issues. -
Origin Validation – The helper constructs a URL from
process.env.PUBLIC_URLand validates that it shares the same origin as the current page. Cross-origin service workers are rejected to prevent security violations. -
Worker Registration – Upon passing validation, the code executes:
navigator.serviceWorker .register(`${process.env.PUBLIC_URL}/service-worker.js`) .then(registration => { // Lifecycle monitoring logic }); -
Lifecycle Event Handling – The registration promise resolves to a
ServiceWorkerRegistrationobject. The helper attaches anonupdatefoundlistener to detect when the browser installs a new service worker version. It then monitorsonstatechangeevents on the installing worker to detect when it reaches theinstalledstate. -
Update Detection – When
installingWorker.state === 'installed', the helper checksnavigator.serviceWorker.controller. If a controller exists, the new worker is waiting in the background (indicating an update). If no controller exists, this is the first install, and the content is cached for offline use.
Workbox Integration and Build Process
The service worker registration relies on Workbox integration during the production build. In packages/react-scripts/config/webpack.config.js, CRA includes the WorkboxWebpackPlugin.InjectManifest plugin configuration.
During npm run build, this plugin:
- Compiles
src/service-worker.jsthrough Webpack - Replaces the
self.__WB_MANIFESTplaceholder with a JSON array of URLs to precache (generated from the build assets) - Outputs the final
service-worker.jsto the build directory
The registration URL in serviceWorkerRegistration.js points to ${process.env.PUBLIC_URL}/service-worker.js, which resolves to this Workbox-generated file in the production deployment.
Customizing Registration Behavior
The register() function accepts an optional config object to handle update notifications and successful caching events:
serviceWorkerRegistration.register({
onUpdate: (registration) => {
// Prompt user to refresh for new content
const updateConfirmed = window.confirm('New version available. Reload?');
if (updateConfirmed) {
window.location.reload();
}
},
onSuccess: (registration) => {
// Notify that app works offline
console.log('App is ready for offline use.');
},
});
These callbacks trigger based on the service worker lifecycle states detected during registration.
Unregistering the Service Worker
To disable PWA functionality, call unregister() instead of register():
// src/index.js
serviceWorkerRegistration.unregister();
This executes:
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => registration.unregister())
.catch(error => console.error(error.message));
}
}
Unregistering removes the active service worker, preventing offline caching and background updates. This is the default behavior in CRA to ensure development flexibility.
Summary
- CRA PWA support is opt-in – Service workers only activate when you explicitly call
register()insrc/index.js, preventing caching issues during development. - Registration requires production builds – The
serviceWorkerRegistration.jshelper checksprocess.env.NODE_ENV === 'production'and browser support before executingnavigator.serviceWorker.register(). - Workbox handles precaching – During
npm run build,WorkboxWebpackPlugin.InjectManifestcompilessrc/service-worker.jsand injects the precache manifest atself.__WB_MANIFEST. - Lifecycle monitoring enables updates – The registration helper listens for
onupdatefoundandonstatechangeevents to detect new versions and trigger optionalonUpdateoronSuccesscallbacks. - Unregistering disables the PWA – Calling
unregister()removes the service worker, useful for apps that don't require offline functionality.
Frequently Asked Questions
How do I enable the service worker in my Create React App project?
To enable service worker registration, open src/index.js and change serviceWorkerRegistration.unregister() to serviceWorkerRegistration.register(). This activates the PWA functionality, but only in production builds. The development server (npm start) will never register the service worker to prevent caching issues while you code.
Why is my service worker not registering in development mode?
CRA intentionally disables service worker registration during development. The serviceWorkerRegistration.js helper explicitly checks process.env.NODE_ENV === 'production' before calling navigator.serviceWorker.register(). This prevents stale caches from interfering with live reloading and debugging. To test the service worker, you must run npm run build and serve the production build locally.
How does Create React App generate the service worker file?
During the production build, CRA uses WorkboxWebpackPlugin.InjectManifest (configured in packages/react-scripts/config/webpack.config.js) to process src/service-worker.js. The plugin compiles the source file and replaces the self.__WB_MANIFEST placeholder with a generated list of URLs to precache. The resulting service-worker.js is emitted to the build directory and served from PUBLIC_URL.
How can I notify users when a new version of my PWA is available?
Pass a configuration object with an onUpdate callback to the register() function in src/index.js. When the service worker detects a new version (when installingWorker.state becomes installed and navigator.serviceWorker.controller exists), it invokes your callback with the registration object. You can use this to display a notification or prompt the user to refresh the page to activate the new version.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →