DEV Community

Cover image for Building a Progressive Web App with React
Elliot Brenya sarfo
Elliot Brenya sarfo

Posted on • Updated on

Building a Progressive Web App with React

Progressive web applications (PWAs) are web apps that utilize modern web capabilities and features to provide an experience comparable to native mobile apps. PWAs offer advantages like working offline, push notifications, fast load times, and an installable home screen icon.

In this article, we'll walk through building a PWA from scratch using React. We'll implement key features like offline support, push notifications, and app installation to turn our React web app into a fully-featured PWA.

Prerequisites

To follow along with this tutorial, you should have:

  1. Basic familiarity with React concepts like components, state, props, hooks etc.
  2. Node.js and npm installed on your development machine
  3. Basic knowledge of the command line interface

Who this article is for

This guide is designed for front-end web developers who have some experience with React and JavaScript. You may be looking to take an existing React app and turn it into a PWA to provide a more native-like and resilient user experience.

By the end, you'll understand how to implement PWA capabilities like offline support, push notifications, app manifests and more within a React application.

Now that we've covered the basics, let's get started building our React PWA!

Setting Up Our React PWA

Let's start by bootstrapping our React project using Create React App. This will generate a starter React project with build tools already configured for us:

npx create-react-app my-pwa
cd my-pwa
Enter fullscreen mode Exit fullscreen mode

Now we have a standard React project we can turn into a PWA.

Next, we'll install the key libraries we need:

npm install workbox-build workbox-webpack-plugin
Enter fullscreen mode Exit fullscreen mode

This adds workbox-build which generates our service worker code, and workbox-webpack-plugin to integrate workbox into our build process.

In webpack.config.js, we'll add the Workbox plugin:

const WorkboxPlugin = require('workbox-webpack-plugin');

module.exports = {


  plugins: [
    new WorkboxPlugin.GenerateSW()
  ]

}
Enter fullscreen mode Exit fullscreen mode

This configures Workbox to generate a service worker file for us, which handles things like caching and offline support.

With our dependencies installed and project setup, we're ready to start adding PWA capabilities to our React app.

Adding Offline Support

Now that our React project is set up, let's add offline support using the service worker generated by Workbox. This will allow our web app to work even when the user has lost their internet connection.

In workbox-config.js, we can define runtime caching rules that tell the service worker what to cache:


module.exports = {
  runtimeCaching: [
    {
      urlPattern: /\.(?:png|jpg|jpeg|svg|gif)/,
      handler: 'CacheFirst',
      options: {
        cacheName: 'images',
        expiration: {
          maxEntries: 20,
          maxAgeSeconds: 7 * 24 * 60 * 60, 
        },
      },
    },
  ]
}

Enter fullscreen mode Exit fullscreen mode

This caches image assets using a Cache First strategy, limiting the cache to 20 entries for 7 days.

For caching the app shell (HTML, JS, CSS), we'll use the GenerateSW plugin we already added to Webpack. This automatically caches our build artifacts.

To cache API responses, we can wrap fetch requests in the service worker to a "Network First" strategy:


workbox.routing.registerRoute(
  /\.(?:googleapis|gstatic)\.com/,
  new workbox.strategies.StaleWhileRevalidate()
);
Enter fullscreen mode Exit fullscreen mode

With offline support in place, let's test it out by disabling the network. Our app should now work offline!

Adding Push Notifications

In addition to offline support, we can also add push notifications to our PWA. This allows users to receive notifications even when the app is not open in the browser.

First, we need to generate the application server keys for the Push API:

npm install web-push -g
web-push generate-vapid-keys
Enter fullscreen mode Exit fullscreen mode

This gives us a public and private key to identify our app server to the push service.

In our React code, we'll request notification permission when the app loads:

// App.js

Notification.requestPermission().then(result => {
  if (result === 'granted') {
    console.log('Notification permission granted.');
  }
});
Enter fullscreen mode Exit fullscreen mode

Next, we'll subscribe the user using the public key:

// registerServiceWorker.js

const publicVapidKey = 'YOUR_PUBLIC_KEY';

serviceWorkerRegistration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
});
Enter fullscreen mode Exit fullscreen mode

This registers the user with the push service.

Finally, we can send notifications from the server using the private key:

//server.js

webpush.setVapidDetails('mailto:example@domain.com', privateVapidKey, publicVapidKey);

webpush.sendNotification(pushSubscription, payload);
Enter fullscreen mode Exit fullscreen mode

And that's it! Our PWA can now receive push notifications when the app is not in focus.

App Manifest and Installation

To allow users to install our PWA to their home screen like a native app, we need to add a web app manifest.

First, create manifest.json in the public folder:

{
  "short_name": "My App",
  "name": "My Example App",
  "icons": [
    {
      "src": "logo192.png",
      "type": "image/png",
      "sizes": "192x192" 
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}
Enter fullscreen mode Exit fullscreen mode

This provides metadata like the app name, icon, colors, and more.

Next we link to this in index.html:

<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
Enter fullscreen mode Exit fullscreen mode

Now when a user meets the required engagement criteria, they will be prompted to install the PWA.

We can handle the beforeinstallprompt event:

window.addEventListener('beforeinstallprompt', (event) => {

   deferredPrompt = event;

   button.style.display = 'block';

  button.addEventListener('click', async () => {
     button.style.display = 'none';

     deferredPrompt.prompt();

     const { outcome } = await deferredPrompt.userChoice;

   });

});
Enter fullscreen mode Exit fullscreen mode

This allows us to show an install button and programmatically trigger the install prompt.

Our PWA can now be installed just like a native app!

Deploying the PWA

Once our React PWA is built, we can deploy it like any other web app. Popular options include:

  1. Static site hosting like Vercel, Netlify, or AWS S3
  2. Serverless platforms like AWS Amplify or Firebase Hosting
  3. Traditional servers like AWS EC2, Heroku, or shared hosting

The main considerations are:

*Registering the Service Worker
*

We need to register our Workbox-generated service worker on load so it can handle caching and offline functionality:


if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js');
  });
}
Enter fullscreen mode Exit fullscreen mode

Handling Push Notifications

If using push notifications, our server needs to integrate with a push service provider to send notification messages.

Popular options:

  1. Firebase Cloud Messaging
  2. AWS Pinpoint
  3. Web Push Protocol

With those two requirements met, we can deploy our PWA like any other React app!

Conclusion

In this article, we walked through:

  1. Setting up a React app with Workbox for PWA support
  2. Implementing offline caching with a service worker
  3. Adding push notifications with the Push API
  4. Configuring a web app manifest for installation
  5. Deploying to production

PWAs provide a powerful way to build resilient, app-like experiences with web technologies. By harnessing modern browser APIs, React and other frameworks make it easy to turn websites into installable, offline-capable progressive web apps.

Feel free to connect with me on Twitter @elliot_mlaidv

Top comments (0)