DEV Community

loading...
Cover image for Transform any Meteor App into a PWA

Transform any Meteor App into a PWA

Jan Küster
Graduated in Digital Media M.Sc. now developing the next generation of educational software. Since a while I develop full stack in Javascript using Meteor. Love fitness and Muay Thai after work.
Updated on ・5 min read

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!), 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:

$ meteor create pwa-boilerplate
$ cd pwa-boilerplate && meteor npm install
$ meteor
Enter fullscreen mode Exit fullscreen mode

When we test our fresh new app using lighthouse we will get something like this:

Screenshot of non-pwa Meteor app

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

Step 1 - Add a service worker

The service worker 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 solution available. However, you still need to integrate the sw.js file in your project's public folder manually:

$ 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
Enter fullscreen mode Exit fullscreen mode

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:

$ mkdir -p imports/startup/client
$ touch imports/startup/client/serviceWorker.js
Enter fullscreen mode Exit fullscreen mode

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

// 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)
    })
})
Enter fullscreen mode Exit fullscreen mode

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

// client/main.js
import '../imports/startup/client/serviceWorker.js'
Enter fullscreen mode Exit fullscreen mode

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 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:

192 px image

512px image

2.1. Create manifest file
$ echo "{}" >> public/manifest.json
Enter fullscreen mode Exit fullscreen mode
2.2. Add minimal required content
{
  "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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode
2.3. Add the images
$ 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
Enter fullscreen mode Exit fullscreen mode
2.4. Link to the file in the head
<head>
    <title>pwa-boilerplate</title>
    <link rel="manifest" href="/manifest.json">
</head>
Enter fullscreen mode Exit fullscreen mode

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

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 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.

{
  "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"
}
Enter fullscreen mode Exit fullscreen mode
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.

<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>
Enter fullscreen mode Exit fullscreen mode

A special case is the noscript tag. 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. 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 🎉 🎉 🎉

$ meteor add force-ssl
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

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.

Discussion (23)

Collapse
lizzymendivil profile image
Lizzy Mendivil

Hi, I just followed your guide but it did not work. Maybe my chrome does not support service worker? I am working with this: Version 80.0.3987.122 (Official Build) (64-bit) Could help me please? :(

Collapse
jankapunkt profile image
Jan Küster Author

Hi Lizzy, what does the chrome console say?

Collapse
lizzymendivil profile image
Lizzy Mendivil

I added this:
if ("serviceWorker" in navigator) {
in serviceWorker.js file
So it never enters into the conditional

Thread Thread
jankapunkt profile image
Jan Küster Author

Please try the code as from the tutorial:

// 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)
    })
})

does it throw an Error? If so, could you please show me the error stack?

Thread Thread
lizzymendivil profile image
Lizzy Mendivil
serviceWorker.js:5 Uncaught TypeError: Cannot read property 'register' of undefined
    at serviceWorker.js:5
    at maybeReady (meteor.js?hash=857dafb4b9dff17e29ed8498a22ea5b1a3d6b41d:938)
    at HTMLDocument.loadingCompleted (meteor.js?hash=857dafb4b9dff17e29ed8498a22ea5b1a3d6b41d:950)
Thread Thread
jankapunkt profile image
Jan Küster Author

Hm there seems to be no service worker in navigator. Is this a private session or do you block all cookies? I think there were some sw issues at least in Firefox that were related to these.

Thread Thread
lizzymendivil profile image
Lizzy Mendivil

well, i am using a virtual machine with ubuntu 16.04 but i connect through ssh and a network location and open the application in my window (physical machine)

Thread Thread
lizzymendivil profile image
Lizzy Mendivil

So, my url is 172.21.19.91:3000/
could it be the problem?

Thread Thread
lizzymendivil profile image
Lizzy Mendivil

JIC, I helped me
stackoverflow.com/questions/522992...

Now it is working fine! Thanks!

Collapse
borisimple profile image
Boris Krstić

I enjoyed working with Meteor back in 2016. I think this framework was just a bit ahead of its time 😏

Collapse
jankapunkt profile image
Jan Küster Author

Hey Boris, yes their lead developer is constantly on the edge of Js.
Btw, you should check out the roadmap: github.com/meteor/meteor/blob/mast...

Also note that Meteor has been aqquired by Tiny: blog.meteor.com/a-new-chapter-for-...

Collapse
harryadel profile image
Harry Adel

Great icons :D

Collapse
jankapunkt profile image
Jan Küster Author • Edited

In the past I used to spend time and engery on design things but I realized that it won't be better with more time spent. Instead I saved the effort for writing. ;-)

Collapse
harryadel profile image
Harry Adel

yeah, great and informative article nonetheless

Collapse
mullojo profile image
Bob

This guide worked really great for me! 🎉 I now have a desktop & mobile installable app, amazing!! Thanks Jan!

Collapse
chaawlaapooja profile image
chaawlaapooja • Edited

Hello Jan,
Thanks for the article. I followed the steps mentioned above and my app is now PWA compliant. Can you help me on how to implement features like push notifications to make it behave like native app?

Collapse
jankapunkt profile image
Jan Küster Author • Edited

I currently did not implement push notifications in my pwas but I am planning a follow up article on specific pwa features that bases on this article. In the meantime you may check out the package registry atmospherejs.com for packages that provide push notifications.

Collapse
chaawlaapooja profile image
chaawlaapooja

Thanks!

Collapse
ramprakashram profile image
Ram

Hi, i added force-ssl, still i am getting "Does not redirect HTTP traffic to HTTPS" error when i run lighthouse. Could you help me please ???

Collapse
jankapunkt profile image
Jan Küster Author

I assume you are on localhost? Then this is expected behavior. It will work on your deployment, assuming your certificate is correctly installed.

Collapse
ramprakashram profile image
Ram

Awesome, it worked fine on Deploying. Thank you Jan.
BTW, can you write a article for prompting user to install app on iOS.

Collapse
desdichado profile image
Pontus Kindblad

I got the error: start_url does not respond with a 200 when offlineThe start_url did respond, but not via a service worker.

Collapse
coderjamal profile image
jamal coder

as Pontus Kindblad mentioned, me too i have the same issue ( error: start_url does not respond with a 200 when offlineThe start_url did respond, but not via a service worker).

the SW is very basic and does not handle post requests, so at least you can avoid login errors in your console by editing this line like that.

if(event.request.method === "GET") caches.open(version).then(cache => cache.put(event.request, clonedResponse));

if anyone want his/her application to handle post requests offline, just google it.