
April 29, 2026
Ruby has always been a server-first language. But what if you could take Ruby beyond MRI and run it directly in the browser, or even on the edge?
That’s exactly what Opal enables.
Opal is not just a transpiler. It’s a full Ruby execution environment implemented in JavaScript, capable of compiling Ruby code into JS and running it with a custom runtime that emulates Ruby’s object model, core classes, and method dispatch.
This opens the door to a very different way of thinking about Ruby applications.
How Opal Actually Works
At a glance, Opal looks simple:
opal --compile app.rb > app.js
But under the hood, it’s doing something much more ambitious.
Opal consists of three key layers:
- Compiler (lib/) → Parses Ruby and generates JavaScript
- Runtime (opal/) → Implements Ruby semantics in JS
- Stdlib (stdlib/) → Partial Ruby standard library
When you compile Ruby with Opal, you’re not just translating syntax you’re shipping a Ruby runtime embedded in JavaScript.
That’s why code like this:
puts "Hello world"
works in a browser. The puts call is resolved by Opal’s runtime, not native JavaScript.
The Mental Model Shift
If you approach Opal like “Ruby → JS transpilation,” you’ll hit a wall quickly.
The correct model is:
Ruby code runs inside a JavaScript-hosted Ruby VM.
That distinction matters, especially when dealing with:
- method dispatch (obj.$method)
- class system emulation
- lack of native Ruby threading
- async execution constraints
You’re not escaping JavaScript you’re embedding Ruby inside it.
Where Opal Shines
1. Ruby in the Browser
You can write client-side applications in Ruby:
<script type="text/ruby"> puts "Hello from Ruby in the browser"</script>
While not recommended for production in that form, it demonstrates the core capability: Ruby as a frontend language.
2. Shared Logic Between Frontend and Backend
Opal allows you to reuse:
- validations
- serializers
- business rules
across environments.
This is particularly useful when trying to maintain consistency between a Rails backend and a JS frontend.
3. Experimental Runtimes (Edge, Workers, etc.)
This is where things get interesting.
With the right abstractions, Opal can target:
- browser environments
- Node.js
- edge runtimes (e.g., workers)
This enables patterns like:
Writing request handlers in Ruby that execute in a JavaScript runtime.
The Hard Parts (You Should Know These)
Opal is powerful but it’s not a drop-in replacement for MRI.
No Native Rack
Frameworks like Sinatra depend on Rack, which assumes:
- blocking I/O
- request/response lifecycle
- server environment
None of these exist in a browser or edge runtime.
So running Sinatra on Opal requires:
- patching
- shimming
- or rethinking the entire request model
Async vs Sync Mismatch
Ruby code is typically written as synchronous:
get '/' do data = fetch_data data.to_jsonend
But JavaScript environments are async:
const data = await fetch(...)
Opal does not automatically reconcile this difference.
Any serious system built on Opal must define:
- how async flows are exposed to Ruby
- whether to simulate sync behavior or embrace async semantics
Stdlib and Gem Compatibility
Opal ships a partial stdlib.
Anything relying on:
- file system access
- native extensions
- threads
will not work.
This means:
- not all gems are compatible
- many need adaptation or replacement
Real-World Pattern: Sinatra + Opal
A growing pattern in advanced setups is:
- vendoring Sinatra
- patching it for Opal compatibility
- compiling it into JavaScript
- running it on a custom runtime
This effectively creates:
A Sinatra-like DSL executing inside a JavaScript environment.
However, this approach comes with trade-offs:
- tight coupling to Sinatra internals
- fragile upgrades
- need for a custom runtime (event loop, request handling, etc.)
In practice, most successful implementations evolve into:
A Sinatra-inspired DSL, not full compatibility.
So, Should You Use Opal?
Opal is not for everyone. But it’s extremely valuable in specific scenarios:
Use it if you want:
- Ruby on the frontend
- shared Ruby logic across environments
- experimental runtimes (edge, workers, client-side apps)
- full control over a Ruby-in-JS execution model
Avoid it if you need:
- full gem ecosystem compatibility
- traditional Rack-based applications
- predictable performance across all environments
Final Thoughts
Opal challenges one of Ruby’s core assumptions: that it belongs on the server.
By bringing Ruby into the JavaScript runtime, it enables a new class of architectures ones where the language stays the same, but the execution environment changes completely.
It’s not just about running Ruby in the browser.
It’s about redefining where Ruby can live.
