An Opinionated Companion to Hotwire
Hotwire is a frontend toolkit favoring minimal JavaScript, server-side rendering, and HTML payloads. While it's deliberately open-ended about how you use it, this site prescribes a more constrained approach.
Pitfalls & Burns
What can bite you in a working Hotwire app, grouped by the part it comes from, with the refactoring that fixes each one.
Stimulus
Paying for Free Listeners
Manually wiring (and dutifully unwiring) event listeners in connect(), boilerplate that data-action descriptors replace entirely.
Tightly Coupled Controllers
One controller built for a single feature, with autosave, character counting, and autosizing all tangled together, versus small controllers composed in the markup.
So Much for Server-Side
A parse-and-validate engine the server must own anyway, rebuilt in JavaScript to power a preview. Two implementations, guaranteed to disagree.
Turbo Drive
What's The Disconnect?
Wiring up a third-party library in connect() without tearing it down. Navigate away and back, and watch the calendars pile up.
Cache-Back Guarantee
Temporary UI, like flash messages and armed confirmations, frozen into Turbo's cached snapshot and brought back by the Back button.
Prefetch Surprise
Turbo prefetches every link on hover by default, so slow pages and view counters get hit for pages nobody opens. Invert the default and opt in where intent is high.
Turbo Frames
Have You Seen This Content?
Three unrelated failures (a template that forgot its wrapper, a deleted record, an expired session) and Turbo answers all of them with the same two words, "Content missing."
Lazy N+1s
A frame on every row turns one page into a request (and a few queries) per item, defers nothing, and leaves nowhere to put includes. One combined render batches it away.
Out of Frame
Inline-edit sections that work perfectly inside their frames, while the completeness meter, checklist, and go-live banner, derived from the same fields outside every frame, go stale on every save.
Turbo Streams
For Want of an ID
Bare dom_id and hand-written ids work until a record renders twice or a string drifts, and then streams fail silently. dom_target with real specificity, used on both sides, keeps the contract.
Spray and Pray
Broadcasts wired into model callbacks go out on every save, to streams any page can subscribe to. An import becomes a storm, and one café's orders show up at another. Let the operation decide, and scope the streams.
Lack of Context
Broadcast partials render with no request around them. Personalization written against Current shows the actor's "You" in every window when broadcast from a request, and crashes outside one, when the answer comes from a background job.
Recommended Resources
Where to go deeper with Hotwire: the official handbooks, plus a running list of the books, courses, sites and blogs worth knowing about.