“Write once, use everywhere”. I love this philosophy and I think this is the dream of every web developer: build an application that would be fully supported by all platforms (mobile, browser, desktop), without installation steps. This is a complex idea that Progressive Web Apps are trying to solve.
In this article, I will talk about a complete full-stack environment that helps me to kick off my PWA projects very quickly.
Before starting, I would like to share this github repository. This open-source project is a starter template that perfectly illustrates the points I will raise subsequently. So, if you need a concrete integration example, feel free to take a look. This stack is very opinionated, but it’s also what makes it powerful.
⚠️ Beware that the stack I am going to talk about does not necessarily fit your project’s needs.
Here are my needs:
I want a complete full-stack environment that helps me to focus on my developing skills to build Progressive Web Apps very quickly without compromising the best practices.
Let’s do it step by step!
Database + REST API
I will be very brief. We don’t want any database or REST API. Let me be more specific: We don’t want to manage them on our own. Why? Because it’s hard to host them and it’s more code to maintain. Nor do we want to deal with problematics like scaling etc. Keep in mind that what we want is to be fast during development!
Firestore from firebase seems to be a pretty good option! Thanks to a client API, we will be able to read and write data from a cloud NoSQL database and firestore security rules will help us to provide access control and data validation. In addition, Firestore offers an offline support for data persistence which is really interesting for PWAs. Firestore is also a realtime database. It means that I can listen to specific part of my data structure and get realtime updates on my application.
What if you need to run code in a secure context (not on client side)? For example trigger business logic when a client do a CRUD operation?
To deal with these specific cases, we can use firebase cloud functions.
Cool! We’re done with a huge part of our stack, and be sure that we’ve just eliminated a huge amount of code. That’s what we want!
You really should use a third party (email, phone, social) to authenticate your user. It’s less code for developers to write, and it’s simpler for our users to authenticate.
Once again, we can use firebase to save some development time. Firebase authentication provides a lot of methods to handle authentication:
- Social networks sign-in (Google, Facebook, Twitter, Github …)
- Phone authentication
- Email with authentication link
- Classic email/password
Firebase authentication providers
- 💅 UI component framework / CSS:
In a PWA context, I would recommend to write your own CSS and UI components. I think it’s hard to find a good lightweight UI component framework and our PWA needs to load fast. So if you decides to use a framework, be careful on how it will impact your application size.
- 🔁 Frontend state management:
Vuex is the default state manager of Vue.js and it’s really simple to use. It comes with vue-devtools which is an amazing browser extension to debug your application, trace events, and have a global view of what your data structure looks like in your app.
- ✅ Tests frameworks:
I personally like to use Cypress (Chrome only) for e2e tests and Jest for unit tests, but the most important is to use tests frameworks you like to work with.
- 👨✈️ Frontend quality tools:
One of our prerequisite is to produce quality code because a PWA must be fast and lightweight. Here are the tools we can use:
Bundlesize report example
Lighthouse (browser extension / chrome devtools): A great audit tool that generates reports for your web app pages. A lighthouse report will rate your web pages on several topics (performance, accessibility, best practices and progressive web app). These reports will also give you advises to improve the negative points you might obtain.
Lighthouse report example
Code Coverage (chrome devtools): This tool will display how much code was executed vs. how much was loaded on a web page. This will help you to see which part of your code you could lazyload, and ship only the code a user needs.
Prettier: Code format is something really important in my opinion. I don’t mind using semi-colon or not. The only thing that is important to me is having the code uniform so anyone can read it the same way. Wether you like it or not, Prettier will format the code its way, and that’s what is great about Prettier.
Eslint: Use as many linter rules as you can (within reasonable limits), this will structure your code and helps you to go over best practices. If you don’t want to write your own set of rules, I suggest to use existing eslint configurations. I love the airbnb one because it’s very strict and complete.
If you’re not familiar with PWA, I suggest you to read this.
In this part I am not going to talk about how service workers works, or how to create a web app manifest. There is already great articles and documentation about it, so I’ll focus on tools you can use for a good start and give you some advices based on my experience.
This library brings the Web App Manifest to non-compliant browsers for better Progressive Web Apps. It also comes with some other cool features like creating dynamic splash screen images for IOS (not supported by default on IOS yet).
This plugin will help you to configure your PWA with Workbox, which is today the best way to handle service workers. By default, your service worker file is generated from a basic JSON configuration (you have access to) for more simplicity. But if you want more control over your service worker configuration, you’ll have to write a service worker file yourself (refer to the official documentation for more details).
Vue-cli-plugin-pwa comes with the library register-service-worker that simplify service worker registration and exposes hooks for common service worker events like “updateFound” (when new content is available), “registered” (when service worker has been registered) etc.
A PWA is not a good PWA if it cannot works properly without network connection. A clean offline management requires two things:
- Static files caching: This is a required step if you want your application to just start without network connection. Hopefully you won’t have anything to configure if you’re using vue-cli-plugin-pwa. Otherwise you’ll have some workbox configuration to do.
- Dynamic caching: The easiest way would be to show an offline page, to tell the user he cannot access data without network connection, but we can do better. What we want is that the user can access data he already fetched before. If your data is coming from a firestore database, you can just enable offline persistence and firestore will take care of the rest. Otherwise you’ll have to cache requests responses with workbox.
Now we’re done with the basic configuration!
But remember that you can still improve your PWA user experience. Here is some examples of improvements you could add:
- Encouraging your IOS users to install your PWA:
- IOS does not automatically prompt user for web app installation as Chrome would do on Android devices. But you can easily overcome this shortcoming by displaying a modal prompt with clear and easy steps to install the app. Here is a great article about this.
Add to home screen modal for IOS
- Display a “new version is available” for your PWA:
- Have you ever been on a website and noticed a popup notification which informs you that there is a new version of the site available? This is exactly what I’m talking about. Thanks to this popup, the user will understand that he is not on the last version of your app, so he needs to reload to get a fresher the last one.
“New content available” popup
In many cases, Server Side Rendering is overkill. What I like about pre-rendering is that it won’t affect the way I’m writing my front-end app code or the architecture of my project, while getting almost all advantages of SSR. If you don’t know what pre-rendering is or when you should (not) use it, I suggest you to read this.
To pre-render our app pages, we can use prerender-spa-plugin. In short, this plugin fire up a headless browser, load your app’s routes and save the result to static HTML files. It means that all of my application will remain static, so we could easily host it.
Now we need to host our static website, and I think that you start to understand that I do like firebase 😉.
Firebase hosting will help us to quickly deploy our PWA to a CDN that will serve our content over a secure connection (HTTPS is required to use service workers in production).
Cool! We’re almost done. But we are lazy, and there is some parts that we can automate.
CircleCI is a great platform that will help us to run our tests, check code with our quality tools and deploy to firebase hosting when we push code to the remote repository. Here is the workflow we want:
CircleCI workflow example
- Install our project dependencies.
- When install dependencies step is complete, we will parallelize the following jobs: run e2e tests, run units tests, run linter and check our code format with prettier.
- If all of the previous jobs ended with success, we will build our app (with pre-rendering).
- Finally, if the targeted branch is the release one, we will deploy to firebase hosting.
Here is the CircleCI configuration file that corresponds to the above workflow.
With this full-stack solution, the only thing we need to care about is writing our PWA code, and this is exactly what we were looking for. To summarize:
- Firebase platform saves us A LOT of time, and takes care of the PWA hosting.
- Frontend quality tools help us to keep our code clean, fast and performant. Remember that it’s really important in a PWA context.
- A good CI/CD configuration allows to keep a permanent control over the code quality and spare time for app deployments.
- Consider using pre-rendering over SSR, because it’s easier to implement.
As already mentioned, checkout this github repository (that Thomas Betous and I have been working on) if you’re interested in building a PWA and don’t know where to start, or if you just want a concrete example of the stack I presented in this article.