"Hotwire’s Turbo Streams are great—until your Redis bill hits $5K/month."
Hotwire (Turbo + Stimulus) makes real-time updates feel effortless. But when your app scales, you’ll hit WebSocket bottlenecks, DOM explosions, and infrastructure headaches that no amount of Rails magic can fix.
Here’s when to switch to Server-Sent Events (SSE) or raw WebSockets—and how to do it without rewriting your frontend.
1. The Hotwire Scaling Wall
When Turbo Streams Break Down
✅ Good for:
- Small apps (≤1K concurrent users)
- Simple updates (e.g., "New message arrived!")
❌ Fails at:
- High-frequency updates (e.g., live sports scores)
- Long-lived connections (memory leaks)
- Complex state synchronization
Symptoms You’ve Hit the Wall:
- Redis CPU usage > 70%
- Clients disconnect randomly
- Action Cable struggles beyond 10K connections
2. Server-Sent Events (SSE): The Simpler Alternative
How SSE Works
- One-way (server → client)
- HTTP-based (no protocol switching)
- Auto-reconnects
Ideal For:
- Notifications
- Live dashboards (e.g., analytics)
- Progress bars
Rails Implementation:
# Controller
def updates
response.headers["Content-Type"] = "text/event-stream"
stream = SSE.new(response.stream)
# Send data
StockTicker.on_change do |price|
stream.write({ price: price }, event: "update")
end
ensure
stream.close
end
Pros:
- 10x cheaper than WebSockets (no persistent connections)
- Works behind load balancers
- Built into browsers
Cons:
- No client→server communication
- Limited to ~6 concurrent connections per domain (HTTP/2 helps)
3. Raw WebSockets: When You Need Full Duplex
When to Use Them
- Chat apps
- Collaborative editors (e.g., Figma clones)
- Games
Rails Setup (Without Action Cable):
# Using AnyCable for scale
Rails.application.configure do
config.action_cable.url = "wss://anycable.example.com"
end
Pros:
- Bi-directional
- Lower latency than SSE
Cons:
- Complex to scale (needs sticky sessions)
- Higher infrastructure cost
4. The Decision Framework
Need | Hotwire | SSE | WebSockets |
---|---|---|---|
Simple real-time updates | ✅ | ✅ | ⚠️ (Overkill) |
High-frequency data | ❌ | ❌ | ✅ |
Client→server comms | ✅ | ❌ | ✅ |
Scale > 10K users | ❌ | ✅ | ⚠️ (Hard) |
Case Study:
We switched a live auction dashboard from Hotwire to SSE and:
- Reduced server costs by 60%
- Eliminated Redis dependency
- Kept the same frontend code (
EventSource
instead of Turbo Streams)
5. Migration Tips
From Turbo Streams to SSE
- Replace
turbo_stream_from
with:
const source = new EventSource("/updates");
source.addEventListener("update", (e) => {
const data = JSON.parse(e.data);
document.getElementById("price").innerText = data.price;
});
- Keep Stimulus for interactions (it still works!)
From Hotwire to WebSockets
- Use
AnyCable
+@anycable/web
for better scaling:
import { createConsumer } from "@anycable/web";
const consumer = createConsumer();
consumer.subscriptions.create("UpdatesChannel", {
received(data) { /* update DOM */ }
});
6. When to Stick with Hotwire
- Admin dashboards (low traffic)
- Prototypes (speed over scale)
- Apps with Rails-heavy templates
"But Rewriting Sounds Painful!"
It doesn’t have to be:
- Start with one non-critical feature (e.g., notifications)
- Compare costs (SSE often wins for read-heavy apps)
- Keep Hotwire everywhere else
Have you ditched Hotwire? Share your battle scars below.
Top comments (0)