DEV Community

Alex Aslam
Alex Aslam

Posted on

Server-Sent Events vs. WebSockets: When to Ditch Hotwire

"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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

  1. 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;
   });
Enter fullscreen mode Exit fullscreen mode
  1. Keep Stimulus for interactions (it still works!)

From Hotwire to WebSockets

  1. Use AnyCable + @anycable/web for better scaling:
   import { createConsumer } from "@anycable/web";
   const consumer = createConsumer();

   consumer.subscriptions.create("UpdatesChannel", {
     received(data) { /* update DOM */ }
   });
Enter fullscreen mode Exit fullscreen mode

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:

  1. Start with one non-critical feature (e.g., notifications)
  2. Compare costs (SSE often wins for read-heavy apps)
  3. Keep Hotwire everywhere else

Have you ditched Hotwire? Share your battle scars below.

Top comments (0)