Ruby & Rails

Ruby on Rails 7: HTML Over the Wire

Hotwire, import maps and a default stack with no Node build step. Rails 7 made a real argument that most applications never needed a SPA - and it mostly holds up.

HTML fragments arriving over a rail and slotting into a page, with bundler machinery discarded

The short version

  • Rails 7 made an argument, not just a release: most applications never needed a single-page app, and the JavaScript build pipeline was the tax nobody priced.
  • Hotwire — Turbo plus Stimulus — delivers app-like interactivity by sending HTML rather than JSON.
  • Import maps made the default new app work with no Node, no bundler and no node_modules.
  • The argument holds for most CRUD-shaped products. It stops holding for genuinely stateful, offline-capable or highly interactive interfaces, and Rails is honest about that.

Rails 7 shipped on 15 December 2021 and was less a feature release than a position statement. The industry had settled into a default where every web application was a JSON API with a React client in front of it, and Rails 7 said, in effect, that this default was wrong for most of the applications being built with it.

A month in, that position deserves a fair assessment rather than a changelog. This is what changed, what it bought, and where the argument runs out.

Hotwire: sending HTML instead of JSON

The core idea is old and the execution is what makes it interesting. Instead of the server sending JSON that client-side JavaScript turns into HTML, the server sends HTML and the client swaps it into place.

The consequence is that your rendering logic exists once, in one language, on the server. There is no parallel set of view components in JavaScript that must be kept consistent with the server’s understanding of the same data.

Turbo Drive

Intercepts link clicks and form submissions, fetches the new page in the background, and replaces the <body> without a full reload. The CSS and JavaScript never re-parse, so navigation feels immediate. It is on by default and requires no code at all.

Turbo Frames

Marks a region of the page as independently replaceable. A link inside a frame updates only that frame. This makes inline editing, tabbed panels and paginated lists into ordinary server-rendered actions rather than client-side state machines.

Turbo Streams

Lets the server send fragment updates that append, prepend, replace or remove specific elements — either in response to an action, or pushed over a WebSocket. Combined with a model callback, a record update can broadcast a re-rendered partial to every connected client. Live updates without writing any client-side subscription code.

Stimulus

For the behaviour Turbo cannot express, Stimulus is a deliberately small controller framework. It does not own rendering or state; it attaches behaviour to markup the server already produced. The constraint is the point — Stimulus controllers stay small because there is nothing else for them to do.

The productivity claim is real, and narrower than the marketing. For a form-and-list application, Hotwire genuinely removes an entire codebase. For an interface with meaningful client-side state — a canvas editor, a complex filter that must not round-trip — you will end up writing that state management in Stimulus, and you will wish you had a component framework.

Import maps: no build step

The second half of the argument. Import maps let the browser resolve bare module specifiers natively, so you can write import { Controller } from "@hotwired/stimulus" and have the browser fetch it directly.

What that removes from a default new application: no Webpack or esbuild configuration, no node_modules, no build step in development, no build step in CI, no bundle to invalidate, and no dependency on the JavaScript toolchain’s upgrade cadence. Adding a library is one command that edits a config file.

The trade-offs are genuine. You ship unbundled modules, which HTTP/2 makes acceptable but not free. You cannot use anything requiring compilation — no JSX, no TypeScript. And tree-shaking does not happen, so a large library ships whole.

Rails is pragmatic about this: jsbundling-rails and cssbundling-rails give you esbuild, Rollup, Vite or whatever you prefer when you need it. Import maps are the default, not a mandate.

The rest of the release

  • Encrypted attributes. Declare encrypts :ssn on a model and the column is encrypted at rest with key management handled, while remaining queryable for exact matches. Previously this meant a gem and a meaningful amount of care.
  • Async queries. load_async lets independent queries in one action run concurrently rather than sequentially, which is a straightforward win on any dashboard-shaped page.
  • Zeitwerk only. The classic autoloader was removed. Predictable constant resolution, at the cost of a real migration for older applications.
  • At-work errors in development. Better error pages and a genuinely improved debugging experience.

Where the argument holds

Honestly, in most of the applications we see. Internal tools, admin interfaces, marketplaces, content-driven products, standard B2B SaaS — these are overwhelmingly forms, lists and detail pages. For that shape of product, Hotwire delivers an experience users cannot distinguish from a SPA, built by a smaller team, with one deployment artifact and one set of tests.

The advantages compound in ways that are easy to overlook:

  • One language, one mental model, one place where the truth about a page lives.
  • No API contract to version between your own frontend and your own backend.
  • Server-rendered HTML, so SEO and initial paint work by default rather than by effort.
  • Progressive enhancement is the natural state, not something you have to engineer.

Where it runs out

Being fair to the criticism, there are real limits.

  • Latency-sensitive interaction. Every interaction that requires a server round-trip is bounded by network latency. A drag-and-drop reorder or a filter that must feel instantaneous wants local state.
  • Offline. Turbo assumes a server. Genuinely offline-capable applications need a different architecture.
  • A mobile app sharing the backend. If you need a JSON API for a native client anyway, the marginal cost of a JavaScript client that consumes it drops considerably.
  • Rich stateful interfaces. Editors, dashboards with interlinked controls, anything with substantial client-side state. Stimulus is deliberately not a state management framework, and pretending otherwise produces controllers nobody enjoys maintaining.
  • Hiring. There are simply more React developers than Hotwire developers, and that is a real constraint on a growing team even if it should not be a technical argument.
The question Rails 7 asks is a good one: is this application actually interactive, or does it merely have forms?

Upgrading to Rails 7

If you are still on an earlier version, the sequence that works:

  1. Get to Zeitwerk first, on your current Rails version. It is the largest source of upgrade pain and it can be done independently.
  2. Upgrade one minor version at a time, running rails app:update and reviewing each config change rather than accepting them all.
  3. Use load_defaults deliberately. Bump the framework defaults incrementally with new_framework_defaults rather than flipping everything at once.
  4. Leave the frontend alone initially. Rails 7 runs perfectly well with your existing Webpacker or esbuild setup. Adopting Hotwire is a separate decision on a separate timeline.

Boolean Solutions experience with Rails 7

We have built new applications on the Hotwire stack since it shipped a year ahead of this release, and we are moving existing ones onto Rails 7 while leaving their React frontends in place. Both are legitimate. The mistake we see is treating the frontend change as part of the upgrade — that turns a manageable framework upgrade into an open-ended rewrite, and it is the reason some of these projects stall.

If you are planning an upgrade or weighing Hotwire against a JavaScript frontend for a new build, talk to us.

Further reading

Written by

Udit Mittal

Founder at Boolean Solutions. Twenty years of building and rescuing web, mobile and AI products for SaaS companies and startups — and writing down what actually worked.

Get in touch