Progressive Web Apps (PWAs) bridge the gap between web and native mobile apps, offering features like offline support, installability, and fast load times—all while remaining accessible via a browser. React, with its component-based architecture and robust ecosystem, is an excellent framework for building PWAs. In this guide, we’ll walk through creating a production-ready PWA with React, covering core concepts, tools, and best practices.
Table of Contents
- Introduction to PWAs
- Prerequisites
- Setting Up a React PWA Project
- Understanding PWA Fundamentals
- Step 1: Configure the Web App Manifest
- Step 2: Implement Service Workers
- Step 3: Test with Lighthouse
- Step 4: Add Offline Functionality
- Step 5: Enable Installability
- Advanced PWA Features
- Deployment
- Troubleshooting Common Issues
- Conclusion
- References
Prerequisites
Before starting, ensure you have:
- Node.js (v14+ recommended) and npm/yarn installed.
- Basic knowledge of React (components, hooks, state management).
- A code editor (e.g., VS Code).
- A modern browser (Chrome, Firefox, or Edge) for testing.
Setting Up a React PWA Project
We’ll use create-react-app with the official PWA template to bootstrap our project. This template preconfigures the Web App Manifest and service worker setup.
Step 1: Create the Project
Run the following command in your terminal:
npx create-react-app my-react-pwa --template cra-template-pwa
This creates a new React app with:
- A
public/manifest.jsonfile (for PWA metadata). - A
src/service-worker.jsfile (for offline logic, powered by Workbox). - A
src/serviceWorkerRegistration.jsfile (to register the service worker).
Project Structure
Key files for PWAs in the generated project:
my-react-pwa/
├── public/
│ ├── manifest.json # PWA metadata (icons, name, display mode)
│ ├── icons/ # App icons for different devices
├── src/
│ ├── service-worker.js # Service worker logic (caching, offline support)
│ ├── serviceWorkerRegistration.js # Registers the service worker
│ └── index.js # Entry point (registers the service worker)
Understanding PWA Fundamentals
Before diving into code, let’s clarify the two core technologies powering PWAs:
1. Web App Manifest
A JSON file (manifest.json) that tells the browser about your app’s identity and appearance. It enables features like:
- Adding the app to the home screen.
- Customizing the app’s name, icons, and theme color.
- Defining the display mode (e.g.,
standalonefor a full-screen, app-like experience).
2. Service Workers
A script that runs in the background (separate from the browser tab) to handle:
- Caching assets (HTML, CSS, JS, images) for offline use.
- Intercepting network requests to serve cached content.
- Syncing data in the background when connectivity is restored.
React’s PWA template uses Workbox, a library that simplifies service worker configuration (e.g., precaching static assets, defining caching strategies).
Step 1: Configure the Web App Manifest
The manifest.json file is critical for PWA installability and branding. Let’s customize it for our app.
Default manifest.json
The CRA PWA template generates a basic manifest. Open public/manifest.json and update the fields below:
{
"short_name": "ReactPWA", # Shown on home screen/launcher
"name": "My React PWA", # Full name (e.g., in app stores)
"description": "A sample PWA built with React", # Optional but recommended
"start_url": ".", # URL to load when the app is launched
"display": "standalone", # Display mode: "standalone", "fullscreen", or "minimal-ui"
"background_color": "#ffffff", # Color shown while the app loads
"theme_color": "#4285f4", # Color for browser UI (e.g., address bar)
"icons": [
{
"src": "icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Key Manifest Fields Explained
display: "standalone": Hides the browser toolbar, making the app look like a native app.icons: Provide multiple sizes (72x72 to 512x512) to support different devices (phones, tablets, desktops). Use tools like RealFaviconGenerator to generate icons.theme_colorandbackground_color: Ensure consistency with your app’s design.
Step 2: Implement Service Workers
Service workers are the backbone of offline support. The CRA PWA template uses Workbox to automate caching, but we’ll customize it for our needs.
How Service Workers Work in React
By default, the template uses workbox-precaching to cache static assets (HTML, CSS, JS) during the build process. This ensures assets load quickly and offline.
Registering the Service Worker
The service worker must be registered in your app’s entry point (src/index.js). The template includes this logic via serviceWorkerRegistration.register():
// 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 production (comment out for development)
serviceWorkerRegistration.register();
Customizing the Service Worker
To add runtime caching (e.g., for API responses), modify src/service-worker.js. Workbox provides strategies like StaleWhileRevalidate (serve cached data first, then update) or NetworkFirst (fetch from network, fall back to cache).
Example: Cache API Responses
Add the following to src/service-worker.js to cache data from an API endpoint (e.g., https://api.example.com/data):
// src/service-worker.js
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
// Precache static assets (auto-generated by CRA during build)
precacheAndRoute(self.__WB_MANIFEST);
// Cache API responses with StaleWhileRevalidate
registerRoute(
// Match API requests (adjust the URL pattern as needed)
({ url }) => url.pathname.startsWith('/api/'),
new StaleWhileRevalidate({
cacheName: 'api-cache', # Name for the cache storage
})
);
Step 3: Test with Lighthouse
Lighthouse is a tool built into Chrome DevTools that audits PWA compliance, performance, and accessibility. Use it to validate your PWA.
How to Run Lighthouse
- Build your React app for production:
npm run build - Serve the build folder locally (use
servefor simplicity):npx serve -s build - Open Chrome, navigate to
http://localhost:3000, and open DevTools (F12). - Go to the Lighthouse tab, check “Progressive Web App”, and click “Generate report”.
Lighthouse PWA Checklist
A passing PWA score requires:
- A valid Web App Manifest.
- A registered service worker with offline support.
- HTTPS (or
localhostfor development). - Responsive design (mobile-friendly).
- Fast load times (LCP < 2.5s, FID < 100ms).
Step 4: Add Offline Functionality
To test offline support:
- Run
npm run buildandnpx serve -s build. - In Chrome DevTools, go to Application > Service Workers, check “Offline”, and refresh the page.
Your app should load even without internet! For dynamic content (e.g., user data), use Workbox strategies like NetworkFirst to fetch fresh data when online and fall back to cached data when offline.
Step 5: Enable Installability
Users can install your PWA to their home screen if it meets these criteria:
- A valid
manifest.jsonwithdisplay: standalone. - A registered service worker.
- Served over HTTPS (or
localhostfor development).
Triggering the Install Prompt
Browsers automatically show an “Install” prompt when criteria are met, but you can customize the timing (e.g., after the user interacts with your app).
Add this code to src/App.js to capture the install prompt:
// src/App.js
import { useState, useEffect } from 'react';
function App() {
const [installPrompt, setInstallPrompt] = useState(null);
useEffect(() => {
// Listen for the "beforeinstallprompt" event
const handleBeforeInstallPrompt = (e) => {
e.preventDefault(); // Prevent the browser from showing the default prompt
setInstallPrompt(e); // Store the event to trigger later
};
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
};
}, []);
const handleInstallClick = async () => {
if (!installPrompt) return;
// Show the install prompt
installPrompt.prompt();
const { outcome } = await installPrompt.userChoice;
if (outcome === 'accepted') {
console.log('User installed the app!');
}
setInstallPrompt(null); // Reset the prompt
};
return (
<div>
<h1>My React PWA</h1>
{installPrompt && (
<button onClick={handleInstallClick}>Install App</button>
)}
</div>
);
}
export default App;
Advanced PWA Features
For more functionality, explore these APIs:
Background Sync
Delay API requests until the user is back online. Use workbox-background-sync:
// src/service-worker.js
import { BackgroundSyncPlugin } from 'workbox-background-sync';
registerRoute(
({ url }) => url.pathname.startsWith('/api/submit-data'),
new NetworkFirst({
plugins: [
new BackgroundSyncPlugin('submit-data-queue', {
maxRetentionTime: 24 * 60, // Retry for 24 hours
}),
],
}),
'POST' // Only for POST requests
);
Push Notifications
Send real-time updates with the Push API. Requires a backend server and user permission. See Google’s Push Notifications Guide.
Deployment
PWAs must be served over HTTPS (except localhost). Popular hosting options:
Netlify
- Push your code to GitHub.
- Connect your repo to Netlify.
- Build command:
npm run build, publish directory:build.
Vercel
Similar to Netlify—connect your GitHub repo, and Vercel auto-deploys on push.
Firebase Hosting
- Install Firebase CLI:
npm install -g firebase-tools. - Run
firebase init, select “Hosting”, and link your project. - Deploy with
firebase deploy.
Troubleshooting Common Issues
Service Worker Not Updating
Browsers cache service workers. To force an update:
- Increment the
versioninservice-worker.js(e.g.,const CACHE_VERSION = 'v2'). - Use
workbox-precaching’s built-in cache busting (auto-enabled in CRA).
Manifest Icons Not Loading
Ensure icon paths in manifest.json are correct (e.g., src: "icons/icon-512x512.png"). Test paths with DevTools Application > Manifest.
Install Prompt Not Showing
Check Lighthouse for errors. Common fixes:
- Add
display: standalonetomanifest.json. - Register the service worker in production.
- Serve over HTTPS (use
localhostfor development).
Conclusion
Building a PWA with React is straightforward with create-react-app and Workbox. By following this guide, you’ve learned to:
- Configure a Web App Manifest for branding and installability.
- Use service workers for offline support.
- Test with Lighthouse for PWA compliance.
- Add install prompts and advanced features like background sync.
PWAs offer a cost-effective way to deliver native-like experiences across devices—start building yours today!