All Ruby on Rails templates

Rails + Hotwire (Stimulus + Turbo)

Server-rendered frontends with Turbo Frames, Streams, and Stimulus.

DevZone Tools1,480 copiesUpdated Mar 19, 2026Ruby on Rails
# CLAUDE.md — Rails + Hotwire (Stimulus + Turbo)

## Mental model

- Server renders HTML. **Turbo** intercepts navigation and form submits, swaps fragments without a full reload. **Stimulus** sprinkles behavior onto rendered HTML.
- Don't reach for React/Vue. The whole point of Hotwire is staying in Rails.
- Default to Turbo Drive for navigation, Turbo Frames for partial updates, Turbo Streams for live updates from the server.

## Turbo Frames

- Wrap a region in `<turbo-frame id="...">`. Links and forms inside the frame replace the frame's content with the matching frame in the response.
- Frame IDs are stable strings, ideally tied to a model: `dom_id(@invoice)`, `"new_user"`.
- Don't nest frames unless you really need scoped navigation. They get confusing fast.

## Turbo Streams

- Stream updates from the server push to multiple clients via Action Cable:
  ```ruby
  # in the model
  after_create_commit -> { broadcast_append_to "invoices", target: "invoices_list" }
  ```
- Stream actions: `append`, `prepend`, `replace`, `update`, `remove`, `before`, `after`. Pick the smallest one that does the job.
- Wire up the channel in the view: `<%= turbo_stream_from "invoices" %>`.

## Turbo Drive caveats

- Turbo caches pages aggressively. Flag pages that shouldn't cache: `<meta name="turbo-cache-control" content="no-cache">`.
- For full reloads (auth flows, marketing pages), opt out: `<a data-turbo="false">`.
- `turbo:load` event runs on every navigation. `turbo:before-cache` to clean up before a snapshot.

## Stimulus

- One controller per behavior. Files in `app/javascript/controllers/`.
- Controller classes match HTML data attributes:
  ```html
  <div data-controller="modal" data-modal-open-value="false">
    <button data-action="click->modal#toggle">Open</button>
    <div data-modal-target="content">...</div>
  </div>
  ```
- Use **values**, **targets**, **classes**, **outlets** — not arbitrary `dataset` reads.
- Don't write a controller for a single use. Inline JS or a Turbo response is often enough.

## Forms

- `form_with` uses Turbo by default. Server returns either a redirect (success), a re-rendered form (validation), or a Turbo Stream (in-place update).
- For multi-step interactions, use Turbo Frames with frame-specific responses — keeps the URL stable while the user works inside.

## Action Cable

- Streams piggyback on Action Cable. Configure Redis (or Solid Cable on Rails 8+) and you're done.
- Authenticate channels via `connection.identified_by :current_user` in `ApplicationCable::Connection`.
- Don't broadcast PII to public channels. Each user gets their own stream key when needed.

## Don't

- Don't ship a Turbo response that contains `<head>` tags. They'll be ignored, and you'll be confused why.
- Don't build a Turbo Stream by string-concatenating HTML. Use the `turbo_stream` helpers — they escape correctly.
- Don't use `data-turbo-method="delete"` on an `<a>` for destructive actions. Use `button_to` so it's a real form.
- Don't put business logic in Stimulus. Stimulus is for UI behavior; data and decisions live on the server.

Other Ruby on Rails templates