Every client reconnects at once. That is what a deploy means now on one project of mine, since Reverb made WebSockets a first-party Laravel feature this spring and order updates go to the browser instead of polling. The code was the easy part.

A classic PHP app is stateless between requests. An FPM worker takes a request, answers, forgets. Capacity planning is requests per second. A WebSocket server is the opposite animal: thousands of open connections that mostly do nothing, but each one holds memory and a file descriptor, and each one is state that dies with the process. Your quiet realtime feature has a thundering herd built in. Plan for reconnect storms, raise descriptor limits, and make the client reconnect with jitter, never on a fixed timer.

Auth moves too. An HTTP request authenticates itself every time. A channel subscription authenticates once, at subscribe. So private channels need a real authorization callback, and you must decide what happens to an open connection when access is revoked. Most teams decide nothing, by not thinking about it. We were most teams for the first month.

Scaling is fine once you accept the pattern: several Reverb nodes, Redis pub/sub between them, so an event lands on whichever node holds the subscriber.

And one bug worth its own paragraph. Broadcasting from inside a database transaction:

DB::transaction(function () use ($order) {
    $order->markPaid();
    OrderPaid::dispatch($order); // too early
});

The browser receives the event, requests fresh data, and hits a replica where the transaction is not committed yet. Or the transaction rolls back and the clients celebrated a payment that never happened. Dispatch after commit, always. Laravel has ShouldDispatchAfterCommit for exactly this.

Realtime is worth it where users watch a screen and wait. But price it honestly. You are adding a long-lived stateful service to a stateless system. New class of problems. Not a new route.