How To Build Progressive Web Apps (PWAs) with React?

- A Progressive Web App is a web application enhanced with installation, offline, and device-integration capabilities.
- React manages the interface but does not provide PWA functionality by itself.
- A web app manifest describes how the installed application should appear and launch.
- A service worker controls network requests and enables caching and offline behaviour.
- Create React App is deprecated, so this guide uses Vite and
vite-plugin-pwa.- PWAs must be served through HTTPS in production; service workers work on
localhost during development.- Do not cache every API response. Choose a strategy based on how fresh and sensitive the data is.
- Test installation, updates, offline routes, and cached data on real devices before launching.
Users expect web applications to load quickly, remain useful on unstable networks, and feel natural on whichever device they use. Meeting those expectations becomes difficult when a web app depends entirely on a reliable internet connection.
That is where Progressive Web Apps help. While building PWAs with React, I have found that creating the interface is usually the easy part. The real work lies in choosing what should function offline, configuring safe caching rules, handling application updates, and testing how the app behaves when the network disappears.
In this guide, we’ll build a React PWA using Vite, add an installable web app manifest, generate a service worker, configure offline caching, and prepare the application for production.
What Is a Progressive Web App?
A Progressive Web App is a web application that uses modern browser capabilities to provide an installable, reliable, and app-like experience.
Users access a PWA through a URL, just like a regular website. On supported browsers and devices, they can also install it, launch it from the home screen or application menu, and use the parts designed to work without a network connection.
A PWA remains a web application. It is built using HTML, CSS, and JavaScript and does not automatically gain every capability available to a native Android or iOS application.
What Role Does React Play in a PWA?
React is responsible for building and updating the user interface. Its component-based structure works well for applications with interactive screens, shared state, reusable layouts, and frequently changing data.
PWA behaviour comes from browser technologies outside React:
- The web app manifest controls installation metadata.
- The service worker handles network requests and caching.
- Cache Storage stores responses and static assets.
- IndexedDB can store structured offline data.
- The Push API and Notifications API support notifications where available.
React and PWA technologies solve different parts of the application. React builds the interface, while the browser APIs make that interface more reliable and installable.
What Are the Core Features of a PWA?
Installability
Users can install the web application on a supported device without downloading a conventional application package from an app store.
Network Resilience
A service worker can serve cached content when the network is slow or unavailable. The level of offline support depends on how the application is designed.
Fast Repeat Visits
Important static assets can be stored locally, reducing the amount of data that must be downloaded when the user returns.
App-Like Display
The application can open in a standalone window without the full browser interface.
Progressive Enhancement
Users on browsers without complete PWA support can still access the application as a normal website.
Device Capabilities
Depending on browser and operating-system support, PWAs may use features such as notifications, background sync, file handling, sharing, shortcuts, and badges.
Before You Start
You need a current Node.js installation and a package manager such as npm.
Check your installation:
node --version
npm --versionThe project in this guide uses:
- React
- Vite
vite-plugin-pwa- Workbox through the Vite PWA plugin
React officially deprecated Create React App for new applications in 2025 and recommends using a framework or a modern build tool such as Vite.
How to Build a React PWA in 7 Steps
Step 1: Create a React Application With Vite
Create the project:
npm create vite@latest my-react-pwa -- --template react
cd my-react-pwa
npm installStart the development server:
npm run devOpen the local URL shown in the terminal. The React application should now be running.
The initial project structure will resemble:
my-react-pwa/
├── public/
├── src/
│ ├── App.jsx
│ ├── main.jsx
│ └── index.css
├── index.html
├── package.json
└── vite.config.jsStep 2: Install the Vite PWA Plugin
Install vite-plugin-pwa as a development dependency:
npm install -D vite-plugin-pwaThe plugin can:
- Generate the web app manifest
- Generate a service worker
- Precache production assets
- Register the service worker
- Configure runtime caching through Workbox
- Detect when a new application version is available
Using a plugin reduces the amount of service-worker lifecycle and asset-versioning logic you need to maintain manually.
Step 3: Add the PWA Icons
Create the following icons and place them inside public/:
public/
├── favicon.ico
├── apple-touch-icon.png
├── pwa-192x192.png
├── pwa-512x512.png
└── maskable-icon-512x512.pngRecommended sizes are:
| File | Recommended size | Purpose |
pwa-192x192.png | 192 × 192 | Standard application icon |
pwa-512x512.png | 512 × 512 | Large installation icon |
maskable-icon-512x512.png | 512 × 512 | Icon designed for adaptive masks |
apple-touch-icon.png | 180 × 180 | Apple home-screen icon |
A maskable icon needs additional safe space around its main design. Otherwise, parts of the logo may be cropped when a device applies a circle, square, or rounded mask.
Do not create one low-resolution icon and resize it for every purpose. Check the icons on light and dark backgrounds and test how the maskable version appears in different shapes.
Step 4: Configure the Manifest and Service Worker
Open vite.config.js and replace its contents with:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { VitePWA } from "vite-plugin-pwa";
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: "prompt",
includeAssets: [
"favicon.ico",
"apple-touch-icon.png",
],
manifest: {
name: "My React Progressive Web App",
short_name: "My React PWA",
description: "A Progressive Web App built with React and Vite",
theme_color: "#0f172a",
background_color: "#ffffff",
display: "standalone",
scope: "/",
start_url: "/",
orientation: "portrait-primary",
icons: [
{
src: "pwa-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "pwa-512x512.png",
sizes: "512x512",
type: "image/png",
},
{
src: "maskable-icon-512x512.png",
sizes: "512x512",
type: "image/png",
purpose: "maskable",
},
],
},
workbox: {
globPatterns: [
"**/*.{js,css,html,ico,png,svg,webp,woff2}",
],
runtimeCaching: [
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/,
handler: "CacheFirst",
options: {
cacheName: "image-cache",
expiration: {
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60,
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
],
},
}),
],
});This configuration generates the manifest and service worker during the production build.
Understanding the Manifest
The important manifest properties are:
name: Full application name displayed during installationshort_name: Shorter name used when screen space is limiteddescription: Short explanation of the applicationstart_url: The page opened when the installed app launchesscope: URLs controlled as part of the installed applicationdisplay: Determines whether the app opens with browser controlstheme_color: Colour used by supported browser and operating-system interfacesbackground_color: Colour displayed while the application loadsicons: Images used for installation, launchers, and operating-system surfaces
The manifest describes the installed experience. It does not create offline support by itself.
Understanding the Workbox Configuration
globPatterns tells Workbox which generated assets should be precached. Each production build includes versioned asset names, allowing changed files to be updated without unnecessarily downloading every asset again.
The runtime-caching rule stores images using CacheFirst. When the image is already cached, it is served locally. Otherwise, it is requested from the network and saved for later use.
The cache is limited to 60 images and expires entries after 30 days. These limits prevent the application from storing an unlimited number of files.
Step 5: Register the Service Worker and Handle Updates
Create a file named src/registerSW.js:
import { registerSW } from "virtual:pwa-register";
const updateSW = registerSW({
onNeedRefresh() {
const shouldUpdate = window.confirm(
"A new version is available. Update now?"
);
if (shouldUpdate) {
updateSW(true);
}
},
onOfflineReady() {
console.log("The application is ready to work offline.");
},
onRegisteredSW(serviceWorkerUrl, registration) {
console.log(
"Service worker registered:",
serviceWorkerUrl,
registration
);
},
onRegisterError(error) {
console.error(
"Service worker registration failed:",
error
);
},
});Import it near the top of src/main.jsx:
import "./registerSW";A complete main.jsx may look like:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import "./registerSW";
import App from "./App.jsx";
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);The update prompt prevents the application from silently replacing the service worker while a user is completing a task.
Let’s Build Your Web App Together!
We build fast, scalable, and secure web applications that help your business grow. From idea to launch, we handle it all.
For a content-only application, automatic updates may be acceptable. For dashboards, forms, checkout flows, or tools containing unsaved work, prompting the user is generally safer.
Step 6: Design the Offline Experience
A PWA should not claim to work offline unless its important user journeys have been deliberately designed for it.
There are several possible levels of offline support:
Offline Application Shell
The main interface loads, but live data may be unavailable. Navigation, branding, and previously cached static content remain visible.
Previously Viewed Content
Pages or data the user has already accessed can be shown from a local cache.
Offline Read and Write
Users can create or edit information while disconnected. The application stores the changes locally and synchronises them later.
Complete Offline Operation
Most important functionality remains available without a network. This requires careful local storage, conflict resolution, and synchronisation design.
Not every application needs complete offline operation. A payment dashboard, for example, may be better served by displaying a clear offline status than by showing outdated financial information.
Displaying Network Status in React
Create src/NetworkStatus.jsx:
import { useEffect, useState } from "react";
function NetworkStatus() {
const [isOnline, setIsOnline] = useState(
navigator.onLine
);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener(
"online",
handleOnline
);
window.removeEventListener(
"offline",
handleOffline
);
};
}, []);
if (isOnline) {
return null;
}
return (
<div role="status" className="offline-banner">
You are offline. Some information may be unavailable.
</div>
);
}
export default NetworkStatus;Use it inside App.jsx:
import NetworkStatus from "./NetworkStatus";
function App() {
return (
<>
<NetworkStatus />
<main>
<h1>My React PWA</h1>
<p>
This application can load its cached interface
when the network is unavailable.
</p>
</main>
</>
);
}
export default App;navigator.onLine provides a useful signal, but it does not prove that your API is reachable. The device may be connected to a network that has no internet access, or the API itself may be unavailable.
Treat it as a user-interface hint, not a complete connectivity check.
Step 7: Build, Test, and Deploy the PWA
Create a production build:
npm run buildVite places the output inside dist/.
Preview it locally:
npm run previewTesting the production build is important because the generated service worker and manifest may not behave the same way during the normal development server session.
What to Test
Check the following:
- The manifest loads without errors.
- The service worker installs and activates.
- The application can be installed on supported devices.
- Icons display correctly after installation.
- The app launches through the configured
start_url. - Cached pages remain usable offline.
- Uncached pages fail gracefully.
- A new deployment triggers the expected update experience.
- Old cached assets are removed eventually.
- Forms do not lose data during an update.
- Authenticated or sensitive responses are not cached accidentally.
Testing With Browser Developer Tools
In Chromium-based browser developer tools:
- Open the Application panel.
- Check Manifest for missing fields or icon problems.
- Open Service Workers to inspect the registration.
- Open Cache Storage to see cached responses.
- Enable Offline mode under the Network panel.
- Refresh the application and test important routes.
Use Lighthouse for performance, accessibility, and best-practice audits. PWA behaviour should also be tested directly through the Application panel and on real devices.
Deployment Requirements
Production service workers require a secure context. Deploy the application through HTTPS using a platform such as:
- Vercel
- Netlify
- Firebase Hosting
- Cloudflare Pages
- AWS
- A properly configured web server
localhost is treated as a secure development environment, but an ordinary production HTTP URL is not.
For a single-page React application, configure the hosting platform to return index.html for client-side routes. Without this fallback, opening an installed PWA directly on a nested route may return a server-side 404 error.
Choosing the Right Caching Strategy
Caching every request with the same strategy is one of the quickest ways to create stale or incorrect behaviour.
Workbox provides several common strategies.
Cache First
The service worker checks the cache before using the network.
Best suited for:
- Versioned images
- Fonts
- Icons
- Static media that rarely changes
Risk: Users may continue seeing an old resource until the entry expires or the cache is refreshed.
Network First
The service worker requests the latest response and falls back to the cache if the network fails.
Best suited for:
- Frequently updated public content
- Article lists
- Product listings
- Data that should be fresh but can be slightly outdated offline
Risk: A slow network can delay the response before the cached version is used.
Stale While Revalidate
The cached response is returned immediately while the service worker requests an updated version in the background.
Best suited for:
- Avatars
- News feeds
- Content thumbnails
- Non-critical API responses
Risk: The user may briefly see outdated information.
Network Only
The request always goes to the network.
Best suited for:
- Payments
- Authentication
- Sensitive account operations
- Real-time financial information
- Requests that must never use stale data
Cache Only
The request is served only when it already exists in the cache.
Best suited for:
- Precached application assets
- Fully controlled offline resources
The correct strategy depends on how damaging stale data would be. A cached logo can be old without causing harm. A cached account balance or checkout response can mislead the user.
Should You Cache API Responses?
API caching requires more caution than caching images, scripts, and styles.
Before caching an API response, ask:
- Does the response contain personal or sensitive data?
- Can it be safely viewed after the user signs out?
- How quickly does the information become outdated?
- Could stale data cause a wrong user action?
- Is the response shared between users?
- Does the request contain an authorisation header?
- What should happen when the server and offline data disagree?
Avoid broad rules that cache every /api/ request. Define specific patterns for public, read-only endpoints and keep sensitive operations network-only.
When an application supports offline writes, store pending operations in IndexedDB and build an explicit synchronisation process. Do not assume that caching a POST request provides reliable offline mutation support.
Adding Push Notifications
Push notifications can re-engage users, but they are optional and should not be presented as a requirement for every PWA.
A complete web-push implementation requires:
- A registered service worker
- Notification permission from the user
- A push subscription
- A backend that stores subscriptions
- A push service and application-server keys
- Service-worker handlers for push and notification-click events
- Unsubscribe and preference management
Do not request notification permission immediately when the application loads. Ask after the user performs a relevant action and clearly explain what notifications they will receive.
For example, an order-tracking application could ask:
Would you like to receive a notification when your order is dispatched?
That gives the user context and a reason to make the decision.
Browser and operating-system support varies, so the application should remain useful when notifications are unavailable or declined.
Common Challenges When Building React PWAs
Stale Application Versions
A previously installed service worker may continue controlling open tabs while a new version waits to activate.
Solution: Provide an update prompt, test the service-worker lifecycle, and avoid forcing reloads while users have unsaved data.
Incorrect Caching Rules
A broad caching rule may store private, outdated, or error responses.
Solution: Cache only clearly identified resources, limit cache size and age, and keep sensitive operations network-only.
Offline Routes Failing
The homepage may work offline while nested routes fail.
Solution: Test every important route, configure a navigation fallback, and ensure the hosting platform supports single-page application routing.
Offline Data Conflicts
A user may edit the same record offline while another device updates it online.
Solution: Define a conflict policy using timestamps, version numbers, server authority, merging, or explicit user resolution.
Let’s Build Your Web App Together!
We build fast, scalable, and secure web applications that help your business grow. From idea to launch, we handle it all.
Browser Differences
Installation, notifications, background features, and update behaviour vary between browsers and operating systems.
Solution: Use feature detection, provide fallbacks, and test the application on actual target devices.
Storage Limits
Browsers can remove cached data under storage pressure, and available storage varies by device.
Solution: Keep caches bounded, avoid depending on them as the only copy of important data, and rebuild cached content when necessary.
Difficult Service-Worker Debugging
An old service worker can continue affecting the application after code changes.
Solution: Inspect registrations and cache storage in browser developer tools. During development, unregister old workers and clear site data when testing a new caching configuration.
Best Practices for React PWAs
Keep the Initial Bundle Small
Use route-level code splitting and load features only when they are needed:
import { lazy, Suspense } from "react";
const Reports = lazy(() => import("./Reports"));
function App() {
return (
<Suspense fallback={<p>Loading reports...</p>}>
<Reports />
</Suspense>
);
}Optimise Images
Use modern formats such as WebP or AVIF, responsive image sizes, appropriate compression, and lazy loading for content outside the initial viewport.
Provide a Clear Offline State
Tell users when information may be unavailable or outdated. Do not allow a failed network request to leave the interface loading indefinitely.
Preserve Unsaved Work
Store drafts locally where appropriate and avoid applying updates in the middle of important forms or transactions.
Respect Accessibility
An installable application must still support keyboard navigation, readable contrast, labelled controls, logical focus movement, and assistive technologies.
Protect Cached Data
Do not cache confidential responses unless the threat model and sign-out behaviour have been carefully designed.
Measure Real Performance
Track Core Web Vitals, loading time, JavaScript errors, service-worker failures, and API latency on real user devices.
Test Upgrades
A PWA must work not only on its first installation but also after repeated deployments, schema changes, cache changes, and service-worker updates.
Benefits of Building a PWA With React
One Web Codebase
A React PWA can support desktop and mobile browsers without maintaining separate native codebases for every platform.
Easier Distribution
Users can access the application through a URL and install it where supported. Updates can be delivered through the web rather than waiting for every user to download a new application package.
Better Performance on Repeat Visits
Precached assets and suitable runtime caching can reduce repeat downloads and make important screens feel faster.
Improved Network Resilience
A carefully designed offline experience allows users to access cached content or continue selected tasks during temporary network failures.
Wider Reach
A PWA remains accessible as a website, even for users who do not install it or whose browsers provide limited PWA features.
Lower Development Overhead
A PWA can reduce the need for separate platform-specific applications when the product does not depend heavily on native operating-system capabilities.
Teams building a complex PWA may still need help with React architecture, caching, offline synchronisation, testing, and performance. Experienced React JS developers can help design these features around the application’s real user journeys instead of adding offline behaviour as an afterthought.
When Should You Choose a PWA?
A PWA can be a strong choice for:
- E-commerce storefronts
- Content and publishing platforms
- Booking applications
- Internal business tools
- Field-service applications
- Learning platforms
- Customer portals
- Event applications
- Products targeting users on inconsistent networks
A native application may be more suitable when the product requires:
- Extensive background processing
- Deep operating-system integration
- Advanced Bluetooth or hardware access
- Highly specialised native interfaces
- Platform-specific monetisation
- Features unsupported by target browsers
The right decision depends on the required capabilities, audience, distribution strategy, and development resources.
Frequently Asked Questions
Is React Suitable for Production PWAs?
Yes. React works well for PWA interfaces when combined with a manifest, service worker, appropriate caching strategies, secure deployment, offline planning, update handling, monitoring, and cross-browser testing.
Does React Provide Offline Support?
No. React manages the user interface. Offline support comes from service workers, Cache Storage, IndexedDB, and application-specific synchronisation logic. React components can then communicate the resulting network and data state.
Is Create React App Still Recommended for PWAs?
No. React deprecated Create React App for new projects in 2025. New client-side React applications can use a modern build tool such as Vite or an appropriate React framework.
Can a React PWA Work Completely Offline?
Yes, but only when its assets, routes, data, and user actions are deliberately designed for offline use. Complete offline support is considerably more complex than caching the initial application interface.
Are React PWAs SEO-Friendly?
They can be. Search visibility depends on crawlable content, rendering strategy, metadata, internal linking, performance, and indexability. Installing a service worker does not automatically improve SEO.
Do PWAs Work on iPhones?
PWAs can be installed and used on iPhones, but available capabilities and installation behaviour differ from Android and desktop platforms. Test the required features on the specific iOS versions and browsers you support.
Can a PWA Send Push Notifications?
Yes, on supported browsers and operating systems. The application needs permission, a push subscription, backend delivery logic, and service-worker event handlers. Users should be given a clear reason before permission is requested.
Does a PWA Need HTTPS?
Yes, service workers and most PWA capabilities require a secure context in production. Browsers make an exception for localhost so developers can build and test locally without an HTTPS certificate.
Can a PWA Be Published in an App Store?
In some cases, yes. PWAs can be packaged or submitted through supported store mechanisms, but store availability, requirements, and supported capabilities vary between platforms.
Our Final Words
Building a React PWA is not simply a matter of generating a manifest and turning on a service worker. Those steps make installation and caching possible, but they do not decide what the application should do when data is unavailable, an update arrives, or an offline edit conflicts with the server.
What matters most is identifying the user journeys that genuinely benefit from installation and network resilience. Cache stable assets, handle sensitive data carefully, communicate offline states clearly, and test updates as thoroughly as the initial installation.
Start with a reliable application shell and one useful offline experience. Once that works consistently across your target devices, expand the caching and offline capabilities based on real user needs.



