# Rails Hotwire Realtime

Apply Hotwire, Turbo, Stimulus, and ActionCable best practices for real-time Rails...

- Repository: https://github.com/marckohlbrugge/37signals-skills (730 stars)
- Category: Engineering
- License: see repository
- Last updated: 2026-06-09
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add marckohlbrugge/37signals-skills --skill rails-hotwire-realtime -g -a claude-code -y`
Codex: `npx skills add marckohlbrugge/37signals-skills --skill rails-hotwire-realtime -g -a codex -y`
Cursor: `npx skills add marckohlbrugge/37signals-skills --skill rails-hotwire-realtime -g -a cursor -y`
Gemini CLI: `npx skills add marckohlbrugge/37signals-skills --skill rails-hotwire-realtime -g -a gemini-cli -y`

## Description

Apply Hotwire, Turbo, Stimulus, and ActionCable best practices for real-time Rails interfaces. Use when building Turbo Streams/Frames, Stimulus interactions, or websocket-driven updates.

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# Rails Hotwire + Realtime

Use for Turbo/Stimulus/ActionCable architecture and reviews. Patterns from Campfire (cable-heavy chat) and Fizzy (streams-only kanban).

## Choose the Right Realtime Architecture

Two proven 37signals topologies:

- **Streams-only (Fizzy):** no custom ActionCable channels at all. `turbo_stream_from`, `broadcasts_refreshes` + morph, lazy frames with ETags. Default to this for CRUD-ish apps.
- **Cable-augmented (Campfire):** custom channels only for lightweight JSON signals (presence, typing, unread pings) where rendering HTML server-side would be wasteful. Durable DOM state still goes through Turbo Streams.

Rule of thumb: Turbo Streams for anything that changes the DOM; bare ActionCable only for tiny ephemeral signals.

## Turbo Defaults

- Prefer server-rendered partial updates over heavy client-side state systems.
- Set global morph refresh in the layout: `turbo_refreshes_with method: :morph, scroll: :preserve`.
- Use Turbo Streams for targeted updates; one action can render a multi-target stream template that updates every affected region atomically (source column + destination + detail pane).
- Lazy-load expensive sections with `turbo_frame_tag ..., src:, loading: :lazy` and give the frame endpoint its own `fresh_when` ETag. Extract frequently-changing fragments into their own frames so they don't bust the parent cache.
- Use `data-turbo-permanent` for elements that must survive navigation/morph (footer trays, in-progress editors).
- Block morph from clobbering client-owned state (e.g. localStorage-driven collapsed columns) via `turbo:before-morph-attribute` + `preventDefault()`.
- Exempt realtime-heavy pages from Turbo's page cache (`turbo_exempts_page_from_cache`); rely on frame-level ETags instead.
- Optimistic UI without a JS framework: server-render a `<template>` partial with `$placeholder$` tokens; client clones it with a generated ID before submit; the stream response replaces it.

## Broadcast Patterns

The rest: https://github.com/marckohlbrugge/37signals-skills/blob/HEAD/skills/rails-hotwire-realtime/SKILL.md
