intertwingly

It’s just data

Rails Apps on V8 Isolates


Running a Ruby on Rails application on a V8 Isolate sounds absurd. V8 runs JavaScript, not Ruby. Rails expects a long-running server, not ephemeral functions. The whole idea seems backwards.

And yet.

The JavaScript ecosystem is delightfully weird. Nobody writes JavaScript anymore—they write TypeScript, JSX, Svelte, or a dozen other languages that transpile to JavaScript. Bundlers reshape applications into entrypoints. React Server Components use "use server" directives that do nothing except signal the bundler to transform the code.

What if we could do the same with Ruby? Not run Ruby itself, but transpile Ruby applications into JavaScript and reshape them into functions?

That's the vision: full-stack Rails applications running on Vercel Edge, Cloudflare Workers, or Deno Deploy.

Why V8 Isolates?

First, some context on where applications run.

Traditional hosting means a server that's always on—a VPS, a container, a Kamal deployment. You pay for capacity whether you're using it or not. This makes sense if you're large and your traffic is predictable.

But if you're small or your traffic is spiky, you're either overprovisioned (paying for idle capacity) or underprovisioned (slow when it matters). V8 Isolates offer a different model.

V8 Isolates power Cloudflare Workers, Vercel Edge, and Deno Deploy. They're lightweight JavaScript execution environments that spin up on demand:

For a Rails developer, this means: deploy your app once, have it run globally, scale up when needed, and pay almost nothing when it's idle. No capacity planning. No configuring load balancers on Hetzner or Digital Ocean. No worrying about which region to deploy to.

The question is: can we get there?

Why Rails?

Rails is surprisingly well-suited for this transformation.

The core is MVC, but what makes Rails special is how declaratively it's expressed. Routes, associations, validations, callbacks—these aren't imperative code, they're declarations. Convention over configuration means the architecture is implicit in the file structure. Then you add small bits of imperative code: a validation here, a loop there, a controller action.

class Article < ApplicationRecord
  has_many :comments, dependent: :destroy
  validates :title, presence: true
end

This isn't a program to execute—it's a specification to transform.

Beyond the core, Rails has abstract interfaces: Active Job, Action Mailer, Active Storage, Action Cable. Each represents a proven pattern with cloud-hosted alternatives—SendGrid for email, S3 for storage, Pusher for real-time. These abstractions define what your application needs, not how it's implemented.

Declarative structure. Abstract interfaces. Small scripts. All things that an "npm run build" process could use as inputs.

Demo Time

My previous post showed the same Rails blog running on five platforms. But CRUD is the easy part. Real applications need real-time updates—and that's where things get interesting.

Introducing the Juntos Chat Demo. A simple chat application—Stimulus controller, Turbo Streams, WebSocket—that runs unchanged in Rails with Action Cable, in the browser with BroadcastChannel, on Node.js with WebSockets, and on Cloudflare with Durable Objects.

Here's the chat's Stimulus controller, written in Ruby:

class ChatController < Stimulus::Controller
  # Auto-scroll to show the new message
  def messageTargetConnected(element)
    element.scrollIntoView()
  end

  # Clear the message input after form submission
  def clearInput
    bodyTarget.value = ""
    bodyTarget.focus()
  end
end

Familiar Ruby syntax. Direct and seamless access to JavaScript objects. The documentation includes live demos where you can edit Ruby code and see it transpile in real time.

Is This Just Demoware?

Honestly, yes for some parts, no for others.

The Ruby2JS base is solid. Ruby and JavaScript compatibility is a spectrum—Opal represents one end with full method_missing support, while Ruby2JS lets you choose where you want to be, with intelligent defaults that work for most applications.

The Rails filters are new and have gaps. Routes and ERB work well. The model filter is minimal—just enough to support the demos—but Rails' Arel provides a design to follow. Controllers are somewhere in the middle.

No showstoppers. Just work to be done.

What's Next

Edge deployment needs caching. A DSL for revalidation times and cache tags—no-ops in traditional Rails, but generating proper headers for edge targets.

Next, Vite and Phlex look like quick wins.

A Vite plugin would bring modern developer experience to Ruby: hot module replacement, source maps showing Ruby in DevTools, production builds with tree shaking. Edit a Stimulus controller, see the change instantly.

Phlex—pure Ruby view components—opens the door to component-based architecture. Since Ruby2JS can already target React, and ERB is essentially EJS with Ruby, perhaps multiple component formats can be first-class citizens.

And since we're transforming code for both client and server, why not use :client and use :server directives? Ruby's answer to React Server Components.

Hybrid Use Cases

While taking Rails to places it can't currently reach is Juntos' raison d'être, the hybrid use cases are interesting on their own.

Here's a practical one: you want to deploy to Cloudflare Workers with D1, but you don't want to run Wrangler and hit remote databases during development. The solution is the same config/database.yml pattern Rails developers already know—SQLite locally, D1 in production. Same SQL dialect, instant feedback, no cloud round-trips while you're iterating.

Deploy Target Prod Database Dev Adapter Dev Target Notes
Cloudflare Workers d1 sqlite node Same SQL dialect
Vercel Edge neon pglite node No server needed
Vercel Edge turso sqlite node Same SQL dialect
Deno Deploy neon pg deno Requires local PostgreSQL

The browser target enables something different: offline-first applications. I'm building an offline scoring app for my dance showcase using the same models and views as the main Rails app. When hotel wifi inevitably fails mid-event, scoring continues. The browser app syncs when connectivity returns, treating the Rails server as an API.

The Vision, Revisited

The goal isn't to replace Rails. It's to give Rails applications more places to run—especially places Rails can't reach on its own.

Write a standard Rails app. Run it on your laptop with SQLite. Deploy it to Fly.io or Render with Postgres. Or transpile it and deploy to Cloudflare Workers with D1—global distribution, scales on demand. Or run it entirely in the browser with IndexedDB—offline-first, zero infrastructure, data stays on the user's device.

V8 Isolates and browsers are the sweet spots: platforms where transpilation isn't just an option, it's the only way to get there. One codebase. Many runtimes. That's the vision.


Ruby2JS is open source: github.com/ruby2js/ruby2js