October 14, 2019

Building Progressive Web App (PWA) with React

According to comScore Mobile Metrix on time spend on Mobile app vs Web App , 13 % were spent on mobile web and 87% on mobile apps.

Progressive Web Apps (PWAs) is the technology that was designed to bridge this gap by giving web apps ability to behave like mobile apps , ability such as making them installable .

In this tutorial , we will use New York Times API to create a PWA app that will display a list of most popular stories from New York Times .

Github Link for final project : https://github.com/robertrutenge/nytimes-pwa

Go on and register for an account via https://developer.nytimes.com/ , don’t worry it should take about 2 minutes to set everything up .

Once registered and logged in , click on the dropdown that has your email on the top right and selects apps then new app . Go on fill in the App Name and other fields . Once Created , you will see a section that has an API Key , go on copy that key somewhere as we will use it later to call our APIs .

One Done , let’s get started . Make sure you have Nodejs and create-react-app installed , from there create a new react application called nytimes-pwa by running the below command :

create-react-app nytimes-pwa

Let’s start building . go on to App.js file , remove logo import and on the header delete everything and add a title “New York Times “ . Go to App.css and modify .App-header class min-height to 10vh;

import React from "react";
import "./App.css";

function App() {
  return (
    <div className="App">
      <header className="App-header">New York Times</header>
    </div>
  );
}

export default App;

Now , let’s go on displaying our popular stories from New York Times . Go on and install axios as we will use it as our HTTP Client to call our APIs .

npm install axios

Under Src directory , let’s create a services folder and under it new file nytimesApi.js with below code :

import axios from "axios";

export const getMostPopularStories = async () => {
  const result = axios
    .get(
      "https://api.nytimes.com/svc/mostpopular/v2/viewed/1.json?api-key=YOUR-API-KEY"
    )
    .then(({ data }) => data);
  return result;
};

The code above should be straightforward , we use axios to call an API that will give us popular posts . Let’s go ahead and use getMostPopularStories() function in our App.js file and for now just display the titles as below:

import React, { useEffect, useState } from "react";
import "./App.css";
import { getMostPopularStories } from "./services/nytimesApi";

function App() {
  const [stories, setStories] = useState([]);

  useEffect(() => {
    getMostPopularStories().then(res => setStories(res.results));
  });
  return (
    <div className="App">
      <header className="App-header">New York Times</header>
      <div className="container">
        {stories && stories.map(story => <p>{story.title}</p>)}
      </div>
    </div>
  );
}

export default App;

From the above code , we used useState hook to declare our state called stories in which its default value is set to an empty array . we will use the useEffect hook to call our API and modify stories state to the response we get and finally display our fetched posts .

In case , you run in to this error

Uncaught Error: Cannot find module ‘/Users/my-user/my/project-path/nytimes-pwa/node_modules/babel-preset-react-app/node_modules/@babel/runtime/helpers/slicedToArray’

Just go ahead and install babel runtime

npm add @babel/runtime

After this , let’s finalize displaying the stories by creating a story component that will display each story together with adding some styles .

To speed things up , i have gone ahead and add a Story Component together with the styling on app.css , go ahead and copy below code .

import React, { useEffect, useState } from "react";
import "bootstrap/dist/css/bootstrap.css";
import "./App.css";
import { getMostPopularStories } from "./services/nytimesApi";
import Story from "../src/components/Story";

function App() {
  const [stories, setStories] = useState([]);

  useEffect(() => {
    getMostPopularStories().then(res => setStories(res.results));
  });
  return (
    <div className="App">
      <header className="App-header">New York Times</header>
      <div className="container">
        {stories && stories.map(story => <Story story={story} />)}
      </div>
    </div>
  );
}

export default App;

And then our component is as below :-

import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
  faTag,
  faUser,
  faCalendar,
  faEye,
  faArrowRight
} from "@fortawesome/free-solid-svg-icons";

const Story = ({ story }) => (
  <article>
    <div className="row">
      <div className="col-sm-6 col-md-2">
        <figure>
          <img
            src={
              story.media &&
              story.media[0]["media-metadata"] &&
              // eslint-disable-next-line no-undef
              story.media[0]["media-metadata"][1].url
            }
            alt={story.media && story.media[0].caption}
            className="img-fluid"
          />
        </figure>
      </div>
      <div className="col-sm-6 col-md-10">
        <h4>{story.title}</h4>

        <section>
          <FontAwesomeIcon icon={faUser} />
          <span>{story.byline}</span>
          <FontAwesomeIcon icon={faTag} />
          <span>{story.section}</span>
          <FontAwesomeIcon icon={faCalendar} />
          <span>{story.published_date}</span>
          <FontAwesomeIcon icon={faEye} />
          <span>{story.views}</span>
        </section>
        <p>{story.abstract}</p>
        <a
          href={story.url}
          target="_new"
          className="btn btn-primary btn-sm float-right"
        >
          Read More <FontAwesomeIcon icon={faArrowRight} />
        </a>
      </div>
    </div>
  </article>
);
export default Story;

Note from the above that we are using bootstrap and font-awesome to spice up our UI . so go on , and install them

npm install bootstrap @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome

And at last , our added styling on app.css file

.App-header {
  text-align: center;
  background-color: #282c34;
  min-height: 10vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

article {
  background-color: #e0e0e0;
  padding: 10px;
  margin-bottom: 10px;
  margin-top: 10px;
}
section {
  padding: 5px 0px;
  font-size: 13px;
}

svg {
  margin: 0px 5px;
}

span {
  margin-right: 20px;
}
.btn-primary {
  color: #fff;
  background-color: #282c34;
  border-color: #282c34;
}

If everything went OK , you should have similar screen as below :

New York Times PWA APP Screenshot

Now , let’s start what were here for , Progressive Web App (PWA) .

From the official PWA page on google developer page , The 3 factors that makes an APP PWA are :

Reliable — Load instantly and never show the downasaur (The game you enjoy playing on chrome while offline) , even in uncertain network conditions.

Fast — Respond quickly to user interactions with silky smooth animations and no janky scrolling.

Engaging — Feel like a natural app on the device, with an immersive user experience. Before we start with pwa , go on and build our app

Before we start with pwa , go on and build our app

npm run build

We will use serve for launching our app , if its not installed , go ahead and install it .

npm i serve

Now go ahead and launch our web app using serve .

serve -s build

By default our web app will be served on port 5000 .

We will also be using google lighthouse under chrome dev tools to inspect how progressive our app is , go on and inspect our served web app , click the audit tab and click Run Audit at the bottom of the page .

1. Reliable

Let’s make our react app reliable and to do that , create-react-app already comes with a service worker that we need to register .

A service worker is a script that runs in the background to perform various tasks such as caching resources , handling network resources and storing content for offline use .

At the moment no service worker is registered as seen when you use chrome dev tools to inspect under application then service workers , you will see nothing at the moment .

No Service Worker

Also if you go to network tab , then offline , and refresh the page , you will see downasaur , go on and play the game a little bit as we are about to register our service worker to resolve that .

No Network

Let’s register a service worker , go to index.js file in the src directory of our file and change serviceWorker.unregister() to serviceWorker.register() . Note that the serviceWorker is imported from serviceWorker.js that comes when we created our app .

Go on and build and serve the app again .

Now if you refresh the browser and inspect again you will see our newly registered service worker . At this moment our web app is cached by service worker as stated on the console with the below message ;

This web app is being served cache-first by a service worker. To learn more, visit https://bit.ly/CRA-PWA

Also , if you go to network and turn on offline , you can see that downasaur did not appear , and the page is served with the header and no content as network request to fetch the content failed but all static files has been cached and rendered in the browser .

It’s up to you to play with this offline capability and see to what extent you want to cache the static files like images for offline capability .

2. Engaging

create-react-app comes with a web manifest file called manifest.json file residing in the public directory .

From MDN

The web app manifest provides information about an application (such as name, author, icon, and description) in a JSON text file. The purpose of the manifest is to install web applications to the homescreen of a device, providing users with quicker access and a richer experience.

Go on and change the short_name and name to reflect our app .

short_name is intended to be about 12 characters and is typically displayed in App launcher and new tab page . It is an optional field and if not supplied , the name will be used .

name is the primary identifier of the app and is a required field. It will typically displayed on Install dialog , Extension management UI and chrome web store .

Now Build and serve our web app again

How about our app being installable ? No worries , if your using chrome version 70 and above , open our app and then chrome options settings , the three dots on the top right then next to find , you will see an option to install our app to chrome apps as below

PWA Installed from chrome screenshot

Once done , your app will be launched , or you can open it via chrome or via

chrome://apps/

3 . Fast

According to google study , 53% of web users will abandon the site if it takes more than 3 seconds to load . Let’s test how fast our app is performed generally after building and serving it . Doing a final audit on my end i get below results ;

![PWA lighthouse test](./pwalighthousetest.png

I hope , this gave you some insight on how to take advantage of PWA when creating a new react app or optimizing your existing react apps .

Buy Me A Coffee

Robert Rutenge
Front-end Developer | ReactJS enthusiast | Problem Solver . Find me on twitter and Github