Progressive Web Apps are a (terribly named) wonderful idea. You can build an app once using web technologies which serves all devices and form factors. It can be accessible over the web, but also surface on the home screen of your Android / iOS device. That app can work offline, have a splash screen when it launches and have notifications too.
PWAs can be a money saver for your business. The alternative, should you want an app experience for your users, is building the same application using three different technologies (one for web, one for Android and one for iOS). When you take this path it's hard to avoid a multiplication of cost and complexity. It often leads to dividing up the team as each works on a different stack. It's common to lose a certain amount of focus as a consequence. PWAs can help here; they are a compelling alternative, not just from a developers standpoint, but from a resourcing one too.
However, the downside of PWAs is that they are more complicated than normal web apps. Writing one from scratch is just less straightforward than a classic web app. There are easy onramps to building a PWA that help you fall into the pit of success. This post will highlight one of these. How you can travel from zero to a PWA of your very own using React and TypeScript.
This post presumes knowledge of:
To create our PWA we're going to use
create-react-app. This excellent project has long had inbuilt support for making PWAs. In recent months that support has matured to a very satisfactory level. To create ourselves a TypeScript React app using
create-react-app enter this
npx command at the console:
This builds you a react web app built with TypeScript; it can be tested locally with:
From web app to PWA is incredibly simple; it’s just a question of opting in to offline behaviour. If you open up the
index.tsx file in your newly created project you'll find this code:
As the hint suggests, swap
serviceWorker.register() and you now have a PWA. Amazing! What does this mean? Well to quote the docs:
- All static site assets are cached so that your page loads fast on subsequent visits, regardless of network connectivity (such as 2G or 3G). Updates are downloaded in the background.
- Your app will work regardless of network state, even if offline. This means your users will be able to use your app at 10,000 feet and on the subway.
... it will take care of generating a service worker file that will automatically precache all of your local assets and keep them up to date as you deploy updates. The service worker will use a cache-first strategyfor handling all requests for local assets, including navigation requests for your HTML, ensuring that your web app is consistently fast, even on a slow or unreliable network.
Under the bonnet,
create-react-app is achieving this through the use of technology called "Workbox". Workbox describes itself as:
a set of libraries and Node modules that make it easy to cache assets and take full advantage of features used to build Progressive Web Apps.
The good folks of Google are aware that writing your own PWA can be tricky. There's much new behaviour to configure and be aware of; it's easy to make mistakes. Workbox is there to help ease the way forward by implementing default strategies for caching / offline behaviour which can be controlled through configuration.
A downside of the usage of
create-react-app is that (as with most things
create-react-app) there's little scope for configuration of your own if the defaults don't serve your purpose. This may change in the future, indeed there's an open PR that adds this support.
But it's not just an offline experience that makes this a PWA. Other important factors are:
- That the app can be added to your home screen (A2HS AKA "installed").
- That the app has a name and an icon which can be customised.
- That there's a splash screen displayed to the user as the app starts up.
All of the above is "in the box" with
create-react-app. Let's start customizing these.
First of all, we'll give our app a name. Fire up
index.html and replace
<title>React App</title> with
<title>My PWA</title>. (Feel free to concoct a more imaginative name than the one I've suggested.) Next open up
manifest.json and replace:
Your app now has a name. The question you might be asking is: what is this
manifest.json file? Well to quote the good folks of Google:
The web app manifest is a simple JSON file that tells the browser about your web application and how it should behave when 'installed' on the user's mobile device or desktop. Having a manifest is required by Chrome to show the Add to Home Screen prompt.
A typical manifest file includes information about the app name, icons it should use, the start_url it should start at when launched, and more.
manifest.json is essentially metadata about your app. Here's what it should look like right now:
You can use the above properties (and others not yet configured) to control how your app behaves. For instance, if you want to replace icons your app uses then it's a simple matter of:
- placing new logo files in the
- updating references to them in the
- finally, for older Apple devices, updating the
<link rel="apple-touch-icon" ... />in the
So far, we have a basic PWA in place. It's installable. You can run it locally and develop it with
yarn start. You can build it for deployment with
What this isn't, is recognisably a web app. In the sense that it doesn't have support for different pages / URLs. We're typically going to want to break up our application this way. Let's do that now. We're going to use
react-router; the de facto routing solution for React. To add it to our project (and the required type definitions for TypeScript) we use:
Now let's split up our app into a couple of pages. We'll replace the existing
App.tsx with this:
This will be our root page. It has the responsiblity of using
react-router to render the pages we want to serve, and also to provide the links that allow users to navigate to those pages. In making our changes we'll have broken our test (which checked for a link we've now deleted), so we'll fix it like so:
App.test.tsx with this:
You'll have noticed that in our new
App.tsx we import two new components (or pages);
Home. Let's create those. First
Now we've split up our app into multiple sections, we're going to split the code too. A good way to improve loading times for PWAs is to ensure that the code is not built into big files. At the moment our app builds a
single-file.js. If you run
yarn build you'll see what this looks like:
build/static/js/main.bc740179.chunk.js file. This is our
single-file.js. It represents the compiled output of building the TypeScript files that make up our app. It will grow and grow as our app grows, eventually becoming problematic from a user loading speed perspective.
create-react-app is built upon webpack. There is excellent support for code splitting in webpack and hence create-react-app supports it by default. Let's apply it to our app. Again we're going to change
Where we previously had:
Let's replace with:
This is the syntax to lazily load components in React. You'll note that it internally uses the dynamic
import() syntax which webpack uses as a "split point".
Let's also give React something to render whilst it waits for the dynamic imports to be resolved. Just inside our
<Router> component we'll add a
<Suspense> component too:
<Suspense> component will render the
<div>Loading...</div> whilst it waits for a routes code to be dynamically loaded. So our final
App.tsx component ends up looking like this:
This is now a code split application. How can we tell? If we run
yarn build again we'll see something like this:
Note that we now have multiple
*.chunk.js files. Our initial
main.*.chunk.js and then
As we continue to build out our app from this point we'll have a great approach in place to ensure that users load files as they need to and that those files should not be too large. Great performance which will scale.
Now that we have our basic PWA in place, let's deploy it so the outside world can appreciate it. We're going to use Netlify for this.
The source code of our PWA lives on GitHub here: https://github.com/johnnyreilly/pwa-react-typescript
We're going to log into Netlify, click on the "Create a new site" option and select GitHub as the provider. We'll need to authorize Netlify to access our GitHub.
You may need to click the "Configure Netlify on GitHub" button to grant permissions for Netlify to access your repo like so:
Then you can select your repo from within Netlify. All of the default settings that Netlify provides should work for our use case:
Let's hit the magic "Deploy site" button! In a matter of minutes you'll find that Netlify has deployed your PWA.
If we browse to the URL provided by Netlify we'll be able to see the deployed PWA in action. (You also have the opportunity to set up a custom domain name that you would typically want outside of a simple demo such as this.) Importantly this will be served over HTTPS which will allow our Service Worker to operate.
Now that we know it's there, let's see how what we've built holds up according to the professionals. We're going to run the Google Chrome Developer Tools Audit against our PWA:
That is a good start for our PWA!