Progressive Web Apps (PWAs) combine the best of web and native experiences: they load instantly, work offline, and can be installed on a user’s home screen. If you’re comfortable with React and want to give your users a smoother, app‑like experience, this guide will walk you through turning a regular React project into a fully‑featured PWA. We’ll cover everything from project setup to service‑worker configuration, common mistakes, and deployment tricks.
What You’ll Need
- Node.js (v14 or newer) and npm/yarn installed
- A code editor (VS Code is recommended)
- Basic familiarity with React and JavaScript modules
- Git for version control (optional but helpful)
- An HTTPS‑enabled local server for testing (e.g.,
serve -s build)
Step 1: Scaffold a Fresh React Project
Start with a clean Create‑React‑App (CRA) boilerplate. CRA already includes a service‑worker template, but we’ll replace it with Workbox for more control.
Open a terminal and run:
npx create-react-app my-pwa
Navigate into the folder:
cd my-pwa
At this point you can run npm start to verify the app works in development mode.
Step 2: Install Workbox and Configure the Service Worker
Workbox is a set of libraries that simplify service‑worker creation. Install it as a dev dependency:
npm install workbox-webpack-plugin --save-dev
Next, open craco.config.js (or create one if you’re using CRA without eject). Add the Workbox plugin to the webpack config:
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
module.exports = {
webpack: {
configure: (webpackConfig) => {
if (!webpackConfig.plugins) webpackConfig.plugins = [];
webpackConfig.plugins.push(
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [{
urlPattern: /\.js$/,
handler: 'StaleWhileRevalidate',
options: {cacheName: 'js-cache'}
}, {
urlPattern: /\.css$/,
handler: 'StaleWhileRevalidate',
options: {cacheName: 'css-cache'}
}, {
urlPattern: /\.(?:png|jpg|jpeg|svg|gif)$/,
handler: 'CacheFirst',
options: {cacheName: 'image-cache', expiration: {maxEntries: 50, maxAgeSeconds: 30 * 24 * 60 * 60}}
}]
})
);
return webpackConfig;
}
}
};
Now rebuild the app for production:
npm run build
The generated service-worker.js will automatically precache static assets and apply the runtime caching rules you defined.
Step 3: Add a Web App Manifest
The manifest tells browsers how your app should appear when installed (icons, name, theme colors, etc.). Create a file called public/manifest.json with the following content:
{
"short_name": "MyPWA",
"name": "My Progressive Web App",
"icons": [
{"src": "/icon-192.png", "sizes": "192x192", "type": "image/png"},
{"src": "/icon-512.png", "sizes": "512x512", "type": "image/png"}
],
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1976d2",
"orientation": "portrait-primary"
}
Place the two icon files (192 × 192 and 512 × 512 PNGs) in the public folder. Then, reference the manifest in public/index.html:
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
Also add a meta tag for the theme color right after the manifest link:
<meta name="theme-color" content="#1976d2">
Step 4: Enable HTTPS for Local Testing
PWAs require a secure context (HTTPS) for service‑workers to register. The easiest way to test locally is to serve the production build over HTTPS.
Install the serve package globally:
npm install -g serve
Then run it with the -s flag and the -l option to specify a port:
serve -s build -l 4430
Open https://localhost:4430 in Chrome. You should see a green padlock, and the DevTools Application panel will show the service‑worker as “activated”.
Step 5: Fine‑Tune Caching Strategies
Workbox gives you several built‑in handlers. Choose the right one for each asset type:
- CacheFirst – best for static images or fonts that rarely change.
- StaleWhileRevalidate – ideal for CSS/JS where you want fast response but still fetch updates in the background.
- NetworkOnly – use for API calls that must always hit the server.
Update the runtimeCaching array in craco.config.js to include an API endpoint:
{
urlPattern: new RegExp('https://api.example.com/'),
handler: 'NetworkOnly',
method: 'GET'
}
Remember to rebuild after each config change.
Step 6: Provide an Offline Fallback Page
If a user loses connectivity, the service‑worker can serve a custom HTML page. Create public/offline.html with a friendly message and a basic navigation button.
Then add a catch handler in the Workbox configuration:
new WorkboxWebpackPlugin.GenerateSW({
// …previous options
navigateFallback: '/offline.html',
navigateFallbackDenylist: [/^/api//]
});
Now any navigation request that fails (e.g., a refreshed page while offline) will display offline.html instead of a generic browser error.
Step 7: Deploy to a Static Host
Most PWAs live on static file servers because the service‑worker only needs to serve cached assets. Popular choices include Netlify, Vercel, and GitHub Pages. Below is an example using Netlify:
- Commit your code to a Git repository.
- Create a
netlify.tomlfile at the root:
[build]
publish = "build"
command = "npm run build"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Push to GitHub, then link the repo in the Netlify dashboard. Netlify will run npm run build and serve the build folder over HTTPS automatically. After deployment, open the site on a mobile device, tap “Add to Home Screen”, and you’ll have a native‑looking shortcut.
Common Mistakes to Avoid
1 Forgetting the manifest link: Without <link rel="manifest">, browsers won’t prompt installation.
2 Serving over HTTP: Service‑workers simply won’t register in insecure contexts.
3 Over‑caching: Caching API responses can serve stale data. Use NetworkOnly or set short maxAgeSeconds for dynamic endpoints.
4 Missing icons: Android requires a 512 × 512 icon; iOS looks for Apple‑specific meta tags. Add apple-touch-icon links if you target iOS users.
5 Ignoring Lighthouse: Run Chrome’s Lighthouse audit to catch missed PWA criteria early.
Tips and Tricks
• Use CRA’s built‑in PWA template if you don’t need custom Workbox logic – run npx create-react-app my-pwa --template cra-template-pwa.
• Leverage precacheManifest generated by Workbox to version assets automatically.
• Test offline behavior with Chrome DevTools → Application → Service Workers → “Offline”.
• Enable background sync for queued POST requests – Workbox’s BackgroundSyncPlugin makes it trivial.
• Compress images before adding them to the public folder; smaller payloads improve installability scores.
Frequently Asked Questions
Do I need to eject Create‑React‑App to use a service‑worker?
No. By using a configuration override tool like CRACO or react-app-rewired, you can inject Workbox without ejecting, preserving future CRA updates.
Can I use TypeScript with this setup?
Absolutely. Create the project with npx create-react-app my-pwa --template typescript and the same Workbox configuration works; just ensure your craco.config.js is typed as .cjs or .ts if you prefer.
Will my PWA work on iOS Safari?
iOS supports most PWA features, but it does not fully honor service‑worker caching for all request types and lacks push notifications. Add Apple‑specific meta tags (apple-mobile-web-app-capable, apple-touch-icon) to improve the experience.
Conclusion
Turning a React app into a Progressive Web App is mostly about adding a manifest, a well‑configured service‑worker, and serving everything over HTTPS. By following the steps above, you’ll deliver faster load times, offline resilience, and a native‑like install prompt—all without leaving the familiar React ecosystem. Keep testing with Lighthouse, iterate on caching strategies, and watch your users enjoy a smoother, more reliable web experience.
Photo by izaiah lopez on Unsplash





