Transform any Meteor App into a PWA
Tags: meteor, javascript, pwa
Published: 2020-02-02 12:44:00

Okay, so you created and tested your Meteor app successfully. Now you want your users enjoy the benefits of a *pwa (progressive web app)*, too. Not that hard!

While Meteor does not bring all PWA features out-of-the-box ([yet!](https://github.com/meteor/meteor/blob/devel/Roadmap.md)), you only need to implement a few steps to support it:

1. Add a service worker
2. Provide a manifest file
3. Optimize for Desktop / Mobile use


While this can be applied to any Meteor app, we will start from scratch in order to make this tutorial easier to reproduce:

```bash
$ meteor create pwa-boilerplate
$ cd pwa-boilerplate && meteor npm install
$ meteor
```

When we test our fresh new app using [lighthouse](https://developers.google.com/web/tools/lighthouse/) we will get something like this:

![Screenshot of non-pwa Meteor app](https://dev-to-uploads.s3.amazonaws.com/i/bhcpnhuotq0noctjqdr2.png)

Fortunately most of these issues will already be resolved after the next two steps :tada:

## Step 1 - Add a service worker

The [service worker](https://developers.google.com/web/fundamentals/primers/service-workers/) is the core functionality that makes a pwa possible. If read the linked article and you know Meteor for a while, you may think at first how a HTTP baswed service worker may be compatible with Meteor's websocket protcoll DDP?

Well, there is fortunately a [repository with a Meteor-specific service worker](https://github.com/NitroBAY/meteor-service-worker) solution available. However, you still need to integrate the `sw.js` file in your project's public folder manually:

```bash
$ mkdir -p public
$ curl "https://raw.githubusercontent.com/NitroBAY/meteor-service-worker/master/sw.js" >> ./public/sw.js
$ # or: wget "https://raw.githubusercontent.com/NitroBAY/meteor-service-worker/master/sw.js" -O ./public/sw.js
```

Then you need to integrate the service worker into your startup code, otherwise it wont't be loaded at all. So let's create a startup file:

```bash
$ mkdir -p imports/startup/client
$ touch imports/startup/client/serviceWorker.js
```

and add a startup function to it, that checks installs the service worker or fails on any error with a console message:

```javascript
// serviceWorker.js
import { Meteor } from 'meteor/meteor'

Meteor.startup(() => {
  navigator.serviceWorker
    .register('/sw.js')
    .then(() => console.info('service worker registered'))
    .catch(error => { 
      console.log('ServiceWorker registration failed: ', error)
    })
})
```

Finally, don't forget to import the `serviceWorker.js` file in your `client/main.js` file:

```javascript
// client/main.js
import '../imports/startup/client/serviceWorker.js'
```

At this point the service worker is integrated but it is still missing important information to work properly. These are to be provided by the `manifest.json` file, which we will integrate in the next step. After that your app will have basic pwa support.

## Step 2 - Provide a manifest file

Similar to a `package.json` file, the [manifest file](https://developers.google.com/web/fundamentals/web-app-manifest) describes the properties of your pwa. Your app can hint your browser to load this manifest by placing the file inside the `public` folder and include a proper `link` tag in the html head.

Note, there is one drawback - you have to provide at least one 192x192px icon in order to make your pwa installable. If you don't have any icon available, you can use these ones:

<img src="https://dev-to-uploads.s3.amazonaws.com/i/id91jftqd0ualcuirtyf.png" width=64 height=64 alt="192 px image">

<img src="https://dev-to-uploads.s3.amazonaws.com/i/evszcjg6ix64lkcc5wb0.png" width=64 height=64 alt="512px image">


##### 2.1. Create manifest file

```bash
$ echo "{}" >> public/manifest.json
```

##### 2.2. Add minimal required content


```json
{
  "short_name": "mypwa",
  "name": "My Cool PWA",
  "start_url": "/",
  "display": "standalone",
  "icons": [
    {
      "src": "/images/icons-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/images/icons-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ]
}
```

##### 2.3. Add the images

```bash
$ mkdir -p public/images
$ cp /path/to/icons-192.png public/images/icons-192.png
$ cp /path/to/icons-512.png public/images/icons-512.png
```

##### 2.4. Link to the file in the head

```html
<head>
    <title>pwa-boilerplate</title>
    <link rel="manifest" href="/manifest.json">
</head>
```

Now let's check the pwa compatibility using a new lighthouse audit at this point. As you can see we are now supporting everything required to install the app as pwa:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rxfzy3swstpazspgf1ai.png)


**If you can't see the install button** while the audit clearly says it can be installed, then you might choose another port. Once an app is installed on that `url:port` combination the browser will think this app has already been installed.

Now let's add some last tweaks to make our app pwa optimized.

# Step 3 - Optimize for Desktop / Mobile use

First, add the missing properties to your `manifest.json` file. Read the [manifest guide](https://developers.google.com/web/fundamentals/web-app-manifest) to get an idea about what the properties can configure.

Then you should add a `noscript` fallback and important `head` entries in order to optimize viewport, icons etc.  

Finally, you need to ensure your traffic is always reroutes to https. This can be achieved by adding the zero-config Meteor package `force-ssl`.


##### 3.1. Complete manifest file

While the pwa will work fine without these, it may be beneficial to add some theming in order to provide a more embedded experience. Furthermore you may define a `scope`, that is a (sub-) route which your pwa will be restricted to. For example google restricts the pwa of it's maps application to `/maps`.

```json
{
  "short_name": "mypwa",
  "name": "My Cool PWA",
  "start_url": "/",
  "display": "standalone",
  "icons": [
    {
      "src": "/images/icons-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/images/icons-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "background_color": "#000000",
  "scope": "/",
  "theme_color": "#FFFFFF"
}
```

###### 3.2. Optimize head tags

Some of the following head properties may be redundant with the entries in the manifest file, so be sure to keep them updated.



```html
<head>
	<meta charset="utf-8">
	<meta name="application-name" content="my cool pwa">
	<meta name="viewport" content="initial-scale=1, maximum-scale=5, minimum-scale=-5"/>
	<meta name="theme-color" content="#FF00AA">
	<link rel="manifest" href="/manifest.json">
	<link rel="shortcut icon" type="image/png" href="/images/icons-192?v1" sizes="192x192">
	<link rel="apple-touch-icon" sizes="192x192" href="/images/icons-192.png">
	<link rel="fluid-icon" href="/images/icons-192.png?v1" title="app icon">
	<title>pwa-boilerplate</title>
        <noscript>
		<style>
			body:before { content: "Sorry, your browser does not support JavaScript!"; }
		</style>
	</noscript>
</head>
```

A special case is the [noscript tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript). You should place it in the head to make sure it is definitely covered on page load. If you place it in the body, it may not be detected when Javascript is disabled since Meteor applications may load the initial body content dynamically (depends on your rendering engine).

Furthermore, the html standard does not allow to place `link` `style` or `meta` elements [but not flow content](https://html.spec.whatwg.org/multipage/scripting.html#the-noscript-element). For this workaround we use the css `:before` selector and the `content` property to still being able to render the text into the default body.



###### 3.3. Force SSL

This is the last and easiest part. Just add the `force-ssl` package and make the lighthouse audit happy :tada: :tada: :tada:

```bash
$ meteor add force-ssl
```

Finally, you should be able to have a full pwa optimization support integrated into your app:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0gvmxgwu6auvz8pbkt5t.png)


Feel free to leave comments on any issues / problems you have in the process or if there is space for improvement. If you love to see more articles on specific Meteor topics, just let me know.

<hr>
I regularly publish articles here on dev.to about **Meteor** and **JavaScript**. If you like what you are reading and want to support me, you can [send me a tip via PayPal](https://paypal.me/kuesterjan).

You can also find (and contact) me on [GitHub](https://github.com/jankapunkt/), [Twitter](https://twitter.com/kuester_jan) and [LinkedIn](https://www.linkedin.com/in/jan-kuester/).

Keep up with the latest development on Meteor by visiting [their blog](https://blog.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors) and if you are the same into Meteor like I am and want to show it to the world, you should check out the [Meteor merch store](https://shop.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors).