Ruby on Rails on WebAssembly: A Full-Stack, In-Browser Journey

March 11, 2026

What if you could run a fully functional Rails application — backend, database, file storage, and all — directly inside a browser tab, with zero servers?

WebAssembly (Wasm) makes this possible. By compiling Ruby and Rails into a Wasm module, the entire application stack executes client-side. This post explores a practical journey: from scaffolding your Rails app to shipping it as a Progressive Web App (PWA) that runs entirely offline.


This article is an original write-up, providing a practical, technical deep-dive into running Ruby on Rails in the browser via WebAssembly. It was inspired by the following works:

  • “Ruby on Rails on WebAssembly”web.dev, 2025. Explains how to compile Rails apps into Wasm, run databases in-browser, and leverage PWA for offline full-stack apps. Read more
  • “Developing Ruby in the Browser” – RubyKaigi 2023 Presentation by Yuma Sawai. Introduces ruby.wasm, demonstrates browser execution of Ruby, and showcases frameworks bridging browser APIs with Ruby logic. View presentation

💡 The concepts and technical examples in this article are originally written, synthesizing these sources into a cohesive guide and practical walkthrough for developers.


1️⃣ Start with a Standard Rails App

Begin with a conventional scaffold — for example, a simple blogging platform:

$ rails new --css=tailwind blog_wasm
$ cd blog_wasm
$ bin/rails generate scaffold Post title:string date:date body:text
$ bin/rails db:migrate && bin/dev

At this stage, the app runs locally on Puma backed by SQLite. The goal? Move this entire stack into the browser.


2️⃣ Compile Ruby to WebAssembly

Ruby 3.2+ supports Wasm. The wasmify-rails gem bundles the Ruby VM, gems, native extensions, and standard library into a single .wasm module:

$ bundle add wasmify-rails
$ bin/rails wasmify:install
$ bin/rails wasmify:build

The result: a self-contained binary (~70 MB) ready to run in any modern browser. Native extensions like nokogiri or bcrypt are compiled automatically.


3️⃣ Service Workers as Your Web Server

In-browser, a Service Worker replaces Puma:

let vm;
const initVM = async () => {
if (!vm) vm = await initRailsVM('/app.wasm');
return vm;
};
self.addEventListener('fetch', (event) => {
event.respondWith(
initVM().then((vm) => {
const rackHandler = new RackHandler(vm);
return rackHandler.handle(event.request);
})
);
});

All requests are intercepted, translated to Rack format, and handled by Rails in Wasm — no changes to your app logic required.


4️⃣ In-Browser Databases

Active Record remains unchanged. SQLite compiled to Wasm (or PGlite for Postgres) serves as your persistence layer:

export function registerSQLiteInterface(worker, db) {
worker.sqlite = {
exec: (sql) => db.exec(sql, { returnValue: 'resultRows' }),
changes: () => db.changes(),
};
}

Database configuration:

# config/database.yml
wasm:
adapter: sqlite3_wasm
js_interface: 'sqlite'

Rails queries run normally — the adapter routes them to the in-browser SQLite engine.


5️⃣ File Storage with OPFS

Active Storage uploads are handled via the Origin Private File System (OPFS):

async function saveFile(name, data) {
const dir = await navigator.storage.getDirectory();
const fh = await dir.getFileHandle(name, { create: true });
const writable = await fh.createWritable();
await writable.write(data);
await writable.close();
}

A custom adapter wraps these calls. All file uploads and retrievals happen client-side.


6️⃣ Launch as a PWA

Tie it all together:

$ bin/rails wasmify:pwa
$ bin/rails wasmify:pack
$ cd pwa && yarn dev

Open http://localhost:5173/ — your full-stack Rails app runs entirely in the browser. MVC, Active Record, Active Storage, all offline-ready.


7️⃣ Real-World Use Cases

  • Offline-First Apps: Journals, note-taking, or dashboards that work seamlessly offline.
  • Rapid Prototyping & Demos: Share fully interactive Rails apps instantly via URL.
  • Educational Playgrounds: Students experiment with Rails without installing Ruby or databases.
  • Personal Data-Heavy Apps: Local dashboards combining analytics, file uploads, and complex logic.
  • Media Management: Photo libraries stored in SQLite Wasm and OPFS, fully offline.

8️⃣ Performance Considerations

  • Memory: Each VM ~300 MB.
  • Compilation: Native extensions must be Wasm-compiled.
  • Execution Speed: Slower than native for CPU-heavy tasks.
  • Data Sync: Remote replication requires careful design.
  • Initial Load: 70 MB Wasm bundle — PWA caching recommended.

Rails abstractions remain intact — same models, controllers, views.


9️⃣ Advanced Patterns

  • In-Browser Background Jobs: Active Job via in-VM queues.
  • Offline Sync: Queue offline mutations and sync later via Service Workers.
  • Gem Testing & Experimentation: Load gems, run unit tests, experiment entirely in-browser.

Conclusion

WebAssembly transforms Rails into a portable, self-contained runtime. Backend logic, database queries, background jobs, and file management happen locally — opening new possibilities for offline-first apps, instant demos, and in-browser learning environments.

Rails on Wasm blurs the line between client and server. The tooling is maturing, constraints exist, but the developer experience remains familiar, and the potential is limitless.

Article content

Leave a comment