Supabase Realtime breaks on remount unless the channel is unique
A Realtime subscription works once, then throws or goes silent when the component remounts. The cause is two subscriptions fighting over the same channel name. Clean up on unmount, and make the name unique per mount.
TL;DR · THE FIX
Subscribing twice to the same Realtime channel name (a remount, React StrictMode's double-invoke, navigating back) collides and the subscription throws or stops delivering. Always supabase.removeChannel(channel) on cleanup, and give the channel a unique name per mount so a lingering stale one can't clash.
The symptom
A Realtime subscription worked the first time the component mounted, then misbehaved on the second: it either threw tried to subscribe multiple times to a channel or quietly stopped delivering events. In development it was worse, because React’s StrictMode mounts effects twice on purpose, so it broke immediately.
What’s actually happening
A channel name is effectively a key. If you subscribe to messages and then subscribe to messages again before the first one is torn down, the two collide. That happens more often than you’d think: StrictMode invokes the effect twice, navigating away and back remounts the component, and a fast re-render can re-subscribe before cleanup runs. The root issue is two live subscriptions to the same name with no teardown between them.
The fix
Two parts. First, always remove the channel when the effect tears down, so a subscription never outlives its component. Second, give the channel a unique name per mount, so even if a stale one lingers for a tick, the new one can’t clash with it:
useEffect(() => {
const channel = supabase
.channel(`messages:${roomId}:${crypto.randomUUID()}`)
.on(
"postgres_changes",
{ event: "*", schema: "public", table: "messages", filter: `room_id=eq.${roomId}` },
handleChange,
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [roomId]);
The cleanup is the non-negotiable part: without removeChannel, subscriptions pile up every remount and you leak connections. The unique suffix is the belt-and-suspenders that makes a fast remount safe.
The lesson
Treat a Realtime channel name as a unique key for one live subscription, and tie that subscription’s lifetime to the component. Remove the channel on cleanup, and make the name unique per mount. Then StrictMode, navigation, and re-renders all stop breaking it.
Discussion
Powered by GitHub. Sign in to leave a comment.