Welcome to Progressive Web Apps with React!
This book will take you through the entire process of building a React application that also functions as a Progressive Web App. We'll cover not only the "how" of constructing such an application, but also highlight best practices and how to measure your application to ensure successful implementation of PWA features.
Progressive Web Apps are poised to become the future of web applications. They promise a bevy of additional functionality, such as push notifications and the ability to be installed, which pushes them into the realm of native iOS or Android apps. Additionally, a strong focus on performance (taking advantage of cutting-edge web technology) means that PWAs create apps that are fast for everyone.
We'll cover each facet of PWAs in depth, as well as the process of converting a regular web application into a progressive one. We'll also dive deep into React best practices, using libraries such as React Router.
To check your code for this and future chapters, you can view the completed project at https://github.com/scottdomes/chatastrophe/. The repository includes branches for each chapter. Visit https://github.com/scottdomes/chatastrophe/tree/chapter1 for this chapter's final code.
In this chapter, we will get started with the basic structure of our application. Here's what we'll cover:
- The use cases of Progressive Web Apps
- The basic user stories we want our app to fulfill
- The project structure and basic HTML
- Installing dependencies
- Getting started with React
First, let's set the scene for our application's journey.
One of your friends calls you on the phone, bursting with excitement about his latest start-up idea (you know the one). You patiently listen to his description, but respectfully decline to be a part of it. He's disappointed, but understands and promises to keep you updated on the project details. You murmur your assent.
A few months later, he meets you at your work and announces that he has found a group of serious investors, and he needs you to help him build the software he promised them. You again decline, but when discussing compensation, he mentions a number that you can't refuse. A week later, you're on a plane to San Francisco.
In front of the investors (who are, to your surprise, a rapt audience), your friend guides you through the basics of the application. In between the buzzwords ("mass interconnection" and "global community"), you gather just enough to summarize the application in a sentence.
"So, it's a chat room… for everyone in the world… all at once…"
Your friend smiles. "Yes."
You're bewildered by the image of a million strangers all talking at once on the same application, in the same room, but the investors break into applause. As you head for the door, your friend again announces how they'd like to compensate you… citing an even higher number than before. You sit down.
"The problem," your friend explains, "is that this chat room has to be for everyone."
"Global community," you say with a knowing nod.
"Exactly. Everyone. Even if they have terrible internet in some hut in the desert. They should be included."
"Mass interconnection," you add.
"Exactly! So it needs to be fast. And lightweight. And beautiful. And dynamic."
"So everyone will be talking at once? Won’t that be-"
"A worldwide collective, yes."
"The other problem," your friend declares, "is that our users will mostly be on their phones. On the go."
"So you want to do an iOS and Android app?"
Your friend waves his hand. "No, no. No one downloads apps anymore. Especially in developing countries; that takes too much bandwidth. Remember, worldwide collective."
"So a web app."
"Yes. A web collective."
Despite your best instincts, the project intrigues you. How do you craft a web application to be as fast as possible? How do you make it work under all network conditions? How do you make a chat application with all the conveniences of a native app, but for the web?
You sigh and shake his hand. "Let's get to work."
Welcome to the world of Progressive Web Applications.
In the preceding scenario, the problems your friend was describing are exactly the problems PWAs (Progressive Web Applications) are crafted to solve.
The first problem is that many users will be visiting your web page under poor network conditions. They may be a Silicon Valley technocrat on their iPhone in a coffee shop with bad WiFi, or they may be a Bangladeshi villager in a remote location. Either way, they will not stick around if your site isn't optimized for them, for everyone.
How fast your application loads--its performance--thus becomes an accessibility concern. PWAs solve this by loading quickly the first time, and even more quickly every time after that. We'll talk more about how they do so as the book progresses.
Second, the installation process for mobile apps is an obstacle for users. It means that your users need to be extra committed to engaging with your application--enough to give up storage space and time, and expose themselves to the possibility of malicious and intrusive code, and that's before they even get the chance to try the app!
What if we can provide the native app experience without the initial investment? PWAs are an attempt to bridge that gap. Again, we'll talk in subsequent chapters about how they do so, and how successful they actually are. However, these are both worthy challenges, and solving both will be a huge user experience win for our application.
React is quickly becoming the go-to solution for frontend web applications. Why? This is because it's fast, elegant, and makes managing large applications easy.
In other words, it makes complexity simple. There's no reason a PWA has to use React, though. PWAs can be any web app or site.
The advantage of React is that it is a beautiful and fun way to build frontend applications. It's also an in-demand skill. If you pair knowledge of React with experience with PWAs, you'll be about as future-ready as one can be in the fast-moving web development world.
You tell your friend about your learnings on PWAs and React, but before you finish, he waves his hand and interrupts.
"Yeah, yeah. Hey, what do you think the name should be?"
Once again, you're struck with the unnerving feeling that all of this was a mistake, that you never should have jumped on board this questionable venture, this potential catastrophe.
"Chatastrophe," you blurt out.
Your friend smiles and claps you on the back. "Brilliant. Okay, get Reacting or whatever!"
Before we begin building our app, let's take a deeper look at what exactly we want to achieve.
We can start with user stories. A user story is a description of a specific feature of an application, framed from the perspective of one of our users.
Here's the framework, as suggested by Jon Dobrowolski:
Users should be able to _____. As a user, I want to do ___ because ____. Given that I'm doing ___, I should be able to ___ in order to ___.
Not all features require the whole framework, though. Let's start with some basic examples:
- Users should be able to log in and out of the application
Pretty straightforward. I don't think we need to add the justification for this, as it is a fairly basic feature.
Let's move on to something more advanced:
- Users should be able to view their messages even when offline
- As a user, I want to be able to check my messages without needing an internet connection, because I may need to read them on the go
- Given that I start the application without internet access, I should be able to view all past messages
Let's cover some of the more basic functionality of the app. Users should be able to send and receive messages in real-time.
Real-time functionality will be key to our application. There's no point in having chat unless it's fast and fluid:
- Users should be able to view all messages by a given author
- As a user, I want to be able to view a list of all messages sent by a given user, because I may need to view their contribution to the conversation without the noise of others' messages
- Given that I click on a user's email, I should be taken to a profile view with all their messages
The profile view is a special feature you suggested to the client to manage the inevitable chaos of the main chat room.
Let's add a couple more PWA-specific user stories:
- Users should receive push notifications when a message is sent by another user
- As a user, I want to be constantly updated on the progress of the conversation, because I don't want to miss anything important
- Given that the chat is not open or visible on my screen, I should receive notifications for each message sent by another user
- Users should be able to install the app on their mobile device
- As a user, I want to be able to open the application without navigating to the URL in my browser, because I want easy access to the chat room
- Given that I have signed up to the chat for the first time, I should be prompted to install the app on my device
Don't worry about how we will achieve these goals; we'll cover that in due time. For now, let's just continue documenting what we want to do.
Our client was big on performance, so let's specify some performance-specific goals:
- Users should be able to load the app in under 5 seconds, even under shaky network conditions
- As a user, I want to be able to interact with the app as quickly as possible, because I don't want to be stuck waiting for it to load
- Given that I have opened the application using a poor internet connection, I should still have it load in under 5 seconds
Load in under 5 seconds is still a bit vague in terms of what that means for our application. We'll revisit this story in more depth in the chapters on performance.
The previously mentioned user stories cover the basic functionality of our app. Let's talk about the specific challenges these points present.
With each of the following, I encourage you to think about how you will solve these problems within the context of a web application. Hopefully, this will give you a better insight into what we try to achieve with PWAs, and the difficulties we face.
With Progressive Web Apps, we aim to provide an experience that is closer to a native app (one downloaded from the Apple App Store, Google Play Store, or another app store) than your typical web application. One of the advantages native apps have, of course, is that all relevant files are predownloaded and installed, while each time a user visits a web application, they may have to download all the assets again.
The solution? When the user first visits the page, download those assets and then save them for later (also known as caching). Then, when the user reopens the application, instead of downloading the files again over the internet (slow), we simply retrieve them from the user's device (fast).
However, this only works for when the user revisits the application. For the initial visit, we still have to download everything. This situation is particularly precarious, because when the user first visits Chatastrophe, they're not yet sold on its value, and so, are likely to leave (for good) if loading takes too long.
We need to ensure that our assets are as optimized as possible, and we download as little as possible on that first visit, so that the user stays around.
In short, fast loading for the first visit, near-instant loading for every subsequent visit.
There's no point in a chat application without notifications! Again, we're trying to emulate what has traditionally been a native app feature--push notifications directly to the user's device.
This problem is trickier than it might seem. Push notifications are only received when the app isn't open (that's the whole point, after all). So, if our web application isn't open and running, how can we possibly run the code to display a notification?
The answer is to use a third-party service that is engineered to send notifications to registered devices. So, rather than the device receiving the message alerting its user, the device sending the message alerts our notification service, which then notifies all relevant devices.
We also need a piece of code that is constantly "on"--always running and waiting to receive notifications from the third-party service and display them. This challenge only recently became solvable with web technology, and is one of the reasons PWAs are so exciting.
For now, don’t worry if this distinction doesn't "click" yet. We'll go into it in greater detail later. For now, the point is that push notifications will be an interesting challenge for our web application.
Even when our user isn't connected to the internet, they should be able to check past messages and navigate around our application.
The answer turns out to go hand-in-hand with the earlier discussion on instant loading. We simply need to cache everything our app needs to function, and then load that on demand; simply, of course, being the operative word.
For years, the big buzzword of web design has been responsive--websites that look just as good when scaled from desktop to mobile size.
PWAs are, in essence, responsive design on steroids, expanding design for mobile to every aspect of the app, from appearance to functionality.
However, at the end of the day, we need to ensure that our app looks great on every screen size. It also needs to look good under the restrictions we've already discussed. We can't rely too much on big background images or intense graphics. We need a simple and good-looking UI, engineered for both looks and performance.
Progressive enhancement is a technique that aims to fix that problem. In essence, it means that a user's experience should get progressively better as the application downloads, depending on the user's browser. In other words, the application experience improves as time goes on (and more of the application downloads), and as a user's software improves.
A user with the most modern browser, the fastest internet connection, and the application fully downloaded will have the best experience, but a user with an outdated browser, a shaky connection, and who just landed on the page will also have a quality experience.
This means our
Think of our UX as a series of layers, from good to fantastic, that we build up as time goes on.
I hope the preceding overview has given you a specific idea of what we're trying to accomplish with this application, and also the roadblocks to achieving those aims. There are a lot of challenges, but as we work through our user stories, we'll deal with them one by one, until we have a fast and functional Progressive Web App.
With the challenges mentioned, you can see the general trend: good performance and user experience under any condition. Certainly a worthy goal, and exactly why PWA techniques are applicable to any web app; they simply promise a better experience for everyone.
Once we start building our application, we'll also see that solving these problems is still a challenge, but all very achievable with React.
The next step is to get everything set up for our application, and create our basic folder structure with HTML and CSS.
First things first. Before we start building our React application, let's get set up with the basic HTML and CSS--the skeleton of our application, if you will, upon which we will heap the React muscles:
- Open up your Terminal and switch to whichever directory you want to store your project in.
- Then, we'll make our app directory with
mkdir chatastrophe. Let's go inside that folder, make another folder within it named
public, and within
touch index.html. If you're on Windows, use
type nul > index.htmlinstead of
- Then, open up the whole
chatastrophefolder in your text editor of choice. I'll be using Sublime Text 3 for this tutorial. Open up the
index.htmlfile, and let's write some HTML!
- Let's start with the basic HTML elements. Create a
<html>tag, and within that,
- This wouldn't be a programming tutorial without a hello world, so within the body, let's put
Hello world!within an
- Then, open up
index.htmlwithin your browser:
Our goal by the end of the chapter is to display the exact same as the preceding illustration, but using React to render our
Why did we put our
This is a distinction that will make more sense as we move into the React world, but for now, just know that the convention is to put HTML and static assets in the public folder.
Our good friend at the start-up (now dubbed Chatastrophe--what have you done?) has tapped a designer to provide some basic assets for us. These include a send icon for our chat box, and a logo for the application. You're not a fan of the style, but c'est la vie.
Let's go ahead and download the image files from https://github.com/scottdomes/chatastrophe-assets. You can download them by clicking on the
Clone or Download button, and then selecting
Download as Zip. Then, unzip those into the
public folder, in a new folder called
assets (all asset files should thus be in
Before we continue, we can ensure that our assets look okay by testing them in our
<h1>, let's put in an
img tag, with the
src set to
/img/logo.png, and an ID as
<img src=”assets/icon.png” id=”test-image”/>
Here's what it should look like:
This is even more beautiful.
The last thing we need to do is add our CSS. By the luck of the gods, all of our CSS has been mysteriously prepared for us, saving us the cumbersome task of styling our application. All we have to do is pull in
We include it in our
index.html with a link tag:
We should see an immediate change to our page. The background should be a gradiant, and the image should now have a slightly pulsing animation:
It worked! That does it for our main assets. Let's move on to some improvements to our HTML.
Our application will be mobile-first, as we have already discussed. To ensure that our HTML is fully optimized, let's add a bit more markup.
First, let's add a
DOCTYPE declaration to the top of
index.html. This tells the browser what kind of document to expect. In HTML 5 (the newest version of HTML), it always looks like this:
Next, we need to add a meta tag for
viewport width. It looks like this:
<meta name="viewport" content="width=device-width, initial-scale=1">
What does this do? Essentially, it tells the browser to display the web page at the same width as its screen. So, if the web page seems to be 960px and our device is 320px wide, rather than zooming out and showing the whole page, it'll instead squish all the content down until it's 320px.
As you might expect, this is only a good idea if your website is responsive and able to adapt to a smaller size. However, since responsiveness is one of our main goals, let's do this from the start. Add this tag within the
<head> of our document.
A couple more tags to go! The character set we use on our web page can be encoded in a couple of different ways: Unicode and ISO-8859-1. You can look up these encodings for more information, but long story short, we're using Unicode. Let's add it like so, right below the previous
While we're at it, let's add the language the HTML is in. On our existing
<html> tag, add
Okay, that about does it for HTML housekeeping. The last thing we need is a favicon, the little icon displayed next to the title in the browser tab. This is included in our assets bundle, so all we have to do is link it up (right underneath our
<link rel="shortcut icon" href="assets/favicon.ico" type="image/x-icon">
Your browser tab should now look like this:
With that, we're done!
Next, we'll look at how we will include React in our project, and all the other dependencies we will need.
<script> tag, and the browser downloads and runs it.
We'll be doing something similar with our React application (with considerable complications; more on that in Chapter 2, Getting Started with Webpack).
Okay, this is exciting, but why does this matter for our React project?
React is one such package. Webpack, mentioned earlier, is another one. In short, in order to build a complex web application, we will inevitably rely on a lot of other people's code, so we need packages, and we need Node's package manager (shorthand
npm) to install them.
We’ll also use
npm to start up our application and do some basic tasks, but its primary purpose is to manage packages.
Okay, enough said. Let's go ahead and install Node, which comes bundled with
- Go to https://nodejs.org and download the latest stable release of Node:
- Here, I would choose
v6.10.3, the one recommended for most users.
- Once that is installed, open up your terminal and run
node -vjust to confirm the installation:
- You can also confirm that
npmhas been included by running
npm a lot.
In the past year,
npm has come under fire for various reasons.
- It can be slow (just try installing large packages over a poor Wi-Fi connection)
- Its installation process can lead to different results for different developers on the same project
- It doesn't work offline, even if you've downloaded the package before
In response to these issues, Facebook came out with a package manager called Yarn. Yarn is essentially a wrapper around
npm, giving the same basic functionality with an extra layer of goodness. Let's go ahead and install it so that we can use it to manage our packages!
Visit https://yarnpkg.com/en/docs/install for installation instructions. For macOS, note that you'll need Homebrew (which is like
npm for macOS packages--packages everywhere!), which you can get at https://brew.sh/.
The next thing we need to do is initiate our application as an
npm project. Let's try it out, and then we'll discuss why we needed to do so:
- Inside your
projectfolder, in your terminal, type
yarn initand hit enter.
- It’ll ask you a series of questions. The first one is the most important--the name of our application. It should just take the name of the current folder (
chatastrophe). If it doesn't, just enter
chatastrophe. From there, just hit enter to skip the rest of the questions, accepting the default answers. These questions would matter more if we were planning on publishing our own package, but we're not, so no worries!
- If you take a look at your project folder after completing the yarn init, you'll notice that it added a
package.jsonfile with our project name and version. Our
package.jsonis important, in that it will act as a list of our dependencies--the package we will install via
Enough talking about dependencies, though, let's install our first one! What better choice than to install React?
Let’s try it by running
yarn add [email protected] from within your
We're installing a specific version of React (15.6.1) to ensure compatibility with other dependencies, and to ensure that there are no unexpected problems as new versions are released.
Once the installation is complete, you should see React added to our
package.json under dependencies. You'll also see that
yarn generated a
node_modules folder and a
node_modules folder is where all our packages will live. If you open it up, you can see that there are several folders already. We've not only installed React, but everything that React depends on--dependencies on dependencies.
As you might imagine, the
node_modules folder can get quite hefty. So, we don't check it into source control. When a new developer joins the team and downloads the project files, they can then install the dependencies independently, based on the
package.json; this saves time and space.
However, we need to ensure that they get the same packages as everyone else, and the same version; this is where the
yarn.lock file comes in.
The previously mentioned setup ensures that we are ready to safely use third-party libraries. We have the
node_modules folders in our project. Before we continue, let's ensure that adding React worked.
Let's confirm that React is in our project by using it to render a simple element to our screen. This will be our first dipping of our feet into React, so go slow and ensure that you understand each step.
First, we need to import our React package (which we just installed with
yarn) into our
index.html so that we can use it there.
To do this, we add a
<script> tag with the path to the main React file within our
node-modules folder. This tag looks like this:
Place this in your
index.html, at the bottom of the
body tag (before the closing
Okay, we have React! Let's use it to make a simple
<h1> tag, just like the one we wrote in HTML.
React has a function called
createElement for this purpose. It takes three arguments: element type, something called props (more on that later), and the children (what goes inside the tag).
For us, it looks like this:
React.createElement('h1', null, 'Hello from React!')
This function call creates an element that looks as follows:
<h1>Hello from React!</h1>
To confirm that it will work, let's
console.log it out:
<script src="../node_modules/react/dist/react.js"></script> <script> console.log(React.createElement('h1', null, 'Hello from react!')) </script>
index.html, then right-click or control-click and select
Inspect to open up DevTools in Chrome and switch to the
Console tab. There, we see our element… or not. Instead of the HTML output, we get something like this:
h1. Let’s see whether we can transform this into an actual HTML tag on the screen.
Here's a secret about React--it's a library for creating UIs, but not a library for rendering UIs. In itself, it has no mechanism for rendering a UI to the browser.
Fortunately, the creators of React also have a package called ReactDOM for exactly this purpose. Let's install it and then see how it works.
First, we install it with
yarn add [email protected].
Then, require it in
index.html in much the same way as React:
<body> <img src="assets/icon.png" id="test-image"/> <h1>Hello world!</h1> <div id="root"></div> <script src="../node_modules/react/dist/react.js"></script> <script src="../node_modules/react-dom/dist/react-dom.js"></script> <script> console.log(React.createElement('h1', null, 'Hello from react!')); </script> </body>
ReactDOM has a function called
render, which takes two arguments: the React element to be rendered to the screen (hey, we have that already!), and the HTML element it will be rendered inside.
So, we have the first argument, but not the second. We need something in our existing HTML we can grab and hook into; ReactDOM will inject our React element inside of it.
So, below our existing
<h1> tag, create an empty
div with the ID
Then, in our
ReactDOM.render function, we’ll pass in the React element, and then use
document.getElementById to grab our new
Here's what our
index.html should look like:
<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta charset="utf-8"> <link rel="stylesheet" href="assets/app.css"> <link rel="shortcut icon" href="assets/favicon.ico" type="image/x-icon"> </head> <body> <img src="assets/icon.png" id="test-image"/> <h1>Hello world!</h1> <div id="root"></div> <script src="../node_modules/react/dist/react.js"></script> <script src="../node_modules/react-dom/dist/react-dom.js"></script> <script> ReactDOM.render(React.createElement('h1', null, 'Hello from react!'), document.getElementById('root')); </script> </body> </html>
Reload the page, and you should see
'Hello from React!' text in the middle of the screen!
We will be diving deeper (much, much deeper) into both ReactDOM and React in the next few chapters. We'll learn how to create elements in a much more intuitive way, and also how React makes building UIs a dream.
For now, we have our project skeleton ready to go—the basis of our future application. Great work!
Our next step is to finish the last stage of preparation, and take a deep look at one of our most important dependencies--a tool called Webpack.