PWA Tips and Tricks

In a previous post, I showed you how to optimise your CSS delivery.

In this post, I'll walk you through the Progressive Web App-related tips and tricks I've learned over the past few months.

Icons

Android and iOS

Each platform has different requirements and it's hard to keep track of everything you need to do. So why not let the Real Favicon generator do the work for you:

After you deploy your PWA don't forget to try the Real Favicon checker:

Splash Screens

Android

When your app first launches, it can take a moment for the browser to spin up and the initial content to begin rendering. Instead of showing a white screen (that may look to the user like the app has stalled), Chrome will show a splash screen.

Chrome will create the splash screen based on the contents of your PWA's Web App Manifest:

iOS

To make your Progressive Web App more native-like on iOS devices, you can add a custom splash screen that is displayed when users launch your app.

The iOS Splash Screen generator will create all the different sizes required:

As well as the accompanying HTML:

<link rel="apple-touch-startup-image"  href="assets/images/splash/iphone5_splash.png"
      media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" />
<link rel="apple-touch-startup-image" href="assets/images/splash/iphone6_splash.png"
      media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" />
<link rel="apple-touch-startup-image" href="assets/images/splash/iphoneplus_splash.png"
      media="(device-width: 621px) and (device-height: 1104px) and (-webkit-device-pixel-ratio: 3)" />
<link rel="apple-touch-startup-image" href="assets/images/splash/iphonex_splash.png"
      media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)" />
<link rel="apple-touch-startup-image" href="assets/images/splash/ipad_splash.png"
      media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2)" />
<link rel="apple-touch-startup-image" href="assets/images/splash/ipadpro1_splash.png"
      media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2)" />
<link rel="apple-touch-startup-image" href="assets/images/splash/ipadpro2_splash.png"
      media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)" />

Note: Inactive Progressive Web Apps appear as white screens unless you provide custom splash screens:

Meta Tags

Android

My PWA's index.html includes the following Android specific meta tags:

<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#488aff">

iOS

My PWA's index.html includes the following iOS specific meta tags:

<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="Brew">

<meta name="apple-mobile-web-app-status-bar-style" content="black">

<link rel="apple-touch-icon" sizes="152x152" href="apple-touch-icon-ipad.png" type="image/png">
<link rel="apple-touch-icon" sizes="167x167" href="apple-touch-icon-ipad-retina.png" type="image/png">
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon-iphone-retina.png" type="image/png">

<link rel="mask-icon" href="assets/images/icons/safari-pinned-tab.svg" color="#5bbad5">

The "apple-mobile-web-app-capable" and the "apple-mobile-web-app-title" meta tags are required by Safari to show the 'Add to Home' screen:

I use the content="black" style with the "apple-mobile-web-app-status-bar-style" meta tag:

Note: Add viewport-fit=cover to your "viewport" meta tag (to fix the iPhone X "notch" issue):

<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">

Web App Manifest

Android

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 users mobile device or desktop.

Having a manifest is required by Chrome to show the 'Add to Home screen':

Service Workers

In a previous post, I showed you how to add Angular's Service Worker (NGSW) module to an Ionic project.

iOS

Safari's support for Service Workers is currently experimental and I have encounted problems when trying to register a Service Worker in the same module that imports AngularFire2 and when trying to upload images to Firebase Storage.

To work around these issues I have updated the project's main.ts as follows:

...

platformBrowserDynamic().bootstrapModule(AppModule).then(() => {

  const isSafari = () => {
    return navigator.userAgent.indexOf('Safari') !== -1;
  };

  if (isSafari()) {
    console.log('Service Worker not registered');
  } else {
  
    if ('serviceWorker' in navigator && ENV.production) {
      navigator.serviceWorker.register('/ngsw-worker.js');
      console.log('Service Worker registered');
    }

  }

}).catch(error => console.log(error));

The project's core.module.ts:

...

import { AngularFireModule } from 'angularfire2';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreModule } from 'angularfire2/firestore';
import { AngularFireStorageModule } from 'angularfire2/storage';

import * as firebase from 'firebase';

import { ENV } from '@env';

...

@NgModule({
  imports: [

    AngularFireModule.initializeApp(ENV.firebase),
    AngularFireAuthModule,
    AngularFirestoreModule,
    AngularFireStorageModule,

    ...

    ServiceWorkerModule.register('/ngsw-worker.js', { enabled: ENV.production })

  ],
  exports: [],
  declarations: [],
  providers: [
  
    ...

    AuthService,
    DatabaseService,
    ServiceWorkerService,
    
    ...

    { provide: ErrorHandler, useClass: SentryErrorHandler },
    { provide: LoggerService, useClass: ConsoleLoggerService }

  ]
})
export class CoreModule {

  constructor( @Optional() @SkipSelf() parentModule: CoreModule,
               private afs: AngularFirestore) {

    throwIfAlreadyLoaded(parentModule, 'CoreModule');

    const settings = { timestampsInSnapshots: true };
    afs.app.firestore().settings(settings as firebase.firestore.Settings);
  }

}

...

The project's package.json:

...

  "dependencies": {
    ...
    "@firebase/app": "0.1.10",
    "angularfire2": "5.0.0-rc.6",
    "firebase": "4.13.1",
    "ionic-angular": "3.9.2",
    "promise-polyfill": "7.1.2",
    "raven-js": "^3.27.0",
    ...
  },
  "devDependencies": {
    ...
    "@ionic-angular/schematics": "^1.1.4",
    "@ionic/app-scripts": "^3.2.0",
    ...
    "@types/node": "~6.0.60",
    "firebase-admin": "^5.12.0",
    ...
    "typescript": ">=2.4.2 <2.5"
  }
  
  ...

Sentry

The upload image to Firebase Storage issue only appeared when testing the PWA on my iPhone. I was able to resolve the issue with the help of Sentry.

The project's sentry-error-handler.ts:

import { IonicErrorHandler } from 'ionic-angular';

import { ENV } from '@env';

import * as Raven from 'raven-js';

const ravenConfig =  {
  release: ENV.version
};

Raven.config(ENV.sentryDsn, ravenConfig).install();

export class SentryErrorHandler extends IonicErrorHandler {

  constructor() {
    super();
  }

  handleError(error: any): void {

    if (!ENV.production) {
      return super.handleError(error);
    }

    try {
      Raven.captureException(error.originalError || error);
    } catch (err) {}

  }
}
References:
Resources:
Additional Resources: