← All insights
Engineering · March 18, 2024 · 9 min read

From Stimulus to Another Framework in a Ruby on Rails App

Stimulus is a good choice for most Rails apps. It's lightweight, it integrates naturally with the Rails view layer, and it keeps the frontend complexity low when the product is still finding its shape.

At some point, some products outgrow it. The signs: you're building increasingly complex client-side state management in Stimulus controllers, you're fighting with controller lifecycle to manage cross-controller communication, or you've got a section of the app that genuinely needs a reactive component model. At that point, the question is how to move without a big-bang rewrite.

The spectrum of options

The options range from "keep Stimulus" to "replace everything" with meaningful stops in between:

  • Turbo + Hotwire — if you haven't adopted this yet, it handles a lot of what makes Stimulus painful for complex pages. Real-time page updates, frame-based partial rendering. Often the right answer before jumping to a heavier framework.
  • React/Vue in specific pages — mount a React or Vue component in specific parts of the app where the complexity warrants it, while keeping Stimulus everywhere else. This is the most pragmatic migration path.
  • Full frontend framework with API — decouple the frontend entirely from Rails views. Rails becomes an API. Highest complexity, highest payoff if you're building a genuinely app-like experience.

The hybrid approach in practice

For most Rails apps, the hybrid approach (React in specific pages) is the right first step. It avoids the risks of a full migration while letting you build new complex pages in a framework suited to the complexity.

The Rails side: use react-rails or shakapacker to bundle React components and mount them from views:

# Gemfile
gem 'react-rails'

# view (erb)
<%= react_component("MyComplexWidget", props: { user_id: @user.id }) %>

The React side gets data via a JSON API endpoint. The Rails view layer handles layout, navigation, and the simpler pages. Complex interactive sections are React components.

Managing the boundary

The hardest part of a hybrid migration is the boundary between the Stimulus world and the React world. You need to decide:

  • State that lives in Rails — forms that submit to Rails controllers, server-rendered pages, session state. Keep these in the Rails layer.
  • State that lives in React — complex client-side interactions, real-time updates, multi-step flows. Move these to React.
  • Shared state — the hardest category. If a Stimulus controller and a React component need to share state, you're either duplicating it or introducing a communication channel. Consider whether this is a signal that more of the page should be React.

The migration sequence

The sequence that works in practice:

  • Identify the pages or features that are genuinely hitting Stimulus's limits
  • Define the API contract for those features (what data does the component need?)
  • Build the React component alongside the existing Stimulus implementation
  • Ship behind a feature flag — run both implementations simultaneously
  • Validate the React implementation, then remove the Stimulus code

The feature flag step is often skipped. Don't skip it. Running both implementations for a sprint means you can roll back instantly if something breaks in production.

When to reconsider the migration

Before starting, ask: is the problem actually Stimulus, or is it the underlying data model and architecture? Stimulus controllers that manage complex state often do so because the backend API doesn't give the frontend what it needs in the right shape.

A React migration on top of a poorly-designed API is an expensive way to get a more complex version of the same problem. If the backend needs work, do that first.

The pragmatic call

If you're on Stimulus today and it's working fine, there's no reason to migrate. The productivity cost of maintaining two frontend paradigms in one app is real. Migrate when you're genuinely hitting the limits — not because React is newer.

When you do migrate, do it incrementally. Pick the one page where Stimulus is clearly the wrong tool, build it in React, ship it, then decide whether the next page warrants the same treatment. Don't plan a full migration on a timeline; let the product guide when each page moves.

Still reading? Good. Book a 30-minute call.

No sales pitch. We'll ask what's on fire and tell you if we can help. If we can't, we'll name three firms who can.

Book a call →