Skip to main content

Lessons learned developing a PWA with Create React App

I started working with React few year ago, always the project creation was from scratch, not using any template/scaffolding. Regarding bundlers normally I used either Webpack at work or Parcel for personal projects.

Few months ago, I wanted to start a personal project to keep track of my travel expenses. I was in a kind of rush because at that time, I was almost in the middle of my gap year, I wanted to focus on implementing main functionality and get an MVP (minimum viable product) the sooner the better, so I thought it was the right time to try out Create React App or CRA.

CRA allows you to have a production ready PWA in React quickly, which is awesome. They take care of configuration and package dependencies, you only have to take care of dependencies you need for your project and of course, implement your project, CRA is good, but is not magic.

As I said, I wanted to be implementing business logic ASAP, so together with using CRA, I also took other decisions/shortcuts driven by the need of speeding up the development pace, I will talk about those choices in following sections describing some drawbacks and benefits.

Chosen Technology Stack for Budget Tracker

So far I am quite happy with the outcome, but with the lessons learned while developing this app, in the future, with enough time, most likely I will not choose same technology stack again. You can try the application Budget Tracker and judge for yourself.

Along this post I will describe what are, in my experience, the benefits and drawbacks of taking these shortcuts and technical decisions.

Create React Application: CRA

Create React App doesn't support Web Workers neither allows to customize Service Worker implementation without ejecting .

Service worker

You might want to customize your Service Worker to send/receive post messages, to perform background sync or show web notifications. In that case, you will have to eject your project and maintain the configuration by yourself, which might imply a little bit of headache.

There are other options to customize service worker and avoid ejecting CRA, but they are not straightforward enough for my taste.

Web worker

If you need to perform any heavy processing without blocking the main thread, you can just use a Web Worker, but this feature is not supported by CRA. The Web Worker can communicate with main thread using post messages and it can also show web push notifications.

There are also other options to use Web Workers in CRA and not eject, but they imply quite some extra work.

Webpack

Webpack is the bundler used by CRA. You don't need to know much about it, unless you eject your project, then you will have to deal with Webpack configuration file, this is just a warning, just in case you are not comfortable with it.

Firebase

Budget Tracker supports data synchronization between different devices, so it requires a backend side to deal with authentication and to save/read data remotely. I considered two options: Firebase or implement REST API.

For this backend, I chose Firebase because it is super easy to implement, because there is nothing to implement. You only have to configure authentication methods and security rules for Firestore.

But Firebase brings some drawbacks you must know before choosing it.

Drawbacks

Bundle size

I got really shocked first time I analyzed Budget Tracker bundle size after integrating it with Firebase, it grew around a 39%!

Happily Budget Tracker implementation is following code-splitting principle, so user experience was not really affected with this integration. But user's device will eventually have to download this extra 39% (539KB).

Offline first, not really

This section is not relevant if your use case doesn't imply saving data linked to the user.

Firestore requires user to be authenticated, but user can be anonymous, this is really cool feature if you don't want to force the user to identify to use the app.

Another very useful and cool Firestore feature is that it supports offline mode, so data can be saved and read even there is no Internet connection.

Anonymous user + offline mode features will allow an application to work as offline first.

So... what is this "Offline first, not really" issue about? Let me explain a tricky scenario. First time the application is opened, Firebase needs to authenticate the user, to do so, user's device has to be connected to Internet, so you have to consider following scenario and either be OK with it or deal with it:

  1. PWA is installed in your device.
  2. User is not authenticated.
  3. User's device is offline.
  4. User opens the PWA and tries to save some data.
  5. That data won't be saved correctly, because there is no user to link the data with, not even an anonymous user, because application needs to call Firebase API to create an anonymous user.

This is not big deal, because it will seldom occur. If you want to deal with it anyway, check next section explaining how and why I did deal with this scenario.

How did I deal with this issue with Budget Tracker?

First of all, this might not be an issue for your use case, because it will happen only first time application is loaded. I just wanted Budget Tracker to be fully offline first, because it brings other benefits.

Implementation details
  • Implement 2 persistence layers: Local (IndexedDB) and Remote (Firestore).
  • Save always data locally, regardless user authentication status.
  • If there is any authenticated user, after saving to local layer, propagate same action to remote layer (Firestore) asynchronously.
Benefits
  • If user is not authenticated, Budget Tracker won't load Firestore client bundle. As I explained before, it is 27% of application size.
  • Application reads and writes are faster, because latest valid data is always saved locally.
  • Clarification: Save data in Firestore is also fast, because data is also cached locally, but it does a little bit more than just saving to IndexedDB and you need an authenticated user.

You can find a more detailed performance report, where I analyze 3 different implementations:

  1. Only Firestore client.
  2. Local (IndexedDB) and remote (Firestore) persistence layers.
  3. Same as previous one, but remote layer implemented in service worker.

The performance results were in general better for option 2.

Data model

Firestore API is easy and intuitive, I really like it, but don't assume it will have same features as other document DBs or SQL DBs.

Check if Firestore limitations fit into your data model, or if it is not too late, define your data model following Firestore best practices and having those limitations in mind.

Firebase alternatives

Besides implementing a REST API, there are other services similar to Firebase with smaller client bundle size and other features which might fit better to your requirements.

Consider other alternatives:

UI Components Library: Material UI

I chose Material UI: "React components for faster and easier web development. Build your own design system, or start with Material Design", quoting their website.

There were two reasons which drove me to use Material UI:

  • To create simple UI components which are accessible, responsive and with a consistent design is tricky and time consuming.
  • It has SVG set of Material Icons. Budget Tracker allows to create categories defined by a name and a selectable icon, so this icon set was really convenient.

There are some drawbacks, not very important in my opinion, maybe the most annoying for me is the first one:

Charts library

Many of the chart libraries I've found are really powerful and complete, but they are also heavy because they depend on other third party libraries like D3.

Initially I chose Victory, but I realized that I only needed charts to show percentages and time series and Victory's bundle size is 468KB minified.

After quick search in the Internet I discovered other lighter alternatives:

I tried them and I liked both. I chose Frappe charts because I thought its default color scheme fits better with Budget Tracker theme.

Both libraries come with more chart types than just bars and XY axis, take a quick look at their websites if you are interested about their supported chart set and to check how they look like.

Conclusion

I will try to come up with a conclusion better than: "It depends", "Your use case will tell you" and so on.

That said. It depends on your needs :p.

Seriously, let's play "do not go for ... if ...":

Do not go for CRA if

Do not go for Firestore if

  • You are aiming for your app to be hit by many users and you don't know the estimated amount of reads/writes, otherwise you might get surprised with the bill. Firestore scales like charm, maybe your budget doesn't.
  • Bundle size is critical for your web application. Remember that bundle size is not that critical if you are implementing a PWA, because your app files are cached.

Do not go blindly for the best charting library

First of all, check what kind of charts you need. In many applications you are OK with XY axis chart, time series, bars or pie charts. You can easily get an smaller bundle size by just using a simple charting library like Frappe charts or Chartist


Just check what are your requirements, if you are not sure about them, the technology stack I used for Budget Tracker consists of awesome products which most likely will fit your use case.

What next?

My next technology stack bet goes for Svelte/Sapper, it is promising project, the results for small projects are really impressive, mainly in regards to bundle size, it is ridiculously small and development experience is quick and intuitive.

I've created a tiny PWA to estimate currency exchange loss when you go to a money changer shop: currency-loss.netlify.com. Note, I got that app up and running in few hours, thanks to Svelte.

Comments

Comments powered by Disqus