DEV Community

Alex Aslam
Alex Aslam

Posted on

Hotwire’s Dark Side: When Real-Time Isn’t Worth It

"We added Hotwire to our Rails app—and watched our server bill double."

Hotwire (Turbo + Stimulus) promises snappy real-time updates without JavaScript frameworks. But after a year in production, we learned the hard way:

Real-time isn’t free.

Here’s when Hotwire becomes a liability—and how to avoid our mistakes.


1. The Performance Pitfalls

Problem 1: DOM Explosion

What happens:

  • Every Turbo Stream update keeps old DOM nodes in memory
  • Long-lived pages (e.g., dashboards) bloat to 10,000+ elements

Symptoms:

  • 5-second lag when clicking buttons
  • Chrome crashes on low-end devices

Fix:

<%# Manually prune detached elements %>
<%= turbo_stream.update "notifications" do %>
  <%= render @latest_notifications %>
<% end %>

<script>
  // Clean up old notifications
  document.querySelectorAll(".notification:not([data-turbo-permanent])")
    .forEach(el => el.remove());
</script>
Enter fullscreen mode Exit fullscreen mode

Problem 2: WebSocket Stampede

What happens:

  • 10,000 users online → 10,000 persistent WebSocket connections
  • Redis + Action Cable buckles under load

Symptoms:

  • Erratic disconnects
  • 30% CPU just for connection overhead

Fix:

# config/cable.yml
production:
  adapter: nats # Replace Redis for scale
  url: nats://nats.example.com:4222
Enter fullscreen mode Exit fullscreen mode

2. The Complexity Traps

Trap 1: Stateful Frontends

Scenario:

  • A Turbo Frame contains a multi-step form
  • User navigates away → state vanishes

Workaround:

// Persist state manually
document.addEventListener("turbo:before-cache", () => {
  localStorage.setItem("form-state", JSON.stringify(myFormState));
});
Enter fullscreen mode Exit fullscreen mode

Trap 2: Silent Failures

Scenario:

  • A Turbo Stream update fails (e.g., 500 error)
  • No UI feedback — the page just… doesn’t change

Fix:

// Show errors on failure
addEventListener("turbo:before-fetch-response", (event) => {
  if (!event.detail.fetchResponse.succeeded) {
    alert("Update failed. Please refresh.");
  }
});
Enter fullscreen mode Exit fullscreen mode

3. When to Avoid Hotwire

🚫 High-interaction apps (e.g., Figma-like tools)
🚫 Public-facing high-traffic pages (WebSocket costs add up)
🚫 Apps needing offline support (Turbo caches poorly)

Golden Rule:

Use Hotwire for enhancements, not as a total SPA replacement.


4. The Hybrid Approach

What We Do Now

Feature Technology Why
Notifications Turbo Streams Low-frequency, high-value
Dashboard charts React + JSON API Complex state management
Admin CRUD Turbo Frames Simple, fast iterations

Migration Tip

<%# Gradually replace Turbo with Stimulus controllers %>
<div data-controller="async-content"
     data-async-content-url="/api/comments">
  <!-- Loaded via fetch() -->
</div>
Enter fullscreen mode Exit fullscreen mode

"But Basecamp Uses Hotwire at Scale!"

They do—with a dedicated infrastructure team. Our lessons:

  1. Start small (one Turbo Stream, not the whole app)
  2. Monitor WebSocket memory like a hawk
  3. Know when to eject (SPAs still win for complex UIs)

Hit Hotwire limits? Share your story below.

Top comments (4)

Collapse
 
dotallio profile image
Dotallio

This hits home - I ran into the same DOM bloat and socket pain trying to scale real-time features. Curious, did you ever try alternatives before landing on the hybrid model?

Collapse
 
alex_aslam profile image
Alex Aslam

Glad it resonated—sounds like you’ve felt the pain too! Before settling on the hybrid approach, we tested:

  1. Server-Sent Events (SSE): Cleaner than WebSockets for one-way updates, but lacked Turbo’s DOM magic.
  2. Stimulus + Polling: Simple but ate bandwidth with constant requests.
  3. React Islands: Embedding React just for complex parts (worked but fragmented our stack).

The hybrid model ended up being the ‘least bad’ compromise. What alternatives did you try? Always hunting for better solutions!

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

This is extremely impressive. This kinda real-world breakdown is exactly what I always look for when I run into tech that ends up biting me down the road

Collapse
 
alex_aslam profile image
Alex Aslam

Glad it resonated! Real-world scars make the best lessons—what’s the one tech that’s bitten you hardest? (For us, it was Action Cable in production 😅)