1. Progressive Web Apps = Freedom #

The Web is the only free way to access the internet without a major gatekeeper like “Apple App Store” or “Google Play Store”.

Accessing the Web on mobile sucks. People prefer apps over browser tabs on mobile.

A B2B app can get away with having only a web version (that people use at work from their computer, not phone).

A B2C app is dead on arrival if it can not be installed on a phone.

Progressive Web Apps are a minimal compromise to make a web app “installable” on a mobile device. A PWA will have it’s own icon and launch without the browser controls. To make an app installable, you will need a manifest.json file.

The killer feature of having a PWA VS a browser tab is having Push Notifications. They can be managed via the service-worker.js file.

2. PWA in Rails 8 #

Recently DHH merged a PR into Rails Add default pwa manifest and service worker file #50528.

The PR adds basic manifest.json & service-worker.js files to a new Rails app.

Rails 8 will Release Action Notifier framework for push notifications

3. Activate Manifest.json #

Add a Manifest and make your app installable!

Chrome prompt to install PWA:

Install a PWA in Chrome

Edge prompt to install PWA:

Edge prompt to install app

If you added app to Dock in Safari, you get a prompt to open it in app:

If you added app to Dock in Safari, you get a prompt to open it in app

Current SupeRails manifest:

// app/views/pwa/manifest.json.erb
  "name": "<%= Rails.application.config_for(:settings).dig(:site, :name) %>",
  "short_name": "<%= Rails.application.config_for(:settings).dig(:site, :short_name) %>",
  "icons": [
      "src": "/logo.png",
      "type": "image/png",
      "sizes": "512x512"
      "src": "/logo.png",
      "type": "image/png",
      "sizes": "512x512",
      "purpose": "maskable"
  "start_url": "/",
  "id": "/",
  "display": "standalone",
  "scope": "/",
  "description": "<%= Rails.application.config_for(:settings).dig(:site, :description) %>",
  "categories": "<%= Rails.application.config_for(:settings).dig(:site, :keywords) %>",
  "theme_color": "#f43f5e",
  "background_color": "#f43f5e",
  "shortcuts": [
      "name": "<%= t('posts.index.title') %>",
      "description": "<%= t('posts.index.subtitle') %>",
      "url": "<%= posts_path %>",
      "icons": [{ "src": "<%= image_url("pwa/shortcuts/play-circle.svg") %>", "sizes": "96x96" }]
      "name": "<%= t('playlists.index.title') %>",
      "description": "<%= t('playlists.index.subtitle') %>",
      "url": "<%= playlists_path %>",
      "icons": [{ "src": "<%= image_url("pwa/shortcuts/playlist.svg") %>", "sizes": "96x96" }]
  "screenshots": [
      "src": "<%= image_url("pwa/screenshots/narrow.png") %>",
      "sizes": "850x1628",
      "form_factor": "narrow",
      "label": "<%= Rails.application.config_for(:settings).dig(:site, :description) %>"
      "src": "<%= image_url("pwa/screenshots/wide.png") %>",
      "sizes": "3420x1964",
      "form_factor": "wide",
      "label": "<%= Rails.application.config_for(:settings).dig(:site, :description) %>"

Inspect manifest in Chrome Dev Tools:

Inspect manifest in Chrome Dev Tools

Be sure that you also have these links in your app/views/layouts/application.html.erb:

  <link rel="manifest" href="/manifest.json">
  <link rel="icon" href="/logo.png" type="image/png">
  <link rel="icon" href="/logo.svg" type="image/svg+xml">
  <link rel="apple-touch-icon" href="/logo.png">

You can even have multiple isolated instances of a PWA running on one devise:

Multiple instances of a PWA

4. Activate Service Worker #

Service worker can:

  • manage push notifications 💪💪💪
  • cache pages so that your app works offline 💪💪💪

To start, activate an empty service worker with this script:

// app/javascript/application.js

if ('serviceWorker' in navigator) {
  // Register the service worker
    .then(function(registration) {
      console.log('Service Worker registered with scope:', registration.scope);
    .catch(function(error) {
      console.log('Service Worker registration failed:', error);
// app/views/pwa/service-worker.js

// The install event is fired when the service worker is first installed
self.addEventListener('install', function(event) {
  console.log('Service Worker installed');

// The activate event is fired after the install event when the service worker is actually controlling the page
self.addEventListener('activate', function(event) {
  console.log('Service Worker activated');

Now you have a useless service worker running:

Service Worker activated on a PWA

Service workers can get really sophisticated. I will not dig into this rabbithole now.

Campfire already has all the PWA goodies.

Let’s wait for smart people to extract it into Action Notifier framework for push notifications.

Useful resources #