Rails 7 Hotwire: A Lightweight Alternative to Heavy JavaScript Frameworks
Rails 7’s Hotwire lets you build real‑time, interactive apps with minimal JavaScript. This guide walks through setting up Turbo Streams and Stimulus, shows a live‑update task list, and weighs Hotwire’s trade‑offs versus heavy front‑end frameworks.
24 Jul 2025, 04:36 UTC

Problem: The JavaScript Overhead in Rails Apps
Traditional Rails applications often rely on large front‑end bundles—React, Vue, or Angular—to bring interactivity. Those bundles increase page load time, complicate asset pipelines, and force teams to maintain two separate codebases. For many CRUD‑centric or collaborative apps, the heavy tooling is unnecessary.
Thesis: Hotwire Keeps the Client Light
Rails 7 ships with Hotwire (Turbo + Stimulus), a set of features that let you write rich, real‑time interfaces while keeping the client‑side footprint minimal. Turbo handles navigation and partial updates, while Stimulus gives you a small, declarative JavaScript layer for component logic.
Hotwire in Action
1. Set Up a Minimal Hotwire App
- Create a fresh Rails 7 project without the default JavaScript stack:
rails new demo --skip-javascript cd demo - Add the Hotwire gem and install its components:
bundle add hotwire-rails bundle install rails turbo:install rails stimulus:install - Verify that
app/javascript/controllerscontains ahello_controller.jsand thatapp/assets/stylesheets/application.cssincludes the Hotwire styles.
2. Build a Real‑Time Task List with Turbo Streams
Generate a simple model and scaffold a controller that responds to Turbo Stream format.
rails g model Task name:string completed:boolean
rails g controller Tasks
In app/controllers/tasks_controller.rb:
class TasksController < ApplicationController
def index
@tasks = Task.all
end
def create
@task = Task.new(task_params)
if @task.save
respond_to do |format|
format.turbo_stream
format.html { redirect_to tasks_path }
end
else
render :index
end
end
private
def task_params
params.require(:task).permit(:name, :completed)
end
end
In app/views/tasks/index.html.erb:
<h1>Tasks</h1>
<%= form_with model: Task.new, data: { turbo: "true" } do |f| %
<%= f.text_field :name, placeholder: "New task" %>
<%= f.submit "Add" %
</%= end %>
<div id="tasks" data-controller="tasks">
<%= render @tasks %>
</div>
Create a partial for a single task in app/views/tasks/_task.html.erb:
<turbo-stream target="tasks" action="append">
<template>
<div>
<%= task.name %>
<%= check_box_tag "completed_#{task.id}", "1", task.completed, data: { action: "change->tasks#toggle" } %>
</div>
</template>
</turbo-stream>
Finally, add a Stimulus controller in app/javascript/controllers/tasks_controller.js to handle the checkbox toggle via an AJAX request:
import { Controller } from "stimulus"
export default class extends Controller {
toggle(event) {
const checkbox = event.target
fetch(`/tasks/${checkbox.dataset.id}`, {
method: "PATCH",
headers: { "X-CSRF-Token": document.querySelector("meta[name='csrf-token']").content, "Content-Type": "application/json" },
body: JSON.stringify({ completed: checkbox.checked })
})
}
}
With this setup, creating a task or toggling its completion updates the list in real time without a full page reload. No large JavaScript bundle is loaded—only the tiny Stimulus controller.
3. Verify the Lightweight Footprint
Open the browser’s Network panel. You should see:
- A single
application.jsbundle (often under 10 KB) containing the Hotwire runtime and your Stimulus controllers. - No React, Vue, or Angular bundles.
- Turbo Drive automatically intercepts link clicks and form submissions, providing SPA‑like navigation.
Trade‑offs and Limitations
- Server Load: Turbo Streams render partials on the server for each update. In high‑traffic scenarios, consider caching or throttling updates.
- Complex Client‑Side State: For animations, drag‑and‑drop, or highly interactive UIs, Hotwire may be insufficient; a dedicated front‑end framework could still be needed.
- Legacy JS Conflicts: Libraries that manipulate the DOM directly can clash with Turbo’s automatic page replacement. Isolate or refactor such code.
- Browser Compatibility: While Hotwire works in modern browsers out of the box, older browsers may need polyfills for WebSockets or the Fetch API.
Actionable Next Steps
- Prototype a core feature (e.g., a comment system) using Turbo Streams to gauge server load and UI responsiveness.
- Measure bundle size and page load times with tools like Chrome DevTools or Lighthouse before and after adding Hotwire.
- For teams with existing JavaScript libraries, create a sandbox to test Turbo Drive’s interaction with those libraries.
- If Hotwire meets your needs, commit to a migration plan: replace heavy front‑end bundles, update asset configurations, and train developers on Stimulus conventions.
Adopting Hotwire in Rails 7 can drastically reduce client‑side complexity while still delivering real‑time, responsive interfaces. Evaluate your app’s requirements against the trade‑offs above to decide if Hotwire is the right fit.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.