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:
- Create a new React PWA using the CRA template
- Customize the manifest.json with your app details
- Add the useOnlineStatus hook to your app
- Implement the UpdateNotification component
- Build and test offline functionality with Chrome DevTools
- 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.