Progressive Web Apps (PWA) @ Matrimony.com

image

At matrimony.com, our mobile was long due for a refresh and we wanted to make it the latest and greatest. That started us on looking the tech stack that it was running on and how we can change it.

Our teams were wetting their beaks on AngularJs and Single Page Applications(SPA). It was the right moment for us and progressive web app became a no-brainer. Coupled with the data that people generally try the mobile website for atleast a few times before installing the corresponding App, we kicked off this change.

And to share, we were amongst the top few internet properties in the India and the first in the matchmaking space to have our own PWA.

Read through the rest of this tech blog for a complete know-how about PWA/Service worker/ionic/browser compatibilities.

1. Why we needed a Progressive Web App?

a. Internet speed: 60% of the world’s population is still using 2G internet. That was true for us also where our customers accessing from tier-2 & 3 cities had challenges in stable internet connection.

b. Three seconds rule! 53% of users abandon a website if rendering takes more than 3 seconds.

c. Mobile site experience: We wanted our mobile site users to have an "app-like'' experience even in low memory smartphones.

d. App Support for Older Phones: As app support stops for older OS versions, PWA acts as the best alternative.

2. How PWA is different from a normal web experience?

"No Address Bar" pages

Launcher icon in home screen

Background updates

Offline Access

Push Notification

No full page refresh due to App shell

Instant Engagement

PWA is not a new framework or technology. It is a set of best practices to make a web application function similar to a desktop or mobile native application. User is usually unable to distinguish a PWA vs a Native App.

PWA provides experience that is consistently fast. You can program the service worker to cache the assets & data so it is extremely fast to start the app again even without hitting the network. Like native apps can access to a device’s functionalities with Cordova. The experience feels seamless and integrated.

And because we can send notifications to a user, we can really drive the engagement up by keeping the user notified and engaged with the service/business.

3. Building blocks of PWA:

a. Service Worker :
Service Workers are event-driven workers that run in the background of an application and act as a proxy between the network and application. They are able to intercept network requests and cache information for us in the background.

b. Web App Manifest file :
This is just a json file that gives meta information about the web app like App Name, Launcher icon in different dimensions. *Read about how we had a small customization done for our use case in the subsequent sections*

c. Frameworks:
Based on your requirement, you can choose any of the well known frameworks like React, Angular, Ember, Vue or even plain HTML/JS that suits you. We went with Ionic, to get off-the-shelf animations with UI wrappers for Android & iOS and we were fine with AngularJs.

d. Https :
Service worker requires that the web application is served over a secure connection.

4. PWA with Ionic

a. Typescript enforced - This allows to use OOPS and no errors in declaration.

b. When we started Ionic 2 was the stable version which in turn was based on Angular 2.

c. Rich App-like UI

d. Build based code deployment : The output of the build is only HTML,JS & CSS files that can be served with a light weight server like Nginx itself. We support all our 15 domains with this small setup. Only the first time hits or when newer versions are made available (which is identified by the service worker) would the hits come to this server to download the assets.

Ionic runs in NodeJs environment to generate build in the dev setup.

Once you have grasped all the pre-requesties, it all about coding it out for your application. The topics below would help in overcoming typical challenges that you might encounter in successful implementation of PWA.

4. Typical Challenges & Resolutions

a. Browser Compatibility:

When we started developing we gave undue importance to code & test in Chrome. But then as we ran our code in Safari, it started crashing out as Index DB wasn't supported in version 10 & below. This was overcome by changing the order of storage within the ionic framework library to use the local storage as the preferred option.

UC Browser was another challenge we had on the rendering part. Our UI team made more changes on the web components to render them smoothly on the UC browser as well.

b. Build Size:

Ours was a big application with about 20 different pages and lot of built in functionalities. Initially when we generated the build, the size came around 8 MB. This would not serve the purpose for which we were building a PWA.

We then found that there are 2 modes for creating a build:

a. Development Mode

b. Production Mode

In development mode there is no compression applied and all the commented lines would be retained. These get addressed in the Production Mode and we could achieve a site of 1.3 MB which was acceptable for our case.

Command to generate build: - "npm run build --prod"

c. Controlling Service Worker:

Service worker is a script that your browser runs in the background, separate from the web page. It include features like push notifications and background sync without a need for the app web page or user interaction.

Release Consideration:

We split our cache into 2 caches

a. Main Cache - which contains the application core files like main.js, main.css, polyfills.js and manifest.json

b. Asset Cache - All images used in the application except member photos for faster DOM processing.

The reason we took this approach is that we wanted to have control for handling the cache updates during release cycle. If we use only the default caching mechanism all the application core files and images would be cached in the single cache. In this case during updates, we have to remove the complete cache content and re-update cache. Users might experience a slight delay in loading the updated application for the first time and it will also use more band-width after each version release.

2. How client knows that there is a new version available?

Each service worker will have a version number associated with it (Major.Min.patch). Ex: Version 3.2.1

Major - Change this number for a big releases. For major changes, all cache is cleared and re-uploaded.

Min - Change this for enhancements. For minor changes, only Asset cache is cleared.

Patch - Fixes. No asset cache changes. Only application files.

In this way we have a controlled mechanism for handling the app caches and we found it very useful for the end user during each release cycle.

During the release process we will increment the version number based on the change management. Installed service worker at the client end always checks for the byte change in the service worker file at the server end. When that happens the browser will download the new version and install the newer version at the client end as a background process. Till the new version gets activated the old version continues to serve the application requests.

The main service worker events are install, activate & fetch.

'install' event is for service worker installation at the client.

'activate' for cache updates

'fetch' for handling network request & response

The below is our "activate" event code snippet for cache updates

self.addEventListener('activate', function(e) { 
	  console.log('[ServiceWorker] Activated'); 
	  e.waitUntil( 
		caches.keys().then(function(keyList) { 
		  return Promise.all(keyList.map(function(key) { 
			if (!cacheList.includes(key)) { 
			  console.log('[ServiceWorker] Removing old cache', key); 	 	
			  return caches.delete(key);
			} 
		  })); 
		})
	  ); 
	  return self.clients.claim();
	});

The last line in the code above ''return self.client.claim()" is a very handy functionality. Reason being that customers may not close their browsers at all and this piece of code helps to stop the service worker that is currently running and start the new worker. This would help customers get the application changes seamlessly.

'fetch' event customization:

ionic by default caches all network responses including API output. We recommend to omit API response caching as the size would increase and may evict asset cache.

In this event before triggering a request for an asset, service worker will first check if the requested asset is available in the cache. If it's there then it will return the asset without triggering a request in the network. If the asset is not there then it will trigger a request in the network, get the response and checks the status code for the response status. Only if the status code returned is "200" service worker will cache the response and then returns it to the application. Here we used the Cache first, falling back to network model.

Beware if the response is not 200 also, the cache is updated. So ensure you are putting the response code check as stated above.

d. PWA with AMP:

Ionic 2.0 didnt have support for lazy loading of modules as the entire build has to be loaded first. This takes few seconds to load due to the bigger bundle size. To overcome this issue during the initial load we implemented AMP (Accelerated Mobile Page) as our index page and this took under 2 seconds to load the index page. You can use this approach based on your needs but we found it very handy.

e. Deferring app install banner:

By default in PWA the App install banner will be shown in the initial screen itself. In our case its the login page, which was not the right place for our users to ask this action. We found a way to defer the event on the index page and call it again in post-login screen. This was a good learning for our team.

var deferredPrompt;
window.addEventListener('beforeinstallprompt', function(e) { 
e.preventDefault(); 
deferredPrompt = e; 
return false; 
});

f. ionic 3.0:

With Ionic 3.0 the above issue during first time load is also taken care. Instead of one big main.js, code is split into many small files. The files names can be 1.js, 2.js and ionic takes care of maintaining the order using vendor.js. With this we had a huge performance gain as only those modules that the customer is accessing is loaded.

g. Tracking crashes and other JS errors :

We integrated Sentry.io which is an Open-source error tracking that helps you to monitor and fix crashes in real time. Its not advisable to use sentry.io sdk in the production version. So we used to integrated the SDK during our QA process (pre-production build) and tracks all the crashes and JS errors during the testing phase itself. With this we can release a quality build to the production during each release cycle.

h. LightHouse

Light house report of Mobile Site (HTML5, CSS and JS) :

a. Performance - 11

b. Accessibility - 55

c. Best Practices - 64

d. SEO - 70

Light house report of PWA :

a. Performance - 90

c. Progressive Web App - 100

c. Accessibility - 80

c. Best Practices - 86

d. SEO - 90


References:

a. https://www.joshmorony.com/tag/pwa/

b. https://jakearchibald.com/2014/offline-cookbook/