How to Build a Progressive Web App with Ruby on Rails 8: A Complete Guide

Table of contents
- What Makes a Progressive Web App Stand Out?
- Why Choose Ruby on Rails for Your PWA?
- Step 1: Kickstart Your Rails Application
- Step 2: Define Your App with a Web App Manifest
- Step 3: Enable Offline Support with a Service Worker
- Step 4: Engage Users with Push Notifications
- Step 5: Optimize Performance for Fast Loading
- Step 6: Test Your PWA on Multiple Devices
- Step 7: Deploy Your PWA to Production
- Step 8: Maintain and Improve Your PWA
- Conclusion

Progressive Web Apps (PWAs) represent the next generation of web applications, offering a blend of web and native app experiences.
With features like offline capabilities, push notifications, and installation prompts, PWAs provide a powerful way to enhance user engagement.
In this article, we’ll explore how to build a PWA using Ruby on Rails—focusing on Rails 8 and Hotwire—without relying on Webpacker or external libraries like Firebase.
What Makes a Progressive Web App Stand Out?
A Progressive Web App (PWA) combines the best of web and mobile apps, offering:
Offline Functionality: Users can access the app even without an internet connection.
Installability: PWAs can be added to the home screen, providing a native app-like experience.
Push Notifications: Engage users by sending timely updates directly to their devices.
Responsive Design: PWAs adapt seamlessly to various screen sizes and orientations.
To qualify as a PWA, an app must include:
A Web App Manifest: A JSON file with metadata about the app.
A Service Worker: A script to handle caching and offline features.
Secure HTTPS: PWAs must be served over HTTPS to ensure security.
Why Choose Ruby on Rails for Your PWA?
Rails 8 introduces a streamlined development experience with Hotwire (Turbo and Stimulus), eliminating the need for complex JavaScript bundlers like Webpacker. This makes Rails an excellent choice for building PWAs.
Hotwire’s Turbo Drive and Turbo Streams simplify real-time interactions, while Stimulus enhances interactivity with minimal JavaScript.
Key advantages of Rails 8 for PWAs:
Turbo Drive: Automatically enhances navigation for faster transitions.
Turbo Streams: Enables real-time updates without manual JavaScript coding.
Stimulus: Provides a simple way to add interactivity.
Server-Rendered Content: Reduces reliance on heavy front-end frameworks.
Step 1: Kickstart Your Rails Application
Start by creating a new Rails 8 application:
rails new pwa_demo
cd pwa_demo
Hotwire is included by default in Rails 8. Verify its presence:
Turbo:
app/javascript/controllers
Stimulus:
app/javascript/controllers/application.js
Step 2: Define Your App with a Web App Manifest
The manifest file is the foundation of a PWA. It defines the app’s name, icons, theme color, and display settings.
Adding a Manifest File
Create app/views/home/manifest.json.jbuilder
:
{
"name": "PWA Demo App",
"short_name": "PWA Demo",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/assets/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/assets/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Add a route in config/routes.rb
to serve the manifest:
Rails.application.routes.draw do
root "home#index"
get "/manifest.json", to: "home#manifest", defaults: { format: :json }
end
Create a HomeController
to handle the manifest:
class HomeController < ApplicationController
def index
end
def manifest
end
end
Update app/views/layouts/application.html.erb
to include the manifest:
<head>
<meta name="theme-color" content="#000000">
<link rel="manifest" href="/manifest.json">
</head>
Step 3: Enable Offline Support with a Service Worker
Service workers enable offline functionality by caching resources and intercepting network requests.
Creating a Service Worker
Add app/assets/javascript/service_worker.js
:
const CACHE_NAME = "pwa-demo-cache-v1";
const urlsToCache = ["/", "/manifest.json"];
// Install event
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});
// Fetch event
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
Register the service worker in app/javascript/application.js
:
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/service-worker.js").then((registration) => {
console.log("ServiceWorker registered: ", registration);
}).catch((error) => {
console.error("ServiceWorker registration failed: ", error);
});
});
}
Add a route in config/routes.rb
:
get "/service-worker.js", to: "home#service_worker"
Update HomeController
to serve the service worker:
def service_worker
respond_to do |format|
format.js { render file: Rails.root.join("app/assets/javascript/service_worker.js") }
end
end
Step 4: Engage Users with Push Notifications
Push notifications are a powerful feature for engaging users in your Progressive Web App (PWA).
You can implement push notifications using the webpush
gem, which allows you to send notifications directly to users' devices.
For this, you will need to generate VAPID keys, set up Web Push configuration, and then send notifications.
Install the webpush
Gem
To get started with push notifications, you need to install the webpush
gem, which helps send push notifications to users.
Add the webpush
gem to your Gemfile:
gem 'webpush'
Then run:
bundle install
Generate VAPID Keys
VAPID (Voluntary Application Server Identification) keys are required to authenticate your push notifications. These keys allow the push service to verify that the push request comes from your app.
To generate the VAPID keys, open the Rails console:
rails console
Then, run the following command to generate the VAPID keys:
vapid_key = Webpush.generate_key
puts "Public Key: #{vapid_key.public_key}"
puts "Private Key: #{vapid_key.private_key}"
This will output the Public Key and Private Key to the console. These keys will be used to configure your push notifications.
Set the VAPID Keys as Environment Variables
To securely store your VAPID keys, it's best to set them as environment variables, especially for production environments.
- In your
.env
file (for development), add the generated keys:
VAPID_PUBLIC_KEY=your_public_key_here
VAPID_PRIVATE_KEY=your_private_key_here
- If you're deploying to production (e.g., Heroku), set the keys as environment variables in your server:
heroku config:set VAPID_PUBLIC_KEY=your_public_key_here
heroku config:set VAPID_PRIVATE_KEY=your_private_key_here
Configure Web Push in Rails
After storing the VAPID keys as environment variables, configure the webpush
gem to use these keys.
Create a new initializer file in your Rails app at config/initializers/webpush.rb
:
# config/initializers/webpush.rb
Webpush.configure do |config|
config.vapid_public_key = ENV["VAPID_PUBLIC_KEY"] # Fetch the public key from environment
config.vapid_private_key = ENV["VAPID_PRIVATE_KEY"] # Fetch the private key from environment
config.vapid_subject = "mailto:your-email@example.com" # Set the contact email for VAPID
end
config.vapid_public_key
andconfig.vapid_private_key
fetch the VAPID keys from the environment variables.config.vapid_subject
is an email address that identifies the sender for push notifications.
Sending Push Notifications
With the VAPID keys configured, you can now send push notifications to users. Here’s an example of how to send a push notification:
Create a method to send push notifications:
def send_push_notification(subscription, message)
Webpush.payload_send(
message: message, # The message you want to send to the user
endpoint: subscription[:endpoint], # The user's subscription endpoint
p256dh: subscription[:keys][:p256dh], # The user's public key
auth: subscription[:keys][:auth], # The user's auth secret
vapid: {
subject: "mailto:your-email@example.com", # Your contact email
public_key: ENV["VAPID_PUBLIC_KEY"], # The VAPID public key
private_key: ENV["VAPID_PRIVATE_KEY"] # The VAPID private key
}
)
end
In this method:
subscription
is the data received from the client when they subscribe for push notifications.message
is the content of the push notification you want to send.
Subscribe Users to Push Notifications
You’ll need to handle the client-side subscription process. When a user opts in to receive push notifications, you need to subscribe them and store their subscription details (like the endpoint and keys).
On the client side, use JavaScript to request permission for push notifications and register a service worker. Once the user subscribes, send the subscription information to your Rails server to store and use for sending notifications.
Here's an example of how to subscribe the user in JavaScript:
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.ready.then(function(registration) {
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey)
}).then(function(subscription) {
// Send subscription details to your server to store it
fetch('/subscriptions', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'Content-Type': 'application/json'
}
});
});
});
}
Testing Push Notifications
To test push notifications, you can use the Chrome Developer Tools:
Open your app in Chrome.
Open DevTools (right-click > Inspect).
Navigate to the Application tab and look for Service Workers and Push Notifications.
You can simulate push notifications from the Service Worker section and check if the push notification works as expected.
Step 5: Optimize Performance for Fast Loading
PWAs thrive on speed. Use Rails’ asset pipeline and caching mechanisms to minimize load times.
Asset Compression
Enable gzip compression for static assets. Add the rack-deflater
gem to your Gemfile:
gem 'rack-deflater'
Update config/application.rb
to include:
config.middleware.use Rack::Deflater
Browser Caching
Set far-future expiry headers for assets in production. Update config/environments/production.rb
:
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=31536000'
}
Lazy Loading
Rails 6+ supports native lazy loading for images. Use the loading="lazy"
attribute in your views:
<%= image_tag "example.jpg", loading: "lazy" %>
Step 6: Test Your PWA on Multiple Devices
Testing ensures your PWA works seamlessly across devices and platforms. PWAs are designed to provide a consistent experience regardless of the user's device, so it’s essential to test it on multiple environments.
Google Lighthouse
Run a Lighthouse audit directly in Chrome DevTools to evaluate your app's performance, accessibility, and overall PWA compliance:
Open the application in Chrome.
Go to DevTools (right-click > Inspect or press
Cmd + Option + I
on macOS orCtrl + Shift + I
on Windows).Navigate to the "Lighthouse" tab in the DevTools panel.
Click on "Generate report" after selecting the mobile or desktop configuration. This will provide you with a comprehensive performance report with suggestions for improvements.
Testing in Different Browsers and Devices
While Lighthouse is helpful, it’s equally important to test on real devices and browsers to verify how your app behaves in various scenarios:
Mobile devices: Test the PWA on both Android and iOS devices. Ensure that it installs correctly, works offline, and sends push notifications. PWAs on iOS have certain limitations, such as lacking support for service workers in Safari on older versions of iOS, so be mindful of these.
Cross-browser compatibility: Ensure the PWA works smoothly across browsers like Chrome, Firefox, Edge, and Safari. Pay attention to any issues with service workers or manifest file configurations that may arise in different environments.
Network conditions: Test the app with different network speeds (including offline) to see how well it handles caching and service worker-based offline behavior.
Handling Edge Cases
Ensure that edge cases such as slow connections, interrupted services, or failed push notifications are properly handled. For example:
Offline behavior: Make sure that important routes or pages are cached and available offline.
Push notifications fallback: When push notifications fail, provide a fallback UI to notify users.
Step 7: Deploy Your PWA to Production
Once you've completed testing, it's time to deploy your app. For PWAs to work as intended, they must be served over HTTPS. Here’s how to deploy your Rails 8 PWA:
Deploy to Heroku
Heroku is an easy platform for deploying Rails applications. To deploy your app:
Initialize a Git repository if you haven't already:
git init git add . git commit -m "Initial commit"
Create a Heroku app and push your changes:
heroku create pwa-demo git push heroku main
Set up your environment variables for VAPID keys in Heroku:
heroku config:set VAPID_PUBLIC_KEY=your_public_key heroku config:set VAPID_PRIVATE_KEY=your_private_key
Run database migrations:
heroku run rake db:migrate
Once your app is live, test again on the deployed environment to ensure everything is working as expected.
Deploy to Other Platforms
For other hosting platforms like DigitalOcean, AWS, or your own servers, ensure that you’ve set up SSL/TLS certificates for HTTPS (using services like Let’s Encrypt for free SSL certificates).
Step 8: Maintain and Improve Your PWA
After deployment, you’ll want to continuously improve your PWA based on user feedback and performance metrics. Here are some strategies for maintaining a top-tier PWA:
Monitor Performance
Use Google Analytics or other tracking tools to monitor user engagement, load times, and offline usage. Analyzing this data helps in identifying areas for improvement.
Update Content and Push Notifications
Push notifications are an excellent way to re-engage users. Regularly update content on your app, and send notifications when there’s something new or valuable to share.
Timely and relevant notifications: Make sure your notifications are meaningful and don’t overwhelm users.
Segment your audience: You can send personalized push notifications based on user behavior and preferences, improving relevance.
Keep Dependencies Updated
Rails 8 and Hotwire offer a smooth development experience, but you should still periodically check for updates to your dependencies (especially security patches). Use tools like bundler-audit
to scan for vulnerabilities in your gems.
Conclusion
Building a PWA with Ruby on Rails 8 provides a fast, modern, and robust solution for delivering a mobile app-like experience on the web.
With Hotwire’s Turbo and Stimulus, combined with simple and effective service worker integration, you can create a seamless experience that works across devices and platforms.
By following this guide, you’ve learned how to implement key features of a PWA, including offline support, push notifications, and app installability, all while maintaining the simplicity and productivity Rails offers.
Whether you’re building a simple web app or a complex user-facing product, PWAs are a great way to enhance engagement, retention, and performance without relying on large external JavaScript frameworks.
Good luck, and happy coding!
Subscribe to my newsletter
Read articles from Chetan Mittal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Chetan Mittal
Chetan Mittal
I stumbled upon Ruby on Rails beta version in 2005 and has been using it since then. I have also trained multiple Rails developers all over the globe. Currently, providing consulting and advising companies on how to upgrade, secure, optimize, monitor, modernize, and scale their Rails apps.