Progressive Web Apps (PWA)

PWAs with React

18 min Lesson 21 of 30

Building PWAs with React

React provides excellent support for building Progressive Web Apps through Create React App's PWA template and modern tooling. Let's explore how to build production-ready PWAs with React.

Create React App PWA Template

Create React App includes a built-in PWA template that sets up service workers and manifest files automatically.

# Create a new React app with PWA template npx create-react-app my-pwa --template cra-template-pwa # Or add PWA to existing TypeScript project npx create-react-app my-pwa --template cra-template-pwa-typescript # Project structure includes: src/ service-worker.js serviceWorkerRegistration.js public/ manifest.json icons/

Service Worker Registration

CRA provides a service worker registration helper that needs to be activated in your index.js file.

// src/index.js import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import * as serviceWorkerRegistration from './serviceWorkerRegistration'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <App /> </React.StrictMode> ); // Register service worker for PWA serviceWorkerRegistration.register({ onSuccess: (registration) => { console.log('PWA installed successfully'); }, onUpdate: (registration) => { // Show update notification to user if (window.confirm('New version available! Reload to update?')) { window.location.reload(); } } });
Important: Service workers only work in production builds. Run npm run build and serve the build folder with a static server to test PWA features.

Custom Service Worker with Workbox

Create React App uses Workbox under the hood. You can customize the service worker by creating a custom configuration.

// src/service-worker.js import { clientsClaim } from 'workbox-core'; import { ExpirationPlugin } from 'workbox-expiration'; import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; import { StaleWhileRevalidate, CacheFirst } from 'workbox-strategies'; clientsClaim(); // Precache all assets generated by build process precacheAndRoute(self.__WB_MANIFEST); // Cache images registerRoute( ({ request }) => request.destination === 'image', new CacheFirst({ cacheName: 'images', plugins: [ new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days }), ], }) ); // Cache API requests registerRoute( ({ url }) => url.pathname.startsWith('/api/'), new StaleWhileRevalidate({ cacheName: 'api-cache', plugins: [ new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 5 * 60, // 5 minutes }), ], }) ); // App shell routing const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); registerRoute( ({ request, url }) => { if (request.mode !== 'navigate') return false; if (url.pathname.startsWith('/_')) return false; if (url.pathname.match(fileExtensionRegexp)) return false; return true; }, createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') ); self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } });

Manifest Configuration

Customize the manifest.json file in the public folder to define your app's appearance and behavior.

// public/manifest.json { "short_name": "React PWA", "name": "My React Progressive Web App", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "logo192.png", "type": "image/png", "sizes": "192x192" }, { "src": "logo512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff", "orientation": "portrait-primary", "categories": ["productivity", "utilities"], "description": "A powerful Progressive Web App built with React" }

Offline Support in React

Implement offline detection and provide feedback to users when they lose connectivity.

// src/hooks/useOnlineStatus.js import { useState, useEffect } from 'react'; export function useOnlineStatus() { const [isOnline, setIsOnline] = useState(navigator.onLine); useEffect(() => { function handleOnline() { setIsOnline(true); } function handleOffline() { setIsOnline(false); } window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []); return isOnline; } // Usage in component import { useOnlineStatus } from './hooks/useOnlineStatus'; function App() { const isOnline = useOnlineStatus(); return ( <div> {!isOnline && ( <div className="offline-banner"> You are currently offline. Some features may be limited. </div> )} <!-- Rest of app --> </div> ); }
Pro Tip: Use React Query or SWR libraries for API caching that works seamlessly with service worker caching strategies.

Update Detection Component

Create a component to handle service worker updates gracefully.

// src/components/UpdateNotification.js import { useState, useEffect } from 'react'; import * as serviceWorkerRegistration from '../serviceWorkerRegistration'; function UpdateNotification() { const [showUpdate, setShowUpdate] = useState(false); const [registration, setRegistration] = useState(null); useEffect(() => { serviceWorkerRegistration.register({ onUpdate: (reg) => { setShowUpdate(true); setRegistration(reg); } }); }, []); const updateApp = () => { if (registration && registration.waiting) { registration.waiting.postMessage({ type: 'SKIP_WAITING' }); window.location.reload(); } }; if (!showUpdate) return null; return ( <div className="update-notification"> <p>A new version is available!</p> <button onClick={updateApp}>Update Now</button> </div> ); } export default UpdateNotification;
Exercise:
  1. Create a new React PWA using the CRA template
  2. Customize the manifest.json with your app details
  3. Add the useOnlineStatus hook to your app
  4. Implement the UpdateNotification component
  5. Build and test offline functionality with Chrome DevTools
  6. Add custom caching for API requests using Workbox
Testing PWA Features: Always test in production mode. Run npm run build, then serve with: npx serve -s build. Open Chrome DevTools → Application tab to inspect service workers, cache storage, and manifest.